Compare commits

..

723 Commits
v7.4.3 ... v8.0

Author SHA1 Message Date
Koitharu
a3345d11e7 Update parsers 2025-03-18 19:08:41 +02:00
Koitharu
f1ab65ec32 Fixes 2025-03-18 19:06:46 +02:00
Koitharu
6282d25d3d Merge pull request #1333 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-03-18 19:04:06 +02:00
Saterz_
47c3f9ff3b Translated using Weblate (French)
Currently translated at 99.8% (807 of 808 strings)

Co-authored-by: Saterz_ <saterzstudio@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-03-18 07:39:01 +01:00
gekka
5cd2f1b9e6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (804 of 808 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-03-18 07:39:01 +01:00
Frosted
5d9b18ec11 Translated using Weblate (Turkish)
Currently translated at 100.0% (808 of 808 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-03-18 07:39:00 +01:00
Nicola Bortoletto
5aec1f644d Translated using Weblate (Italian)
Currently translated at 100.0% (808 of 808 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-03-18 07:39:00 +01:00
Draken
aee092f0b3 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (807 of 807 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-03-18 07:39:00 +01:00
Koitharu
1e73739ddb Synchronized SieveCache wrapper 2025-03-16 12:54:00 +02:00
Koitharu
d1d7cc9adf Fix crashes 2025-03-16 12:43:31 +02:00
Koitharu
6a0ad7f79b Improve FileNotFoundException handling (#1332) 2025-03-15 10:38:41 +02:00
Koitharu
f7c70577ae Fix local chapters names (close #1323) 2025-03-15 09:00:25 +02:00
Koitharu
937ed798cf Update parsers 2025-03-15 08:16:30 +02:00
Koitharu
8da4f0e180 Merge pull request #1309 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-03-15 08:13:53 +02:00
Drama Lover
170d12f143 Translated using Weblate (Arabic)
Currently translated at 88.8% (8 of 9 strings)

Co-authored-by: Drama Lover <loverdrama053@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translation: Kotatsu/plurals
2025-03-14 19:37:56 +00:00
Akhil Raj
0fe3409577 Translated using Weblate (Malayalam)
Currently translated at 11.1% (1 of 9 strings)

Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ml/
Translation: Kotatsu/plurals
2025-03-14 19:37:55 +00:00
Laura
36e431a1ca Translated using Weblate (French)
Currently translated at 100.0% (805 of 805 strings)

Added translation using Weblate (Arabic (Algerian))

Translated using Weblate (Arabic)

Currently translated at 100.0% (805 of 805 strings)

Co-authored-by: Laura <hankmaroua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-03-14 19:37:54 +00:00
Ore Ki
f30ebda851 Translated using Weblate (Indonesian)
Currently translated at 95.5% (769 of 805 strings)

Translated using Weblate (Indonesian)

Currently translated at 95.4% (768 of 805 strings)

Co-authored-by: Ore Ki <ramadrizkyyy@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-03-14 19:37:53 +00:00
Koitharu
0f021a2d6e Translated using Weblate (Russian)
Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-03-14 19:37:53 +00:00
Alvoracz
f816c8ca6e Translated using Weblate (Czech)
Currently translated at 99.5% (799 of 803 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-03-14 19:37:52 +00:00
Y Ok
fe0c4605f7 Translated using Weblate (Indonesian)
Currently translated at 95.5% (767 of 803 strings)

Co-authored-by: Y Ok <yok111263@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-03-14 19:37:51 +00:00
Milo Ivir
196bbff103 Translated using Weblate (Croatian)
Currently translated at 100.0% (795 of 795 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2025-03-14 19:37:51 +00:00
Anon
80b26e62e9 Translated using Weblate (Serbian)
Currently translated at 98.6% (784 of 795 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-03-14 19:37:50 +00:00
Infy's Tagalog Translations
f877637fd2 Translated using Weblate (Filipino)
Currently translated at 99.7% (801 of 803 strings)

Translated using Weblate (Filipino)

Currently translated at 99.7% (793 of 795 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-03-14 19:37:49 +00:00
Frosted
5037b4ef84 Translated using Weblate (Turkish)
Currently translated at 100.0% (805 of 805 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (803 of 803 strings)

Translated using Weblate (Turkish)

Currently translated at 99.8% (802 of 803 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (799 of 799 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (798 of 798 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (795 of 795 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-03-14 19:37:49 +00:00
gekka
11b7696d31 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.7% (801 of 803 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (797 of 799 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (796 of 798 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (793 of 795 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.6% (792 of 795 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-03-14 19:37:48 +00:00
Draken
4ad361dab8 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (805 of 805 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (803 of 803 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (795 of 795 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-03-14 19:37:47 +00:00
Nicola Bortoletto
1b88857e4d Translated using Weblate (Italian)
Currently translated at 100.0% (805 of 805 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (803 of 803 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (799 of 799 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (795 of 795 strings)

Translated using Weblate (Italian)

Currently translated at 99.8% (794 of 795 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-03-14 19:37:46 +00:00
Koitharu
7823bff063 Fixes and improvements batch 2025-03-14 20:59:37 +02:00
Koitharu
947de6c7c9 Fix tags highlighting 2025-03-11 08:44:55 +02:00
Koitharu
f689bf0cf7 Improve manga link sharing 2025-03-11 08:33:11 +02:00
Koitharu
b3028258ca Apply proxy settings to WebView 2025-03-08 18:51:03 +02:00
Koitharu
2c8476cabd Improve alternatives search functionality 2025-03-08 14:33:23 +02:00
Koitharu
5373e58807 UI tuning 2025-03-08 12:26:39 +02:00
Koitharu
4fdb781622 Merge pull request #1320 from dragonx943/patch-2
Update sync domain
2025-03-08 11:45:46 +02:00
Koitharu
0981ba771a Improve window insets handling 2025-03-08 10:40:20 +02:00
Koitharu
7cec7f5359 Fix window insets handling 2025-03-05 16:56:16 +02:00
Draken
8e55739685 Update constants.xml 2025-03-05 17:10:55 +07:00
Koitharu
d4a2d97071 Fixes 2025-03-04 14:33:50 +02:00
Koitharu
d51790811a Update error details dialog 2025-03-04 14:05:45 +02:00
Koitharu
93e8e87b03 Improve global search 2025-03-03 17:38:05 +02:00
Koitharu
09590cfab0 Update reader actions bar 2025-03-03 14:03:47 +02:00
Koitharu
5d91e20844 Update parsers and fix compatibility 2025-03-02 19:31:01 +02:00
Koitharu
d918b1e274 Fix database migration 2025-03-02 13:35:09 +02:00
Koitharu
7fc2d2f36f Fixes 2025-03-01 17:07:02 +02:00
Koitharu
7a01fdd04c Update parsers and adjust database 2025-02-28 15:35:56 +02:00
Koitharu
8724f5b30c Fixes 2025-02-27 09:36:32 +02:00
Koitharu
fcc05e5e5d Merge pull request #1312 from dragonx943/patch-2 2025-02-26 19:34:23 +02:00
Draken
f6284e7107 Use domain for sync server 2025-02-27 00:19:29 +07:00
Koitharu
5e9dc87470 Fixes and improvements batch 2025-02-26 16:51:33 +02:00
Koitharu
b2f0da9245 Fix "Deflater has been closed" error 2025-02-26 08:12:52 +02:00
Koitharu
b27d6dbe9a Refactor: change window insets handling approach 2025-02-26 08:06:57 +02:00
Koitharu
a7caf9848e Dynamic peek height for BS in details 2025-02-25 13:59:41 +02:00
Koitharu
8d44ad8866 Safe way to getQuantityString 2025-02-25 08:53:45 +02:00
Koitharu
e98f5b9d54 Fix locale changing 2025-02-24 19:58:44 +02:00
Koitharu
30d1d47cdc Fixes 2025-02-24 19:42:18 +02:00
Koitharu
1fa470fd00 Option to disable captcha notification for specific source (#1253 #1300) 2025-02-24 14:57:45 +02:00
Koitharu
c835ebff3f Fix scrobbling bs ui (close #1304) 2025-02-24 13:58:14 +02:00
Koitharu
0e76e69aab Update parsers 2025-02-24 13:21:31 +02:00
Koitharu
1857d9f4e9 Merge pull request #1296 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-02-24 12:55:59 +02:00
Макар Разин
34f13ebd52 Translated using Weblate (Russian)
Currently translated at 100.0% (793 of 793 strings)

Translated using Weblate (Belarusian)

Currently translated at 94.7% (751 of 793 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-02-24 11:53:10 +01:00
Draken
2c4a71cbaa Translated using Weblate (Vietnamese)
Currently translated at 100.0% (793 of 793 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (793 of 793 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-02-24 11:53:10 +01:00
Anon
48515a13da Translated using Weblate (Serbian)
Currently translated at 98.6% (782 of 793 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-02-24 11:53:10 +01:00
Infy's Tagalog Translations
f42cda1584 Translated using Weblate (Filipino)
Currently translated at 99.7% (791 of 793 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-02-24 11:53:10 +01:00
Sixten Lund
3fbbb01e27 Translated using Weblate (Swedish)
Currently translated at 94.4% (749 of 793 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Swedish)

Currently translated at 92.5% (734 of 793 strings)

Translated using Weblate (Swedish)

Currently translated at 68.8% (546 of 793 strings)

Translated using Weblate (Swedish)

Currently translated at 66.8% (530 of 793 strings)

Co-authored-by: Sixten Lund <arbitraryindices@users.noreply.hosted.weblate.org>
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
2025-02-24 11:53:10 +01:00
Frosted
182b8abd7a Translated using Weblate (Turkish)
Currently translated at 100.0% (793 of 793 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-02-24 11:53:10 +01:00
Nicola Bortoletto
dd0445ee79 Translated using Weblate (Italian)
Currently translated at 100.0% (793 of 793 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-02-24 11:53:10 +01:00
Koitharu
d10b64c17f Merge pull request #1297 from dragonx943/patch-2
Add new sync server (Singapore)
2025-02-24 12:53:06 +02:00
Koitharu
ea13c7dbd8 Use WakeLock for background operations 2025-02-23 19:10:05 +02:00
Koitharu
fc5ad9ff90 Include reader tap settings into backups 2025-02-23 18:48:37 +02:00
Koitharu
4cee432a82 Dim navbar in details 2025-02-23 18:06:35 +02:00
Draken
d7c8b12d66 Update constants.xml 2025-02-22 15:13:40 +07:00
Koitharu
87a05ed28a Fix author search 2025-02-20 18:01:16 +02:00
Koitharu
87c242e2bb Dim navbar in details screen 2025-02-20 17:51:47 +02:00
Koitharu
1a7b9c6969 Fix nightly launcher icon 2025-02-20 16:39:12 +02:00
Koitharu
2786e1a2d5 Revert readme updates 2025-02-18 20:18:48 +02:00
Koitharu
05c37da667 Fix build 2025-02-18 20:14:52 +02:00
Koitharu
ed9ed8e964 Fix warnings 2025-02-18 20:08:03 +02:00
Johan Eliott Liebwert
cb36772fd9 Translated using Weblate (Czech)
Currently translated at 100.0% (789 of 789 strings)

Co-authored-by: Johan Eliott Liebwert <joelli@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Sixten Lund
cc8e31995b Translated using Weblate (Swedish)
Currently translated at 66.0% (521 of 789 strings)

Translated using Weblate (Swedish)

Currently translated at 44.4% (351 of 789 strings)

Co-authored-by: Sixten Lund <arbitraryindices@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sv/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Priit Jõerüüt
82cd8024e9 Translated using Weblate (Estonian)
Currently translated at 69.7% (550 of 789 strings)

Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/et/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Anon
ffc7222b3f Translated using Weblate (Serbian)
Currently translated at 98.7% (779 of 789 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Master Dwarf
08c48e9997 Translated using Weblate (Portuguese)
Currently translated at 99.8% (788 of 789 strings)

Co-authored-by: Master Dwarf <aoc.anao78@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Draken
32b6db7343 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (792 of 792 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (790 of 790 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (789 of 789 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Infy's Tagalog Translations
4dfe0f0d88 Translated using Weblate (Filipino)
Currently translated at 99.8% (788 of 789 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
gekka
0e2224eaf7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (789 of 789 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-02-18 20:05:00 +02:00
Koitharu
151777cf61 UI improvements and author search support 2025-02-18 19:29:14 +02:00
Koitharu
47a22064a5 Merge pull request #1295 from MAPKOBKA135/devel
fix 100% problem
2025-02-18 15:29:09 +02:00
Koitharu
1b8d35d424 Fix checking for removed manga updates (close #1064) 2025-02-18 15:27:10 +02:00
Koitharu
604efef832 Improve global search 2025-02-18 15:20:25 +02:00
Koitharu
6f67bd7542 Bring back reader info bar outline 2025-02-18 14:37:34 +02:00
Mac135135
52592ba765 correct display of a fully read manga
shows 100% in the details menu if the manga has actually been read in full.
2025-02-18 01:24:48 +03:00
Mac135135
9bb97c72a1 "Check mark" fix on cover
if the manga has been read in full, the "check mark" will be displayed correctly on the cover
2025-02-18 00:46:03 +03:00
Koitharu
b44cf370aa Fix favorite categories visibility (close #1281) 2025-02-17 18:06:20 +02:00
Koitharu
74900970e1 Advanced global search 2025-02-17 17:56:45 +02:00
Koitharu
4ee52e149e Configurable manga lists badges 2025-02-16 19:20:01 +02:00
Koitharu
4148f4a4b9 Update dependencies 2025-02-16 16:26:41 +02:00
Koitharu
a59b6a418d Merge pull request #1291 from TheBestF22/FutureProofing-Local-indexes
Future-Proofing Local Storage by Updating Local Archive indexes/Padding for Pages and Chapters - devel branch
2025-02-16 16:21:18 +02:00
TheBest_F-22!.
d6ae67ba07 Merge branch 'devel' into FutureProofing-Local-indexes 2025-02-16 07:57:35 +00:00
TheBest_F-22!.
be455bc897 Future-Proofing Chapters and Pages Local Storage indexes.
Closes #1289 .
2025-02-16 07:22:44 +00:00
Koitharu
129035bda3 Fix picking directory on some devices 2025-02-15 11:24:31 +02:00
Koitharu
d558c2fcc0 Saved and favorites indicators in manga lists (Draft implementation)(#1286) 2025-02-15 10:53:29 +02:00
Koitharu
cb5df0d73f UI fixes 2025-02-11 14:39:35 +02:00
Koitharu
19e8e3a618 Update kotlin to 2.1 2025-02-11 14:23:18 +02:00
Koitharu
5f0514638a Fix DateTimeAgo formatting 2025-02-09 17:40:28 +02:00
Koitharu
28ae785142 Fix bottom navigation height 2025-02-09 17:03:46 +02:00
Koitharu
8c59f97b02 Merge branch 'master' into devel 2025-02-09 10:55:55 +02:00
Koitharu
8a26587250 Database migration for downgrade from 24 to 23 (#1269, #1270) 2025-02-09 10:15:24 +02:00
Koitharu
bb68869fe1 Fix crashes 2025-02-09 10:11:24 +02:00
Koitharu
e60ca7115a Update parsers 2025-02-09 10:01:28 +02:00
Koitharu
ee4a780acf Update parsers 2025-02-08 17:04:22 +02:00
gekka
8dd6ce2739 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (788 of 789 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Deleted User
41ad7e90d3 Translated using Weblate (Italian)
Currently translated at 100.0% (789 of 789 strings)

Co-authored-by: Deleted User <noreply+104791@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
大王叫我来巡山
31be55d67a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (784 of 784 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Dragibus Noir
a50b83d876 Translated using Weblate (French)
Currently translated at 99.8% (783 of 784 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
gekka
68ce789db1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (782 of 782 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
urfv ksnph
4702862af3 Translated using Weblate (Indonesian)
Currently translated at 98.2% (768 of 782 strings)

Co-authored-by: urfv ksnph <urfvksnph@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Frosted
c403705b48 Translated using Weblate (Turkish)
Currently translated at 100.0% (789 of 789 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (784 of 784 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (782 of 782 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Nicola Bortoletto
22a5f2d5ee Translated using Weblate (Italian)
Currently translated at 100.0% (782 of 782 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Infy's Tagalog Translations
0f1d8d2835 Translated using Weblate (Filipino)
Currently translated at 99.7% (787 of 789 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Bruno Fragoso
1a6b1672b3 Translated using Weblate (Portuguese)
Currently translated at 100.0% (782 of 782 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Juan Rubin
8e6006177b Translated using Weblate (Portuguese)
Currently translated at 99.7% (779 of 781 strings)

Co-authored-by: Juan Rubin <juancrubin08@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Draken
4e2f010260 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (789 of 789 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (782 of 782 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Nizar Yazidi
8a191f8f04 Translated using Weblate (Italian)
Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: Nizar Yazidi <fedekikamakeup@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Milan Bhandari
88aca02234 Translated using Weblate (Nepali)
Currently translated at 31.6% (247 of 781 strings)

Co-authored-by: Milan Bhandari <milanbhandari604@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Hosted Weblate
9189307d00 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Update translation files

Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
zmni
00fc579824 Translated using Weblate (Indonesian)
Currently translated at 97.9% (765 of 781 strings)

Translated using Weblate (Indonesian)

Currently translated at 97.9% (765 of 781 strings)

Co-authored-by: zmni <zmni@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Anon
7f8d78e0c9 Translated using Weblate (Serbian)
Currently translated at 98.6% (778 of 789 strings)

Translated using Weblate (Serbian)

Currently translated at 99.2% (775 of 781 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Alvoracz
13bd3e918e Translated using Weblate (Czech)
Currently translated at 98.2% (767 of 781 strings)

Translated using Weblate (Czech)

Currently translated at 97.1% (759 of 781 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-02-08 16:44:06 +02:00
Koitharu
e1c46f0604 Fix closing welcome bottom sheet 2025-02-05 17:43:02 +02:00
Koitharu
79f3fe196c Update emoji flags mapping 2025-02-05 16:57:18 +02:00
Koitharu
87a47207ab Save page button in reader 2025-02-05 16:06:53 +02:00
Koitharu
772fd5cc6a Screen rotation button in reader 2025-02-05 15:54:54 +02:00
Koitharu
26a2ffcbb1 Reader bottom bar actions configurable 2025-02-05 14:56:41 +02:00
Koitharu
cfecfce51e Update parsers 2025-02-03 18:56:05 +02:00
Koitharu
23c814bf53 Allow to keep user data on app removal 2025-02-03 18:56:04 +02:00
Koitharu
8ca11b214c Reorganize storage usage settings 2025-02-03 18:56:04 +02:00
Koitharu
008f2d705a Restore backups in background 2025-02-03 18:56:00 +02:00
ViAnh
c37f795dac Fix scroller handle being cut off 2025-02-03 18:21:03 +02:00
Koitharu
5d74bdd3b4 Fix reader info bar background settings 2025-02-02 11:56:08 +02:00
Koitharu
382b44accc Transparent reader info bar option 2025-02-01 09:33:11 +02:00
Koitharu
78cd0eff09 Fix reader info bar icon color 2025-02-01 09:33:11 +02:00
Koitharu
12c954600f Merge pull request #1256 from dragonx943/telegram-feature 2025-01-30 09:58:23 +02:00
Talkc0n
a7d4f3b784 Add Telegram backup feature 2025-01-30 14:09:16 +07:00
Koitharu
91f9feba59 Fix details ui 2025-01-30 08:44:01 +02:00
Koitharu
e03a200c32 Fix build 2025-01-25 12:51:13 +02:00
Koitharu
8713faa487 Update parsers 2025-01-25 12:14:01 +02:00
Koitharu
15e99c03a9 Update parsers and adjust imports
(cherry picked from commit 5e8aa4cec7)
2025-01-22 20:21:43 +02:00
Koitharu
b3933848e9 Local manga parsing fixes 2025-01-22 10:15:12 +02:00
Koitharu
e39e5bf9c4 Remove Telegram backup functionality 2025-01-22 09:18:13 +02:00
Koitharu
3a42dce45f Support nested cbz covers 2025-01-19 16:26:00 +02:00
Koitharu
169539f42f Support closing Okio FileSystem 2025-01-19 15:18:07 +02:00
Koitharu
6360731f34 Update reader info bar 2025-01-19 15:02:34 +02:00
Koitharu
914dd9670a Option to disable LeakCanary in debug builds 2025-01-19 13:48:55 +02:00
Koitharu
498b9aed26 Track services using LeakCanary 2025-01-19 13:18:19 +02:00
Koitharu
b425f3e779 Fix nullability for ParcelableManga
(cherry picked from commit b8b601821a)
2025-01-19 12:00:38 +02:00
Koitharu
c6a51d4d08 Increase version 2025-01-19 11:56:26 +02:00
Koitharu
b8b601821a Fix nullability for ParcelableManga 2025-01-19 11:55:13 +02:00
Koitharu
5e8aa4cec7 Update parsers and adjust imports 2025-01-19 11:53:38 +02:00
Koitharu
54bb02937d Merge branch 'master' into devel 2025-01-19 10:33:34 +02:00
Koitharu
74fe786c00 Detect miui 2025-01-19 09:45:51 +02:00
Koitharu
503bff292c Made SyncAuthActivity exported
(cherry picked from commit 663602282a)
2025-01-19 08:28:02 +02:00
Koitharu
0aa78c0d7e Adjust manga fields nullability 2025-01-19 08:24:25 +02:00
Koitharu
8e1d02f356 Update parsers 2025-01-19 08:01:09 +02:00
Koitharu
e8f9d22128 Update dependencies 2025-01-18 15:24:45 +02:00
Draken
fa60ae2947 Add new resources (#1242) 2025-01-17 15:55:35 +02:00
Koitharu
663602282a Made SyncAuthActivity exported 2025-01-13 20:01:22 +02:00
Koitharu
98dbc20cb0 Update parsers and adjust fields nullability 2025-01-13 20:01:22 +02:00
Draken
cc28293bed Update docs (README file) (#1238)
* Update README + Add new images

* [Docs] Tweak + Fixes

* [Docs] Fix image size

* Add some informations + Docs

* [vi] Update screenshots

* Add new counter

* Fix color

* [ru] Add new screenshots

* Update readme

* Update Readme

* Update Readme

* Update Readme

* Refactor images

* Refactor images

* Fix refactor images

* Update descriptions

* Fix themes

* Fix themes

* Final ?

---------

Co-authored-by: Draken <dragonx943@users.noreply.github.com>
2025-01-11 15:36:36 +02:00
Koitharu
1e90d5541b Update parsers 2025-01-11 15:09:21 +02:00
Koitharu
04c7ca7291 Improve local manga chapter names
(cherry picked from commit dddb00d5ef)
2025-01-11 14:59:38 +02:00
Koitharu
8d52cab6d8 Fix manga importing
(cherry picked from commit dcb92ed1af)
2025-01-11 14:59:33 +02:00
Koitharu
efa13df106 Fix crashes 2025-01-11 14:59:25 +02:00
Koitharu
8bc29ac331 Fix local chapters deletion
(cherry picked from commit 25eb05d305)
2025-01-11 14:58:53 +02:00
Koitharu
7991f9ca97 Skip description for ParcelableManga
(cherry picked from commit bf217b3cbf)
2025-01-11 14:58:44 +02:00
Koitharu
eb1eee1681 Fix pages cache usage
(cherry picked from commit 9e2b60e15e)
2025-01-11 14:57:56 +02:00
Koitharu
b3f748c000 Fix crashes
(cherry picked from commit 4dba90361c)
2025-01-11 14:57:49 +02:00
Koitharu
58a9f7b25a Fix settings menu
(cherry picked from commit c51218240e)
2025-01-11 14:56:13 +02:00
Koitharu
dddb00d5ef Improve local manga chapter names 2025-01-11 14:54:02 +02:00
Koitharu
c9d878a0b7 Upgrade agp 2025-01-11 14:37:49 +02:00
Koitharu
dcb92ed1af Fix manga importing 2025-01-11 14:37:30 +02:00
Maple Javora
749bc4a837 Translated using Weblate (Czech)
Currently translated at 97.0% (758 of 781 strings)

Co-authored-by: Maple Javora <jindrous101@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Alvoracz
94807b7788 Translated using Weblate (Czech)
Currently translated at 97.0% (758 of 781 strings)

Translated using Weblate (Czech)

Currently translated at 94.2% (736 of 781 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Maple Javora
0fe7c66850 Translated using Weblate (Czech)
Currently translated at 94.3% (737 of 781 strings)

Translated using Weblate (Czech)

Currently translated at 94.2% (736 of 781 strings)

Co-authored-by: Maple Javora <jindrous101@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Alvoracz
20cd8413dc Translated using Weblate (Czech)
Currently translated at 94.2% (736 of 781 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
தமிழ்நேரம்
30df4ede6c Translated using Weblate (Tamil)
Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Tamil)

Added translation using Weblate (Tamil)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ta/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ta/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-01-11 11:21:58 +01:00
Milan Bhandari
4aa6baf569 Translated using Weblate (Nepali)
Currently translated at 30.7% (240 of 781 strings)

Co-authored-by: Milan Bhandari <githubmilan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
abdelbasset jabrane
d8a4303c50 Translated using Weblate (Arabic)
Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: abdelbasset jabrane <ribago9317@cubene.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Макар Разин
b355e2ee88 Translated using Weblate (Russian)
Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Infy's Tagalog Translations
55e3b5fb9b Translated using Weblate (Filipino)
Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
gekka
a59853e37a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (781 of 781 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Milo Ivir
ccc665d218 Translated using Weblate (Croatian)
Currently translated at 99.7% (776 of 778 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Anon
02650f5c2a Translated using Weblate (Serbian)
Currently translated at 98.5% (767 of 778 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Hugo Cardoso
24172a1137 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.4% (774 of 778 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.4% (774 of 778 strings)

Co-authored-by: Hugo Cardoso <hugocardosolopes@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Draken
034d69d490 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (778 of 778 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Justine Kyle Cobar
12fc0542d3 Translated using Weblate (Filipino)
Currently translated at 100.0% (778 of 778 strings)

Co-authored-by: Justine Kyle Cobar <cobarjustinekyle583@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Nicola Bortoletto
dcf80ed396 Translated using Weblate (Italian)
Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (778 of 778 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Dragibus Noir
28badb7f6c Translated using Weblate (French)
Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (French)

Currently translated at 100.0% (778 of 778 strings)

Translated using Weblate (French)

Currently translated at 99.8% (777 of 778 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-01-11 11:21:58 +01:00
Itsmechinmoy
19cc158ef8 Translated using Weblate (Assamese)
Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Assamese)

Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/as/
Translation: Kotatsu/plurals
2025-01-11 11:21:57 +01:00
大王叫我来巡山
a2eeae3319 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (778 of 778 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-01-11 11:21:57 +01:00
Frosted
c9336a753d Translated using Weblate (Turkish)
Currently translated at 100.0% (781 of 781 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (778 of 778 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-01-11 11:21:57 +01:00
Anonymous
ea23468ecd Translated using Weblate (French)
Currently translated at 99.2% (772 of 778 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-01-11 11:21:57 +01:00
Koitharu
143643fcd8 Fix crashes 2025-01-11 12:21:40 +02:00
Koitharu
25eb05d305 Fix local chapters deletion 2025-01-09 08:41:27 +02:00
Koitharu
bf217b3cbf Skip description for ParcelableManga 2025-01-09 08:32:53 +02:00
Koitharu
9e2b60e15e Fix pages cache usage 2025-01-09 08:26:43 +02:00
Koitharu
4dba90361c Fix crashes 2025-01-09 08:19:43 +02:00
Koitharu
8dea483f64 Fix drawables state 2025-01-05 10:17:40 +02:00
Koitharu
dc2e603356 Improve drawable and views state management 2025-01-04 16:22:13 +02:00
Koitharu
14973298a0 Emoji flags in details 2025-01-04 13:53:13 +02:00
Koitharu
7efc47724e Improve mime-type handling 2025-01-04 12:01:48 +02:00
Koitharu
c51218240e Fix settings menu 2025-01-02 15:45:17 +02:00
Koitharu
2762caaa8f Option to enable all sources 2025-01-02 15:38:43 +02:00
Koitharu
70d66e5a90 Merge branch 'master' into devel 2025-01-01 15:39:51 +02:00
Koitharu
fc1d704f6f Fix build 2025-01-01 14:24:43 +02:00
Koitharu
c2c3b0f757 Fix details cover corners 2025-01-01 14:00:04 +02:00
Koitharu
8d519dd80f Fix settings search 2025-01-01 13:59:59 +02:00
Koitharu
3b5a9cd2b4 Skip non-existing local chapters 2025-01-01 13:59:54 +02:00
Koitharu
95f4d39893 Update parsers 2025-01-01 13:53:17 +02:00
Koitharu
3173e30caf Fix details cover corners 2025-01-01 13:49:14 +02:00
Koitharu
0dccc66f54 Fix settings search 2025-01-01 13:36:02 +02:00
Koitharu
6b3dd23c01 UI fixes 2025-01-01 12:36:52 +02:00
Koitharu
1c6a125174 Skip non-existing local chapters 2025-01-01 12:16:28 +02:00
Koitharu
f3f269c7fa Fix NPE in SyncSettings 2024-12-30 10:01:08 +02:00
Koitharu
15f37644c0 Update list badges 2024-12-30 09:48:50 +02:00
Koitharu
c2079ebca5 Update dependencies 2024-12-30 07:51:45 +02:00
Koitharu
1146269992 Merge branch 'weblate-kotatsu-strings' of github.com:weblate/Kotatsu into weblate-weblate-kotatsu-strings 2024-12-23 10:43:10 +02:00
Koitharu
099362d198 Update reader ui 2024-12-23 10:38:27 +02:00
Koitharu
22d203fc60 Fix Telegram backups uploading 2024-12-22 09:01:18 +02:00
Anonymous
19602144ef Translated using Weblate (Norwegian Nynorsk)
Currently translated at 48.8% (380 of 778 strings)

Translated using Weblate (Finnish)

Currently translated at 33.0% (257 of 778 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 36.6% (285 of 778 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nn/
Translation: Kotatsu/Strings
2024-12-22 05:01:27 +00:00
Lennard
44bbcd7fe3 Translated using Weblate (Lithuanian)
Currently translated at 5.6% (44 of 778 strings)

Translated using Weblate (Punjabi)

Currently translated at 4.2% (33 of 778 strings)

Translated using Weblate (Czech)

Currently translated at 86.7% (675 of 778 strings)

Translated using Weblate (Arabic)

Currently translated at 85.8% (668 of 778 strings)

Translated using Weblate (English)

Currently translated at 100.0% (778 of 778 strings)

Co-authored-by: Lennard <lennardsdrojek42@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pa/
Translation: Kotatsu/Strings
2024-12-22 05:01:25 +00:00
Frosted
efe5e07c2c Translated using Weblate (Turkish)
Currently translated at 100.0% (778 of 778 strings)

Translated using Weblate (Turkish)

Currently translated at 99.6% (775 of 778 strings)

Translated using Weblate (Turkish)

Currently translated at 98.7% (766 of 776 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-12-22 05:01:24 +00:00
Geovani Amaral
4e633ff735 Translated using Weblate (Portuguese)
Currently translated at 100.0% (776 of 776 strings)

Co-authored-by: Geovani Amaral <geovani.af4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-12-22 05:01:21 +00:00
Dragibus Noir
fef8333763 Translated using Weblate (French)
Currently translated at 99.7% (774 of 776 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2024-12-22 05:01:20 +00:00
大王叫我来巡山
a741f8451a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (778 of 778 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (776 of 776 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-12-22 05:01:18 +00:00
Draken
55baf5a3f3 Translated using Weblate (Vietnamese)
Currently translated at 99.8% (777 of 778 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (772 of 772 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-12-22 05:01:16 +00:00
Anon
6390774d86 Translated using Weblate (Serbian)
Currently translated at 99.0% (769 of 776 strings)

Translated using Weblate (Serbian)

Currently translated at 99.0% (769 of 776 strings)

Translated using Weblate (Serbian)

Currently translated at 99.2% (766 of 772 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-12-22 05:01:13 +00:00
gekka
51a5128e70 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (772 of 772 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-12-22 05:01:12 +00:00
Erekotr
53d81507e4 Translated using Weblate (Turkish)
Currently translated at 98.9% (764 of 772 strings)

Co-authored-by: Erekotr <ereko1ereko55@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-12-22 05:01:11 +00:00
gallegonovato
dcf7236ba2 Translated using Weblate (Spanish)
Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (772 of 772 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-12-22 05:01:09 +00:00
Koitharu
a54744abc6 Option to clear manga database 2024-12-20 12:30:48 +02:00
Koitharu
22e2411c77 Cache chapters list (close #812) 2024-12-20 11:33:16 +02:00
Koitharu
3f66c142b8 Merge branch 'master' into devel 2024-12-19 18:30:16 +02:00
Koitharu
40f262b0ef Update parsers 2024-12-19 17:28:12 +02:00
Koitharu
0f68be9663 Use advanced bitmap decoder for MangaLoaderContext 2024-12-19 17:10:01 +02:00
Koitharu
0b8afe9c40 Fix checking for new chapters in some cases (#1212, #1195, #1190) 2024-12-18 18:26:27 +02:00
Koitharu
734846a018 Fix checking for new chapters in some cases (#1212, #1195, #1190) 2024-12-18 18:24:41 +02:00
Koitharu
754ccc4197 Added url for NoDataReceivedException 2024-12-18 16:26:49 +02:00
Koitharu
ef691b1aed Update parsers 2024-12-18 15:48:57 +02:00
Koitharu
e75035b33a Update parsers 2024-12-18 15:38:56 +02:00
Koitharu
f675c606a2 Refactor navigation 2024-12-18 12:00:25 +02:00
Koitharu
a5199e2f06 New favorite dialog 2024-12-16 19:17:25 +02:00
Koitharu
1b80e48ed4 Telegram backups refactoring stage 2 2024-12-15 09:44:57 +02:00
Koitharu
07e81f21c7 Telegram backups refactoring stage 1 2024-12-14 16:26:37 +02:00
Koitharu
0dbd01f6fc Merge branch 'MAPKOBKA135-devel' into devel 2024-12-14 15:48:18 +02:00
Koitharu
4b453b58dd Fix reader slider visibility 2024-12-14 15:48:09 +02:00
Koitharu
1575bb5242 Merge branch 'devel' of github.com:MAPKOBKA135/Kotatsu into MAPKOBKA135-devel 2024-12-14 15:47:41 +02:00
Infy's Tagalog Translations
55137cf899 Translated using Weblate (Filipino)
Currently translated at 99.4% (762 of 766 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
Lennard
f190ff810e Translated using Weblate (German)
Currently translated at 83.3% (639 of 767 strings)

Translated using Weblate (German)

Currently translated at 83.0% (636 of 766 strings)

Co-authored-by: Lennard <lennardsdrojek42@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
Anon
47c13b46f7 Translated using Weblate (Serbian)
Currently translated at 99.6% (761 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.6% (759 of 762 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
Frosted
2ad9f38906 Translated using Weblate (Turkish)
Currently translated at 100.0% (762 of 762 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
gekka
2783c62ace Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (766 of 766 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (762 of 762 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
Draken
c1a65f8055 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (766 of 766 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (762 of 762 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
大王叫我来巡山
6e5d8e99ca Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (762 of 762 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
Dragibus Noir
020c3b8bba Translated using Weblate (French)
Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.8% (761 of 762 strings)

Translated using Weblate (French)

Currently translated at 99.7% (760 of 762 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2024-12-14 15:41:11 +02:00
Koitharu
76162a06e3 Reader ui updates 2024-12-14 15:39:13 +02:00
Koitharu
19f398d309 Merge branch 'master' into devel 2024-12-14 14:25:48 +02:00
Koitharu
1bd916371a Update parsers 2024-12-14 09:36:50 +02:00
Koitharu
25ae23963e Update reader interface 2024-12-14 09:26:01 +02:00
Koitharu
146ba95af6 Details activity improvements 2024-12-11 13:22:07 +02:00
Koitharu
cd40dab8a4 Error handling fixes 2024-12-10 14:29:55 +02:00
Koitharu
ee10b013a1 Branch selection in chapters list 2024-12-08 19:26:18 +02:00
Koitharu
8c79df3d35 Details ui updates 2024-12-08 18:20:46 +02:00
Koitharu
2c2db1ca96 Rollback kotlin 2024-12-08 09:58:18 +02:00
Koitharu
f556c0b127 Merge branch 'master' into devel 2024-12-07 15:55:32 +02:00
Koitharu
d2ed8a1ace Update parsers 2024-12-07 15:18:18 +02:00
حيدر العراقي
024e3c11ee Translated using Weblate (Arabic)
Currently translated at 88.0% (668 of 759 strings)

Co-authored-by: حيدر العراقي <haiderdc12@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
return_null
23ba302df8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: return_null <demolang@dismail.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
maryush
34e54e43e0 Translated using Weblate (Polish)
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
Anon
07a8de6225 Translated using Weblate (Serbian)
Currently translated at 99.6% (756 of 759 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
gallegonovato
a3df6f799c Translated using Weblate (Spanish)
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
Maple Javora
d5722790ef Translated using Weblate (Czech)
Currently translated at 89.0% (676 of 759 strings)

Co-authored-by: Maple Javora <jindrous101@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
johan
8bf540abbe Translated using Weblate (Czech)
Currently translated at 89.0% (676 of 759 strings)

Co-authored-by: johan <jqb4@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
gekka
5241fa0d13 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
Dragibus Noir
87e0c931a2 Translated using Weblate (French)
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
Infy's Tagalog Translations
a51412801a Translated using Weblate (Filipino)
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
TheOneWhoCares
a6c188d647 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
Frosted
831632cb8f Translated using Weblate (Turkish)
Currently translated at 100.0% (759 of 759 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-12-07 15:17:34 +02:00
Koitharu
ad59bf50f4 Fix loading local manga without index #1192 2024-12-05 18:39:31 +02:00
Koitharu
6fe6c05327 Update parsers 2024-12-05 18:38:47 +02:00
Koitharu
66645d93f8 Update parsers 2024-12-05 15:52:16 +02:00
Koitharu
f2582bce1d Update dependencies 2024-12-03 14:36:47 +02:00
Koitharu
b5053b7820 Update parsers 2024-11-29 09:31:46 +02:00
Koitharu
e4df81495d Merge pull request #1184 from weblate/weblate-kotatsu-strings 2024-11-28 16:10:45 +02:00
Anon
295c5bed9f Translated using Weblate (Serbian)
Currently translated at 99.6% (755 of 758 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-11-28 12:24:33 +01:00
TheOneWhoCares
5fd1cbadcd Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.4% (739 of 758 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.6% (725 of 758 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-11-28 12:24:33 +01:00
Gabriel Vasconcelos
9dd86f57e6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.6% (725 of 758 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.6% (710 of 758 strings)

Co-authored-by: Gabriel Vasconcelos <gabriels.v9@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-11-28 12:24:33 +01:00
TheOneWhoCares
bce6d71743 Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.6% (710 of 758 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-11-28 12:24:33 +01:00
Frosted
6367c06f49 Translated using Weblate (Turkish)
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
Dragibus Noir
3aa8e9d6d3 Translated using Weblate (French)
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
Draken
ac2b367312 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
Justine Kyle Cobar
5cd9b02159 Translated using Weblate (Filipino)
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: Justine Kyle Cobar <cobarjustinekyle583@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
Infy's Tagalog Translations
0bd62c6925 Translated using Weblate (Filipino)
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
gekka
d657216a69 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (758 of 758 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
大王叫我来巡山
39f91464dc Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-11-28 12:24:32 +01:00
gallegonovato
05422b95a1 Translated using Weblate (Spanish)
Currently translated at 100.0% (758 of 758 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-11-28 12:24:31 +01:00
arasseo.
554e3c1b61 Change ZERO_MS DNS
Switch to the primary DNS because the performance is better
2024-11-27 10:11:57 +02:00
Koitharu
56ece80f2a Bump version 2024-11-25 10:03:00 +02:00
Koitharu
3ebde0284d Kitsu fixes #1151 2024-11-24 13:42:52 +02:00
Koitharu
c993488fe7 Option to disable link handling #1149 2024-11-24 10:47:06 +02:00
Koitharu
e65a3b43f6 Fixes 2024-11-24 09:37:46 +02:00
Koitharu
f11a9d8235 Update parsers 2024-11-23 15:15:41 +02:00
Koitharu
8a4bd9a19a Fix "Deflater has been closed" error 2024-11-23 13:17:51 +02:00
Koitharu
cffc6cfd39 Fixes 2024-11-23 09:00:06 +02:00
Koitharu
1568a48328 Update parsers 2024-11-21 08:00:53 +02:00
nichind
0b47b113e0 Translated using Weblate (Russian)
Currently translated at 100.0% (755 of 755 strings)

Co-authored-by: nichind <nichinddev@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-11-21 07:56:13 +02:00
Koitharu
67a5ef016c Fix cleaning saved chapters 2024-11-18 18:59:17 +02:00
Anupam Malhotra
09c049ea9d Translated using Weblate (Hindi)
Currently translated at 88.7% (670 of 755 strings)

Co-authored-by: Anupam Malhotra <anpm.malhotra@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
2024-11-16 17:25:21 +02:00
Gabriel Vasconcelos
0dc1cad52b Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.5% (706 of 755 strings)

Co-authored-by: Gabriel Vasconcelos <gabriels.v9@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-11-16 17:25:21 +02:00
Milo Ivir
782ea0541e Translated using Weblate (Croatian)
Currently translated at 100.0% (755 of 755 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Anon
b220703dd4 Translated using Weblate (Serbian)
Currently translated at 99.7% (753 of 755 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Paul Schönfisch
c5b6586cf4 Translated using Weblate (German)
Currently translated at 83.5% (631 of 755 strings)

Co-authored-by: Paul Schönfisch <asterdux2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
gallegonovato
1ba40ea248 Translated using Weblate (Spanish)
Currently translated at 100.0% (755 of 755 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Hosted Weblate
e8fd2b0dcf Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Maple Javora
046b7b6ef1 Translated using Weblate (Czech)
Currently translated at 87.2% (657 of 753 strings)

Co-authored-by: Maple Javora <jindrous101@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Nicola Bortoletto
907856a0df Translated using Weblate (Italian)
Currently translated at 99.7% (753 of 755 strings)

Translated using Weblate (Italian)

Currently translated at 98.0% (738 of 753 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
gekka
071509ecd1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (755 of 755 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (753 of 753 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Frosted
a0cb34b984 Translated using Weblate (Turkish)
Currently translated at 100.0% (755 of 755 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Dragibus Noir
7fe8217f6d Translated using Weblate (French)
Currently translated at 100.0% (755 of 755 strings)

Translated using Weblate (French)

Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (French)

Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Infy's Tagalog Translations
58937f9fc6 Translated using Weblate (Filipino)
Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Илья
528b85e9ce Translated using Weblate (Russian)
Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Илья <ilya.megavolt.37@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Draken
b57fdd5a99 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (755 of 755 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
大王叫我来巡山
1ad29cebd7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
gekka
7516303b7d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-11-16 17:22:09 +02:00
Koitharu
b2bfebaea2 Update dependencies 2024-11-16 17:09:21 +02:00
Koitharu
9fcff1eac7 Fix crashes 2024-11-16 10:19:42 +02:00
Koitharu
19446db192 Misc improvements 2024-11-13 12:53:58 +02:00
Koitharu
609f2bd134 Fixes 2024-11-12 08:51:23 +02:00
Mac135135
3ef7c6adb0 Added an periodical backup to the telegram bot 2024-11-10 15:11:40 +03:00
Mac135135
62e7e5d8c3 Merge remote-tracking branch 'origin/devel' into devel 2024-11-10 14:19:06 +03:00
Koitharu
644f0af262 Refactor dependencies catalog 2024-11-10 12:58:21 +02:00
Koitharu
a1e5d78877 Update parsers 2024-11-10 10:57:03 +02:00
Koitharu
635839065d Batch pages saving 2024-11-09 11:45:04 +02:00
Koitharu
bb6f7b1e9f Fix external backup crashes 2024-11-09 09:57:15 +02:00
Mac135135
30e43d3bfe Merge remote-tracking branch 'origin/devel' into devel
# Conflicts:
#	app/src/main/kotlin/org/koitharu/kotatsu/settings/backup/PeriodicalBackupSettingsFragment.kt
#	app/src/main/kotlin/org/koitharu/kotatsu/settings/backup/PeriodicalBackupWorker.kt
2024-11-07 23:47:47 +03:00
Koitharu
1f0180d601 Fix periodical backup creation interval 2024-11-07 15:01:09 +02:00
Koitharu
cdce2af4a3 Fix nightly version name parsing 2024-11-07 14:47:52 +02:00
Koitharu
11212ed071 Update readme 2024-11-07 14:34:26 +02:00
Koitharu
e2902fa1ba Fix dependencies 2024-11-07 14:26:36 +02:00
Koitharu
5158f2a70a Merge branch 'CodeWithTamim-add/libs-version-toml-dependency-management' into devel 2024-11-07 13:11:18 +02:00
Koitharu
f9e4752b8c Merge branch 'add/libs-version-toml-dependency-management' of github.com:CodeWithTamim/Kotatsu into CodeWithTamim-add/libs-version-toml-dependency-management 2024-11-07 13:10:41 +02:00
Koitharu
901ffebf97 Change nightly updates repo 2024-11-06 09:54:53 +02:00
Koitharu
dba727bfcb Improvements for nightly build 2024-11-06 09:28:42 +02:00
Koitharu
3ee97a3b99 Fix nightly versionName/versionCode 2024-11-05 13:36:46 +02:00
Koitharu
57d1f54318 Refactor pages saving 2024-11-05 11:46:59 +02:00
Koitharu
02073f6d45 Convert launcher icons to webp 2024-11-05 09:22:30 +02:00
Koitharu
b66a77843e Add nightly build type 2024-11-05 09:21:24 +02:00
Koitharu
03518dd9b4 Update dependencies 2024-11-05 08:43:37 +02:00
Koitharu
d926f334e8 Merge branch 'master' into devel 2024-11-04 16:30:54 +02:00
Koitharu
e2f8d8e022 Fix downloading slowdown 2024-11-04 16:04:18 +02:00
Koitharu
38b342b721 Update dependencies, fix covers restoring 2024-11-04 15:41:16 +02:00
Koitharu
b036a8ed94 Fixes batch 2024-11-04 10:02:08 +02:00
Koitharu
e4fda86bf1 Small fixes 2024-11-02 15:35:01 +02:00
Koitharu
6e20cee972 Fix periodical backups 2024-11-02 15:34:55 +02:00
Claudio Riccio
8901d02dba Update app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
Co-authored-by: Koitharu <nvasya95@gmail.com>
2024-11-02 13:10:41 +02:00
Claudio Riccio
a87b37ce1c Changes relative to issue#1102
Manga pages now have a proposed name as follow: "MangaName-MangaChapter-MangaPage_yyyy-MM-dd_HHmm.ImageExtension"
2024-11-02 13:10:41 +02:00
Claudio Riccio
4f22e29ad6 Changes relative to issue#1102
Manga pages now have a proposed name as follow: "MangaName-MangaChapter-MangaPage_yyyy-MM-dd_HHmm.ImageExtension"
2024-11-02 13:10:41 +02:00
Draken
6effb928fd Translated using Weblate (Vietnamese)
Currently translated at 100.0% (749 of 749 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Infy's Tagalog Translations
1b1d0014da Translated using Weblate (Filipino)
Currently translated at 100.0% (749 of 749 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
gekka
a9632f542b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (749 of 749 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
gallegonovato
a2c256d47f Translated using Weblate (Spanish)
Currently translated at 100.0% (749 of 749 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Anonymous
f87a75e61e Translated using Weblate (Catalan)
Currently translated at 11.7% (88 of 749 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ca/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Макар Разин
09354ae31f Translated using Weblate (Russian)
Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (748 of 748 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Mahmuod abd alalem Selem abd al amed
fb25b8fb3a Translated using Weblate (Arabic)
Currently translated at 89.0% (666 of 748 strings)

Co-authored-by: Mahmuod abd alalem Selem abd al amed <selemabdalamedmahmuodabdalalem@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Justine Kyle Cobar
c8b935ccc3 Translated using Weblate (Filipino)
Currently translated at 100.0% (748 of 748 strings)

Co-authored-by: Justine Kyle Cobar <cobarjustinekyle583@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Anon
7f0376d792 Translated using Weblate (Serbian)
Currently translated at 100.0% (748 of 748 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-11-02 12:31:21 +02:00
Koitharu
0c56e730fe Change periodical backup creation 2024-11-02 12:30:04 +02:00
Koitharu
a7138d23ac Small fixes 2024-10-30 12:54:49 +02:00
Koitharu
a0de73a7ed PageSaveHelper refactor 2024-10-27 18:02:31 +02:00
Koitharu
90f0846fb4 Small fixes 2024-10-27 16:28:11 +02:00
Koitharu
9425d29596 Migrate LocalMangaInfo to Okio 2024-10-27 13:49:06 +02:00
Koitharu
9bb76cc0b2 Update parsers (fix json iterator) 2024-10-26 09:03:50 +03:00
Koitharu
ad0452486f Merge branch 'master' into devel 2024-10-24 12:48:06 +03:00
Koitharu
855b55da9d Update parsers 2024-10-23 19:47:27 +03:00
Koitharu
436168b940 Migrate to coil3 2024-10-23 18:55:10 +03:00
Koitharu
681c80dc3e Fix RegionBitmapDecode usage 2024-10-23 09:15:32 +03:00
Koitharu
4855b2c160 Fix RegionBitmapDecode usage 2024-10-23 09:10:43 +03:00
Koitharu
89d395178c Support for AVIF images
(cherry picked from commit c15a0ece3e)
2024-10-23 08:38:52 +03:00
Koitharu
9942ad5e56 Fix pages loading issues
(cherry picked from commit 5bccc595a8)
2024-10-23 08:37:50 +03:00
Koitharu
d59b0626bc Fix webtoon page detection #1140
(cherry picked from commit 985b062218)
2024-10-23 08:37:00 +03:00
Marius Albrecht
63054e55d6 Give "Complete" status only to fully completed Manga
Up until now a progress of >= 99.5% would count a Manga as completed (and show the checkmark icon). This causes manga with 200 chapters or more to be marked as completed even if they have at least one unread chapter.

https://github.com/KotatsuApp/Kotatsu/issues/1105
(cherry picked from commit b6f57e5656)
2024-10-23 08:36:52 +03:00
Koitharu
486daf69bf Update link resolver
(cherry picked from commit c1d577bdf3)
2024-10-23 08:36:38 +03:00
Koitharu
af209d7048 Fix external plugin communication
(cherry picked from commit 2214c20742)
2024-10-23 08:36:26 +03:00
Koitharu
c15a0ece3e Support for AVIF images 2024-10-22 14:20:13 +03:00
Tamim Hossain
6bf034fd37 Add libs.versions.toml for centralized dependency management
- Introduced `libs.versions.toml` to manage dependencies in a centralized and structured manner.
- This improves maintainability and makes it easier to update and manage library versions across the project.
- Follows best practices for Gradle dependency management by separating version definitions from build scripts.
2024-10-22 01:46:26 +06:00
Koitharu
5bccc595a8 Fix pages loading issues 2024-10-21 13:34:33 +03:00
Priit Jõerüüt
9559e148c6 Translated using Weblate (Estonian)
Currently translated at 70.3% (526 of 748 strings)

Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/et/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
J. Lavoie
637a040a0b Translated using Weblate (French)
Currently translated at 99.1% (742 of 748 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Yoshi Nizar
2bdf146548 Translated using Weblate (Italian)
Currently translated at 90.9% (680 of 748 strings)

Co-authored-by: Yoshi Nizar <canalefinto@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
shimanchu
22831a9796 Translated using Weblate (Japanese)
Currently translated at 62.1% (464 of 747 strings)

Co-authored-by: shimanchu <shimano@knd.biglobe.ne.jp>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
大王叫我来巡山
b5bc64c89f Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (747 of 747 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Hosted Weblate
f2ad58bc97 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Akhil Raj
835a1c73b6 Translated using Weblate (Malayalam)
Currently translated at 2.8% (21 of 744 strings)

Co-authored-by: Akhil Raj <89210430+akhi07rx@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ml/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Felipe Nascimento
5b8a628715 Translated using Weblate (Portuguese)
Currently translated at 98.7% (735 of 744 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Nayuki
4f5418e074 Translated using Weblate (Thai)
Currently translated at 61.0% (454 of 744 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Infy's Tagalog Translations
1cf56b2303 Translated using Weblate (Filipino)
Currently translated at 98.2% (734 of 747 strings)

Translated using Weblate (Filipino)

Currently translated at 98.2% (731 of 744 strings)

Translated using Weblate (Filipino)

Currently translated at 98.2% (731 of 744 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
maryush
a47dcd9ec2 Translated using Weblate (Polish)
Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Polish)

Currently translated at 99.8% (737 of 738 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Макар Разин
7873cc4099 Translated using Weblate (Russian)
Currently translated at 100.0% (733 of 733 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (733 of 733 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
gekka
9002915e30 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (733 of 733 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Draken
099d9df84c Translated using Weblate (Vietnamese)
Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (733 of 733 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
大王叫我来巡山
e531e6bcb8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (733 of 733 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Oğuz Ersen
77ed44bb08 Translated using Weblate (Turkish)
Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (733 of 733 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
gallegonovato
1b0b495029 Translated using Weblate (Spanish)
Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (733 of 733 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Anon
b6296fd586 Translated using Weblate (Serbian)
Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (732 of 732 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-10-21 10:17:44 +03:00
Koitharu
985b062218 Fix webtoon page detection #1140 2024-10-21 10:16:54 +03:00
Marius Albrecht
b6f57e5656 Give "Complete" status only to fully completed Manga
Up until now a progress of >= 99.5% would count a Manga as completed (and show the checkmark icon). This causes manga with 200 chapters or more to be marked as completed even if they have at least one unread chapter.

https://github.com/KotatsuApp/Kotatsu/issues/1105
2024-10-21 10:13:14 +03:00
Koitharu
3d285104a4 Search through settings 2024-10-20 17:01:17 +03:00
Koitharu
100073f45e Reader screen orientation settings 2024-10-16 18:16:14 +03:00
Koitharu
c1d577bdf3 Update link resolver 2024-10-16 13:19:34 +03:00
Koitharu
2214c20742 Fix external plugin communication 2024-10-13 18:24:48 +03:00
Koitharu
688a9fe4d5 Option to open manga source in browser 2024-10-13 18:00:05 +03:00
Koitharu
af5df32fbe Merge branch 'master' into devel 2024-10-13 17:22:25 +03:00
Koitharu
d739e30c84 Improve filter 2024-10-13 16:05:52 +03:00
Koitharu
32eb273fa9 Update parsers 2024-10-13 15:47:43 +03:00
Koitharu
8c5231bb3d Fix read chapters deletion 2024-10-13 14:09:03 +03:00
Koitharu
be4fb3e873 Fix saving cover 2024-10-13 14:09:03 +03:00
Koitharu
d28eff7a75 Fix zip closing 2024-10-13 14:09:03 +03:00
Koitharu
b81063910b Fix read chapters deletion 2024-10-13 14:08:25 +03:00
Koitharu
702ee70f70 Fix saving cover 2024-10-13 09:43:01 +03:00
Koitharu
c5bd979645 Fix zip closing 2024-10-13 09:39:28 +03:00
Koitharu
e515069b53 Fix zip closing
(cherry picked from commit 144e66bedb)
2024-10-11 17:16:57 +03:00
Koitharu
3255fba3c4 Ask for download via metered network 2024-10-11 17:16:31 +03:00
Koitharu
144e66bedb Fix zip closing 2024-10-11 10:55:47 +03:00
Koitharu
05d22167c4 Fix skipping download errors 2024-10-11 10:15:29 +03:00
Koitharu
e5c765dd2f Update parsers 2024-10-11 09:57:58 +03:00
Koitharu
557b69d73f New download dialog 2024-10-10 16:30:01 +03:00
Koitharu
1e22e8de45 Improve filter 2024-10-07 20:02:34 +03:00
Koitharu
9ea1122ca0 Fix CloudFlare protection detection (close #1129) 2024-10-07 15:24:02 +03:00
Koitharu
4faef85086 Fix sources suggestion 2024-10-07 14:40:29 +03:00
Koitharu
b46c00f2d0 Fix parsers version 2024-10-05 16:28:29 +03:00
Anon
9358617a3a Translated using Weblate (Serbian)
Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Serbian)

Currently translated at 96.4% (703 of 729 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
Draken
ba9f31835f Translated using Weblate (Vietnamese)
Currently translated at 100.0% (732 of 732 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (729 of 729 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
abc0922001
357308bfbb Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 90.5% (660 of 729 strings)

Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
gekka
cab56209c1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (732 of 732 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (729 of 729 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
Oğuz Ersen
e9cd32c870 Translated using Weblate (Turkish)
Currently translated at 100.0% (732 of 732 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
gallegonovato
357517ceac Translated using Weblate (Spanish)
Currently translated at 100.0% (732 of 732 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (729 of 729 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
Infy's Tagalog Translations
a57fcce72b Translated using Weblate (Filipino)
Currently translated at 98.3% (717 of 729 strings)

Translated using Weblate (Filipino)

Currently translated at 98.3% (716 of 728 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
Priit Jõerüüt
2e2a818c05 Translated using Weblate (Estonian)
Currently translated at 71.0% (520 of 732 strings)

Translated using Weblate (Estonian)

Currently translated at 67.9% (495 of 728 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/et/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/et/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-10-05 16:03:55 +03:00
Matt
b6f618101f Translated using Weblate (Portuguese)
Currently translated at 98.7% (719 of 728 strings)

Co-authored-by: Matt <contact.mattdev@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-10-05 16:03:55 +03:00
Koitharu
0ce368751a Merge branch 'devel' of github.com:KotatsuApp/Kotatsu into devel 2024-10-05 16:03:07 +03:00
Koitharu
1d28538893 Fixes batch 2024-10-05 16:02:49 +03:00
Koitharu
4ad2f3f608 Fixes batch 2024-10-04 14:27:01 +03:00
Koitharu
5301cc7f97 Ability to start download paused 2024-10-04 11:20:49 +03:00
Koitharu
1290db4a7c Fixes batch 2024-10-04 10:23:49 +03:00
Koitharu
1f1309d934 Increase source add button size 2024-10-03 14:32:34 +03:00
Koitharu
350f1521a6 Fix warnings and cleanup 2024-10-03 13:08:09 +03:00
mnv
cebce20bed Fix MangaSource import and improve user agent handling
MangaSource class was imported twice from different packages.
2024-10-03 09:16:30 +03:00
Koitharu
e5b6947586 Fix StrictMode errors 2024-10-02 16:39:21 +03:00
Koitharu
ac96c49b60 Strict mode notificaiton for debug build 2024-10-02 15:13:54 +03:00
Koitharu
a4345a40bf Update dependencies 2024-10-02 11:34:51 +03:00
Mac135135
0162eaed97 Merge remote-tracking branch 'origin/devel' into devel
# Conflicts:
#	app/src/main/res/values-es/strings.xml
#	app/src/main/res/values-zh-rCN/strings.xml
2024-09-30 01:10:25 +03:00
Koitharu
15ca4111c0 Reapply "Update sources catalog ui"
This reverts commit 8d5bde6e60.
2024-09-30 01:09:00 +03:00
Koitharu
dc45e0f5df Revert "Update sources catalog ui"
This reverts commit 597ad01e8f.
2024-09-30 01:09:00 +03:00
Koitharu
09b6a967a1 Refactor descrambling bitmap 2024-09-30 01:09:00 +03:00
AwkwardPeak7
1cff0eeac4 implement basic methods for descrambling images 2024-09-30 01:09:00 +03:00
Kristian de Frutos
44349c4ede Translated using Weblate (Czech)
Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Czech)

Currently translated at 83.0% (528 of 636 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Kristian de Frutos <kristiandef@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/cs/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-09-30 01:09:00 +03:00
Koitharu
8e8953b07f Skip error for local manga list (close #1113, close #1115) 2024-09-30 01:09:00 +03:00
Felipe Nascimento
150e3d554f Translated using Weblate (Portuguese)
Currently translated at 98.6% (718 of 728 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-30 01:08:58 +03:00
Draken
be3b5a1897 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-30 01:08:58 +03:00
Matt
9be0e8595f Translated using Weblate (Japanese)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Matt <contact.mattdev@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ja/
Translation: Kotatsu/plurals
2024-09-30 01:08:56 +03:00
Oğuz Ersen
f38370592e Translated using Weblate (Turkish)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-30 01:08:56 +03:00
Koitharu
6a54d42867 Update SSIV 2024-09-30 01:08:53 +03:00
Mac135135
49d29ae675 Added an periodical backup to the telegram bot 2024-09-30 01:08:53 +03:00
Mac135135
27d7a6a8cb Added an periodical backup to the telegram bot 2024-09-30 01:08:53 +03:00
Koitharu
e8d04644f8 Remove loggers and reorganize settings 2024-09-30 01:08:52 +03:00
Mac135135
26b512d42e Added an periodical backup to the telegram bot 2024-09-30 01:08:48 +03:00
Koitharu
4fb3173185 Update readme 2024-09-30 01:06:32 +03:00
Koitharu
826587b2c9 Translated using Weblate (Russian)
Currently translated at 99.7% (722 of 724 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-30 01:06:32 +03:00
Hosted Weblate
4efdb1d8d1 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/
Translation: Kotatsu/Strings
2024-09-30 01:06:30 +03:00
Infy's Tagalog Translations
1b9f886d1b Translated using Weblate (Filipino)
Currently translated at 98.4% (713 of 724 strings)

Translated using Weblate (Filipino)

Currently translated at 98.8% (712 of 720 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-30 01:06:30 +03:00
Felipe Nascimento
3241ae5db5 Translated using Weblate (Portuguese)
Currently translated at 98.7% (711 of 720 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-30 01:06:30 +03:00
Макар Разин
30f1b2c73a Translated using Weblate (Russian)
Currently translated at 100.0% (720 of 720 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (720 of 720 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-30 01:06:30 +03:00
Koitharu
8d35101e98 Update parsers 2024-09-30 01:06:30 +03:00
Koitharu
41cfd99d32 Fix applying filter 2024-09-30 01:06:30 +03:00
Koitharu
c8d04e4eb7 Migrate external sources to new filter 2024-09-30 01:06:29 +03:00
Koitharu
956831f9d7 Fix sync auth activity ui 2024-09-30 01:06:29 +03:00
Draken
d65874080b Translated using Weblate (Vietnamese)
Currently translated at 100.0% (720 of 720 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-30 01:06:29 +03:00
Infy's Tagalog Translations
bf35a8ffd7 Translated using Weblate (Filipino)
Currently translated at 98.8% (712 of 720 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-30 01:06:29 +03:00
Oğuz Ersen
eeb8dd8c5b Translated using Weblate (Turkish)
Currently translated at 100.0% (720 of 720 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-30 01:06:25 +03:00
Faiz Faadhillah
299093f863 Improve Spen integration support 2024-09-30 01:06:25 +03:00
Koitharu
86dea2953a Update parsers 2024-09-30 01:06:25 +03:00
gallegonovato
81794e6eb2 Translated using Weblate (Spanish)
Currently translated at 100.0% (720 of 720 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
2024-09-30 01:06:25 +03:00
Draken
d43887e288 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-30 01:05:59 +03:00
Oğuz Ersen
e2cf22e054 Translated using Weblate (Turkish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-30 01:05:59 +03:00
Koitharu
5a75fe77fd Various fixes 2024-09-30 01:05:41 +03:00
Koitharu
8c0617c525 Context menus 2024-09-30 01:05:41 +03:00
Anonymous
38b8966c16 Translated using Weblate (Hungarian)
Currently translated at 86.6% (622 of 718 strings)

Translated using Weblate (Nepali)

Currently translated at 32.3% (232 of 718 strings)

Translated using Weblate (Hindi)

Currently translated at 93.0% (668 of 718 strings)

Translated using Weblate (Portuguese)

Currently translated at 92.7% (666 of 718 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-30 01:05:41 +03:00
desu sude
59f4ff8a3e Translated using Weblate (Latvian)
Currently translated at 24.4% (175 of 717 strings)

Co-authored-by: desu sude <cobsonslittlecocksleeve@proton.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lv/
Translation: Kotatsu/Strings
2024-09-30 01:05:41 +03:00
Anon
357263b496 Translated using Weblate (Serbian)
Currently translated at 100.0% (698 of 698 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-09-30 01:05:41 +03:00
Milo Ivir
4af6fc165b Translated using Weblate (Croatian)
Currently translated at 98.7% (689 of 698 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Infy's Tagalog Translations
a4de58b9b3 Translated using Weblate (Filipino)
Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (698 of 698 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
abc0922001
5696ad7fa2 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 92.1% (643 of 698 strings)

Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Amirreza Safavi
63bfca6d3e Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
gekka
0fecf996e1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.7% (716 of 718 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (696 of 698 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (696 of 698 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (691 of 692 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Shayan
3df2682332 Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Shayan <shayans31516@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Amirreza Safavi
dd9df6e9dc Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
gallegonovato
0889c2cc28 Translated using Weblate (Spanish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Draken
010b1264ae Translated using Weblate (Vietnamese)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Oğuz Ersen
66ff32e14d Translated using Weblate (Turkish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Fikri Akbar
addb642cc9 Translated using Weblate (Indonesian)
Currently translated at 99.8% (688 of 689 strings)

Co-authored-by: Fikri Akbar <akbarfikri1221@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-09-30 01:05:40 +03:00
Koitharu
720c389dbd Search in history, favorites and local 2024-09-30 01:05:40 +03:00
Koitharu
2191d9c83b Fix sources catalog content types 2024-09-30 01:05:40 +03:00
Koitharu
0ee1cda0e4 Local manga source filter 2024-09-30 01:05:40 +03:00
Koitharu
90226b7b78 Update supported domains 2024-09-30 01:05:39 +03:00
Koitharu
6d84294533 Improve quick filters 2024-09-30 01:05:39 +03:00
Koitharu
36bd3cc438 Local manga index in database 2024-09-30 01:05:39 +03:00
Koitharu
e0c983f4eb Search manga with filters 2024-09-30 01:05:39 +03:00
Koitharu
ea5ce23335 Improve filter 2024-09-30 01:05:39 +03:00
Koitharu
26a33e5d9d Add new filter fields 2024-09-30 01:05:39 +03:00
Koitharu
9ab7159cb9 Update parsers and filters 2024-09-30 01:05:39 +03:00
Koitharu
ad21321a1d Update parsers 2024-09-30 01:05:39 +03:00
Koitharu
fe2bb05895 Update dependencies 2024-09-30 01:05:39 +03:00
Koitharu
e48beae324 Batch manga fix functionality 2024-09-30 01:05:39 +03:00
Amirreza Safavi
10109ab2c0 Translated using Weblate (Persian)
Currently translated at 77.7% (7 of 9 strings)

Translated using Weblate (Persian)

Currently translated at 37.4% (258 of 689 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fa/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-09-30 01:05:38 +03:00
Anon
df17bb5af8 Translated using Weblate (Serbian)
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-09-30 01:05:38 +03:00
Henrique
b4592015fb Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.6% (666 of 689 strings)

Co-authored-by: Henrique <heluis110@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-09-30 01:05:38 +03:00
Макар Разин
3fe9ec6918 Translated using Weblate (Russian)
Currently translated at 100.0% (689 of 689 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-30 01:05:38 +03:00
Dilip Patel
23ac9df844 grammar fix 2024-09-30 01:05:38 +03:00
Koitharu
c480992f63 Option to automatically download new chapters (close #425, close #602, close #955) 2024-09-30 01:05:38 +03:00
Koitharu
85d397def0 Update dependencies 2024-09-30 01:05:38 +03:00
Koitharu
f518acb8ee Skip error for local manga list (close #1113, close #1115) 2024-09-29 19:46:48 +03:00
大王叫我来巡山
b39a51d497 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (728 of 728 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Felipe Nascimento
8819d8b1ee Translated using Weblate (Portuguese)
Currently translated at 98.6% (718 of 728 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Draken
05a502b89a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
gekka
c320e3c26a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (723 of 724 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Matt
938849c31e Translated using Weblate (Japanese)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Matt <contact.mattdev@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ja/
Translation: Kotatsu/plurals
2024-09-29 19:43:34 +03:00
Oğuz Ersen
95c243daa1 Translated using Weblate (Turkish)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
gallegonovato
6ce6a02b56 Translated using Weblate (Spanish)
Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-29 19:43:34 +03:00
Koitharu
e92e9fb393 Update SSIV 2024-09-29 19:43:09 +03:00
Koitharu
f4186a2787 Remove loggers and reorganize settings 2024-09-27 14:40:31 +03:00
Koitharu
8b93b699d3 Update readme 2024-09-26 16:02:52 +03:00
Koitharu
7e13482ba5 Translated using Weblate (Russian)
Currently translated at 99.7% (722 of 724 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-26 15:34:44 +03:00
gallegonovato
04700a22c8 Translated using Weblate (Spanish)
Currently translated at 100.0% (724 of 724 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-26 15:34:44 +03:00
Hosted Weblate
549d08cc06 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/
Translation: Kotatsu/Strings
2024-09-26 15:34:44 +03:00
Infy's Tagalog Translations
0fccaf3fbc Translated using Weblate (Filipino)
Currently translated at 98.4% (713 of 724 strings)

Translated using Weblate (Filipino)

Currently translated at 98.8% (712 of 720 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-26 15:34:44 +03:00
Felipe Nascimento
c7e0a47bee Translated using Weblate (Portuguese)
Currently translated at 98.7% (711 of 720 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-26 15:34:44 +03:00
Макар Разин
d527b6e390 Translated using Weblate (Russian)
Currently translated at 100.0% (720 of 720 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (720 of 720 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-26 15:34:44 +03:00
Koitharu
12b2af6b93 Update parsers 2024-09-26 14:46:36 +03:00
Koitharu
63f4fab40f Fix applying filter 2024-09-26 12:33:35 +03:00
Koitharu
9a444cf965 Migrate external sources to new filter 2024-09-25 12:28:10 +03:00
Koitharu
b8be2f7158 Fix sync auth activity ui 2024-09-25 09:48:17 +03:00
Draken
9e2074040f Translated using Weblate (Vietnamese)
Currently translated at 100.0% (720 of 720 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-25 09:47:57 +03:00
Infy's Tagalog Translations
020c151e31 Translated using Weblate (Filipino)
Currently translated at 98.8% (712 of 720 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-25 09:47:57 +03:00
gekka
52eb33a992 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (719 of 720 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-25 09:47:57 +03:00
大王叫我来巡山
907b8fd0ec Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (719 of 720 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-25 09:47:57 +03:00
Oğuz Ersen
e35b2088a1 Translated using Weblate (Turkish)
Currently translated at 100.0% (720 of 720 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-25 09:47:57 +03:00
Faiz Faadhillah
fbb4efb3df Improve Spen integration support 2024-09-25 09:47:03 +03:00
Koitharu
7ff47a322e Update parsers 2024-09-24 17:56:11 +03:00
Koitharu
fda1af5500 Merge branch 'devel' of https://hosted.weblate.org/git/kotatsu/strings into devel 2024-09-24 17:51:14 +03:00
Koitharu
d88847d137 Various fixes 2024-09-24 17:48:13 +03:00
Koitharu
063527b240 Context menus 2024-09-24 17:48:12 +03:00
gallegonovato
b0470110a8 Translated using Weblate (Spanish)
Currently translated at 100.0% (720 of 720 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
2024-09-24 16:46:39 +02:00
Anonymous
5a2a31d1c8 Translated using Weblate (Hungarian)
Currently translated at 86.6% (622 of 718 strings)

Translated using Weblate (Nepali)

Currently translated at 32.3% (232 of 718 strings)

Translated using Weblate (Hindi)

Currently translated at 93.0% (668 of 718 strings)

Translated using Weblate (Portuguese)

Currently translated at 92.7% (666 of 718 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
desu sude
3b009d7c55 Translated using Weblate (Latvian)
Currently translated at 24.4% (175 of 717 strings)

Co-authored-by: desu sude <cobsonslittlecocksleeve@proton.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lv/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Anon
f7e937f2b8 Translated using Weblate (Serbian)
Currently translated at 100.0% (698 of 698 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Milo Ivir
16e23cc1cf Translated using Weblate (Croatian)
Currently translated at 98.7% (689 of 698 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Infy's Tagalog Translations
d12528d80f Translated using Weblate (Filipino)
Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (698 of 698 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
abc0922001
9f04c7b148 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 92.1% (643 of 698 strings)

Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Amirreza Safavi
7a3942f100 Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
gekka
8e46f64f2a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.7% (716 of 718 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (696 of 698 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (696 of 698 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (691 of 692 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Shayan
44c50fca2d Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Shayan <shayans31516@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Amirreza Safavi
55b4d14a93 Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
gallegonovato
743693299f Translated using Weblate (Spanish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Draken
7950a685a6 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Oğuz Ersen
97cfcb5c01 Translated using Weblate (Turkish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Fikri Akbar
b2dfcefee8 Translated using Weblate (Indonesian)
Currently translated at 99.8% (688 of 689 strings)

Co-authored-by: Fikri Akbar <akbarfikri1221@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-09-24 17:46:34 +03:00
Anonymous
ee1ade40c3 Translated using Weblate (Hungarian)
Currently translated at 86.6% (622 of 718 strings)

Translated using Weblate (Nepali)

Currently translated at 32.3% (232 of 718 strings)

Translated using Weblate (Hindi)

Currently translated at 93.0% (668 of 718 strings)

Translated using Weblate (Portuguese)

Currently translated at 92.7% (666 of 718 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
desu sude
3690e15cff Translated using Weblate (Latvian)
Currently translated at 24.4% (175 of 717 strings)

Co-authored-by: desu sude <cobsonslittlecocksleeve@proton.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lv/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
Anon
a955dfbe50 Translated using Weblate (Serbian)
Currently translated at 100.0% (698 of 698 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
Milo Ivir
5e9daa1206 Translated using Weblate (Croatian)
Currently translated at 98.7% (689 of 698 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
Infy's Tagalog Translations
a3c2956a4d Translated using Weblate (Filipino)
Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (698 of 698 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
abc0922001
10ecd92715 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 92.1% (643 of 698 strings)

Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
Amirreza Safavi
37d2d986ef Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
gekka
0aadd6ebe2 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (717 of 718 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (716 of 718 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (696 of 698 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (696 of 698 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (691 of 692 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
Shayan
c23ec9a4b8 Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Shayan <shayans31516@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-24 11:03:58 +02:00
Amirreza Safavi
22a37923f9 Translated using Weblate (Persian)
Currently translated at 41.6% (288 of 692 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-09-24 11:03:57 +02:00
gallegonovato
3fc506b438 Translated using Weblate (Spanish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-24 11:03:57 +02:00
Draken
e98dbd5069 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-24 11:03:57 +02:00
Oğuz Ersen
2a469b27c5 Translated using Weblate (Turkish)
Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (698 of 698 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (692 of 692 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-24 11:03:57 +02:00
Fikri Akbar
0f3ef4559f Translated using Weblate (Indonesian)
Currently translated at 99.8% (688 of 689 strings)

Co-authored-by: Fikri Akbar <akbarfikri1221@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-09-24 11:03:57 +02:00
Koitharu
a87ef0a0a6 Search in history, favorites and local 2024-09-24 11:44:02 +03:00
Koitharu
a7a0a7f0db Fix sources catalog content types 2024-09-24 10:18:10 +03:00
Koitharu
bc4622d610 Local manga source filter 2024-09-23 19:22:48 +03:00
Koitharu
8365603bf1 Update supported domains 2024-09-23 18:28:55 +03:00
Koitharu
b1eabdba79 Improve quick filters 2024-09-23 14:46:29 +03:00
Koitharu
169e31e9ba Local manga index in database 2024-09-23 08:55:17 +03:00
Koitharu
66644d55a4 Search manga with filters 2024-09-22 17:44:36 +03:00
Koitharu
98314960cf Improve filter 2024-09-21 11:58:45 +03:00
Koitharu
b73e44874d Add new filter fields 2024-09-21 09:11:58 +03:00
Koitharu
6f45a44070 Update parsers and filters 2024-09-21 08:22:32 +03:00
Koitharu
d9d11d685e Update parsers 2024-09-15 16:05:52 +03:00
Koitharu
5359267b5a Update dependencies 2024-09-15 15:16:07 +03:00
Koitharu
a6662ab501 Batch manga fix functionality 2024-09-15 15:02:34 +03:00
Amirreza Safavi
699a619c27 Translated using Weblate (Persian)
Currently translated at 77.7% (7 of 9 strings)

Translated using Weblate (Persian)

Currently translated at 37.4% (258 of 689 strings)

Co-authored-by: Amirreza Safavi <amirxcatsanddragons@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fa/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-09-14 14:42:29 +03:00
Anon
85ccbbf719 Translated using Weblate (Serbian)
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-09-14 14:42:29 +03:00
Henrique
a396b33f3d Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.6% (666 of 689 strings)

Co-authored-by: Henrique <heluis110@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-09-14 14:42:29 +03:00
Макар Разин
6076f775c3 Translated using Weblate (Russian)
Currently translated at 100.0% (689 of 689 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-14 14:42:29 +03:00
Dilip Patel
379fa88b4e grammar fix 2024-09-14 14:39:36 +03:00
Koitharu
9b24c507c5 Option to automatically download new chapters (close #425, close #602, close #955) 2024-09-14 12:12:55 +03:00
Koitharu
98bd79f0be Update dependencies 2024-09-11 08:50:51 +03:00
Mac135135
7c74c87524 Merge remote-tracking branch 'origin/devel' into devel 2024-09-07 15:26:13 +03:00
Koitharu
f09e28e782 Fix applying global color filter (close #1088) 2024-09-06 12:19:59 +03:00
Koitharu
b601b07586 Optimize the Downloaded quick filter 2024-09-06 11:35:23 +03:00
Koitharu
73cea59691 Change pref key for ProxyType 2024-09-06 07:45:48 +03:00
Koitharu
e2993d47b6 Fix closing ZipFile 2024-09-06 07:44:29 +03:00
Koitharu
2cd67e7cf8 Fix crashes 2024-09-06 07:28:17 +03:00
Koitharu
c51da5a9d5 Update parsers 2024-09-05 12:52:03 +03:00
Justine Kyle Cobar
bcfce29610 Translated using Weblate (Filipino)
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Justine Kyle Cobar <cobarjustinekyle583@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
maryush
a87d18fae3 Translated using Weblate (Polish)
Currently translated at 99.8% (688 of 689 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
gekka
bbd421445c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
Oğuz Ersen
f4e3d797dc Translated using Weblate (Turkish)
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
Denis Bolba
bd65cbb8b8 Translated using Weblate (Romanian)
Currently translated at 12.9% (89 of 689 strings)

Added translation using Weblate (Romanian)

Added translation using Weblate (Romanian)

Co-authored-by: Denis Bolba <bolbadenis4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ro/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
Draken
7d1f81607a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
gallegonovato
3b6cd0ea7f Translated using Weblate (Spanish)
Currently translated at 100.0% (689 of 689 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-05 11:03:14 +03:00
Koitharu
aff70d8519 Update dependencies 2024-09-05 10:30:52 +03:00
Koitharu
8a74faa4f0 Fix Downloaded quick filter (close #1076, close #1079) 2024-09-05 10:16:09 +03:00
Koitharu
c1ac207809 Fix downloading (close #1072) 2024-09-04 14:33:08 +03:00
Koitharu
e34e745c84 Fix reading saved manga offline (close #1081, close #1071) 2024-09-04 13:41:57 +03:00
Koitharu
50dd119ab5 Fixes 2024-09-03 17:47:36 +03:00
Koitharu
d0ef177d56 Fix sort order direction in filter 2024-09-03 14:52:04 +03:00
Infy's Tagalog Translations
9b9c2e49b9 Translated using Weblate (Filipino)
Currently translated at 100.0% (687 of 687 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-09-03 13:44:57 +03:00
Igor
afeb307453 Translated using Weblate (Ukrainian)
Currently translated at 97.3% (667 of 685 strings)

Co-authored-by: Igor <zerrxs@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-09-03 13:44:57 +03:00
Koitharu
7568b1aedc Translated using Weblate (Russian)
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-09-03 13:44:57 +03:00
yoval keshet
742d8cee00 Translated using Weblate (Hebrew)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: yoval keshet <keshetyoval@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/he/
Translation: Kotatsu/plurals
2024-09-03 13:44:57 +03:00
gallegonovato
d52bef28ff Translated using Weblate (Spanish)
Currently translated at 100.0% (687 of 687 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-09-03 13:44:57 +03:00
Koitharu
b2f48421c7 Sync unstable warning 2024-09-03 13:37:56 +03:00
Koitharu
22643bf9cc Handle scrobbler authorization errors 2024-09-03 11:09:58 +03:00
Koitharu
861ca63ea9 Manual-only new chapters check option 2024-09-02 13:02:25 +03:00
Koitharu
6e34356b6f Fix grayscale color filter (closes ##1065) 2024-09-02 12:43:26 +03:00
Koitharu
a9b3025724 Fix external sources tags filtering 2024-08-31 16:44:28 +03:00
Koitharu
0cc019ef19 Refactor details and reader ViewModels 2024-08-31 14:13:11 +03:00
Koitharu
eb49b31aeb Inverted filter presets 2024-08-31 09:53:32 +03:00
Koitharu
5ebbfd1c00 Refactor alert dialogs 2024-08-31 09:44:42 +03:00
Koitharu
0df67b86f8 Increase suggestions limit 2024-08-31 08:36:17 +03:00
Koitharu
2df567372e Fix empty lists state with Downloaded filter 2024-08-31 08:35:38 +03:00
Koitharu
0fb3c69e10 Fix release build 2024-08-30 11:17:09 +03:00
Koitharu
44900dbcbe Update AGP 2024-08-30 09:19:54 +03:00
Milo Ivir
89cd295f28 Translated using Weblate (Croatian)
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
Draken
d06811d94d Translated using Weblate (Vietnamese)
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
gekka
ced22ebb0a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
Oğuz Ersen
3c2ad26f1d Translated using Weblate (Turkish)
Currently translated at 100.0% (685 of 685 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
Макар Разин
9535e35ba7 Translated using Weblate (Polish)
Currently translated at 99.5% (682 of 685 strings)

Translated using Weblate (Serbian)

Currently translated at 99.5% (682 of 685 strings)

Translated using Weblate (Russian)

Currently translated at 99.8% (684 of 685 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.8% (684 of 685 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
Infy's Tagalog Translations
298e87dce2 Translated using Weblate (Filipino)
Currently translated at 100.0% (685 of 685 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (678 of 678 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
Kacper Małecki
165ce61ded Translated using Weblate (Polish)
Currently translated at 99.7% (676 of 678 strings)

Co-authored-by: Kacper Małecki <kacperito887@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2024-08-30 08:05:06 +03:00
Koitharu
c70e3547d1 Support ascending/descending sort orders 2024-08-29 08:10:16 +03:00
Koitharu
05b5953f35 Fix excluding NSFW 2024-08-28 09:24:49 +03:00
Justine Kyle Cobar
9cba6e694a Translated using Weblate (Filipino)
Currently translated at 100.0% (678 of 678 strings)

Co-authored-by: Justine Kyle Cobar <cobarjustinekyle583@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Lee Khemrinn Phang
b2c73ec9d8 Translated using Weblate (Khmer (Central))
Currently translated at 21.1% (143 of 677 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 13.7% (92 of 671 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 13.5% (91 of 671 strings)

Added translation using Weblate (Khmer (Central))

Added translation using Weblate (Khmer (Central))

Co-authored-by: Lee Khemrinn Phang <Nihil@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/km/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
ALN
cd7620673b Translated using Weblate (Kazakh)
Currently translated at 81.7% (547 of 669 strings)

Translated using Weblate (Kazakh)

Currently translated at 77.7% (7 of 9 strings)

Co-authored-by: ALN <alzhanalan1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/kk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-08-28 09:21:36 +03:00
maryush
09bfb2b0f4 Translated using Weblate (Polish)
Currently translated at 100.0% (669 of 669 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Anon
6dde7e9535 Translated using Weblate (Serbian)
Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (667 of 667 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Laura
98e24072ce Translated using Weblate (Arabic)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Laura <hankmaroua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translation: Kotatsu/plurals
2024-08-28 09:21:36 +03:00
Felipe Nascimento
fdc67f8f0e Translated using Weblate (Portuguese)
Currently translated at 100.0% (667 of 667 strings)

Co-authored-by: Felipe Nascimento <f.kgb@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
gallegonovato
f8acabcc86 Translated using Weblate (Spanish)
Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (671 of 671 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (667 of 667 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
desu sude
5a0771b751 Translated using Weblate (Latvian)
Currently translated at 6.3% (42 of 666 strings)

Added translation using Weblate (Latvian)

Added translation using Weblate (Latvian)

Co-authored-by: desu sude <cobsonslittlecocksleeve@proton.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lv/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
user _425
6a231f76e1 Translated using Weblate (Indonesian)
Currently translated at 98.4% (656 of 666 strings)

Co-authored-by: user _425 <lelcraft96@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
LinCie
d203edbdae Translated using Weblate (Indonesian)
Currently translated at 98.4% (656 of 666 strings)

Translated using Weblate (Indonesian)

Currently translated at 97.2% (648 of 666 strings)

Co-authored-by: LinCie <aldiofernanda@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
user _425
91a5aa8d4c Translated using Weblate (Indonesian)
Currently translated at 97.2% (648 of 666 strings)

Co-authored-by: user _425 <lelcraft96@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Ahmed seif al-nasr
40076dea36 Translated using Weblate (Arabic)
Currently translated at 100.0% (666 of 666 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Ahmed seif al-nasr <ahmdsyfalnsr2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-08-28 09:21:36 +03:00
Eduardo Malaspina
2ab7228727 Translated using Weblate (Spanish)
Currently translated at 100.0% (666 of 666 strings)

Co-authored-by: Eduardo Malaspina <vaio0@swismail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Eduardo
52e500a5fb Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Eduardo <edu200399lim@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Kis Miklós
44734867cc Translated using Weblate (Hungarian)
Currently translated at 93.8% (624 of 665 strings)

Co-authored-by: Kis Miklós <kisbivaly@duck.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Макар Разин
6565d05274 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (667 of 667 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (667 of 667 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (667 of 667 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (665 of 665 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (665 of 665 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Henrique
204758cbbb Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.8% (664 of 665 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Henrique <heluis110@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Milo Ivir
a60e7e13ca Translated using Weblate (Croatian)
Currently translated at 100.0% (671 of 671 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Infy's Tagalog Translations
3596109249 Translated using Weblate (Filipino)
Currently translated at 99.4% (673 of 677 strings)

Translated using Weblate (Filipino)

Currently translated at 99.7% (669 of 671 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (668 of 669 strings)

Translated using Weblate (Filipino)

Currently translated at 99.6% (664 of 666 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (664 of 665 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Draken
3c703d9771 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (671 of 671 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (667 of 667 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (666 of 666 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (666 of 666 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (665 of 665 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Scrambled777
1e87dc4c52 Translated using Weblate (Hindi)
Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (666 of 666 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
gekka
ae61d50a6c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (671 of 671 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (667 of 667 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (666 of 666 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Oğuz Ersen
63fb40dd65 Translated using Weblate (Turkish)
Currently translated at 100.0% (678 of 678 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (677 of 677 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (671 of 671 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (669 of 669 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (667 of 667 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (666 of 666 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
gallegonovato
afe2248bb8 Translated using Weblate (Spanish)
Currently translated at 100.0% (665 of 665 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (664 of 664 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Anonymous
b3b82ace3f Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (663 of 664 strings)

Translated using Weblate (Swedish)

Currently translated at 40.5% (269 of 664 strings)

Translated using Weblate (Finnish)

Currently translated at 38.8% (258 of 664 strings)

Translated using Weblate (Portuguese)

Currently translated at 95.6% (635 of 664 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 43.0% (286 of 664 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-08-28 09:21:36 +03:00
Koitharu
903fef6791 Apply the Disable NSFW option to all lists (closes #1057) 2024-08-26 17:34:28 +03:00
Koitharu
542ad29cd9 Webtoon reader fixes 2024-08-26 17:00:27 +03:00
Koitharu
d588e8d941 Fix chapters selections 2024-08-26 14:31:39 +03:00
Koitharu
6b786084cf Update parsers 2024-08-24 20:04:05 +03:00
Koitharu
85da41be9a Downloading improvements 2024-08-24 10:10:43 +03:00
Koitharu
6e8a1cd6af Skip nsfw sources for recommendations if disabled 2024-08-20 16:28:39 +03:00
Koitharu
0f28d5de11 Fix switching double-page mode 2024-08-20 15:41:55 +03:00
Koitharu
0d39909d89 Update parsers 2024-08-20 15:32:49 +03:00
Koitharu
e4282a8e9d Improve favorite categories screen (fix #1047) 2024-08-20 15:23:33 +03:00
Koitharu
05a64308ac Dont show favorites from hidden categories in the "All favorites" tab 2024-08-20 13:43:32 +03:00
Koitharu
7b01bafd53 Fix pages downsampling 2024-08-20 13:07:59 +03:00
Koitharu
b521460335 Split url and domain validations #1043 2024-08-20 12:08:51 +03:00
Nicolai Dagestad
249c8377bd Default to http for pre-configured syncs
When a user had configured a sync server before, it had no protocol specified.
This commit "restores" the previous behaviour of using http by default.
2024-08-20 12:03:44 +03:00
Nicolai Dagestad
58cdc9f29a Do not rename the string ressource (+ syntax fix) 2024-08-20 12:03:44 +03:00
Nicolai Dagestad
065beb72e1 First attempt to add https syncing 2024-08-20 12:03:44 +03:00
Koitharu
c00614f17d Improve quick filter behavior 2024-08-20 12:03:20 +03:00
Koitharu
d99bc08e49 Fix release build 2024-08-18 18:13:46 +03:00
Koitharu
9e49b28ac3 Quick filter request refactor 2024-08-18 17:02:15 +03:00
Koitharu
d06b396aec Quick filter for suggestions 2024-08-18 16:03:00 +03:00
Koitharu
65abef1282 Quick filter in feed and updates lists 2024-08-18 14:01:29 +03:00
Koitharu
b66d3ee8d4 Merge branch 'master' into devel 2024-08-17 12:19:55 +03:00
Koitharu
174fa800be Refactor and cleanup 2024-08-17 09:38:57 +03:00
Koitharu
28670bc7fb Improve favorites dialog 2024-08-15 16:14:24 +03:00
Koitharu
a61e406c91 Fix reading progress indication 2024-08-15 14:46:06 +03:00
Koitharu
20f357cb12 Handle invalid proxy settings 2024-08-15 12:18:41 +03:00
Koitharu
5ba6b81fac Update dependencies 2024-08-15 09:56:18 +03:00
Koitharu
e34bcd47d5 Refactor tracker 2024-08-15 09:34:59 +03:00
Koitharu
62ed8705e8 UI fixes 2024-08-13 08:20:06 +03:00
vianh
de18324798 Hide toolbar for favorites screen only 2024-08-12 19:36:40 +03:00
vianh
a7a943c8dc Hide toolbar when scrolling up 2024-08-12 19:36:40 +03:00
Coding Otaku
6e975b9d66 ix: migrate from kitsu.io to kitsu.app
The [kitsu.io](https://kitsu.io) domain has expired, and the main developers
do not have access to that domain. So they
[migrated](hummingbird-me/kitsu-server#1556)
to the new [kitsu.app](https://kitsu.app) domain.

This commit updates all references of `kitsu.io` to `kitsu.app`.
2024-08-12 18:39:01 +03:00
Koitharu
9e53dc3d5f Merge branch 'master' into devel 2024-08-12 18:35:22 +03:00
Koitharu
f3c19f9c02 Add more quick filters 2024-08-12 16:46:07 +03:00
Koitharu
33b4b9fbcb Update incognito mode hint view 2024-08-12 16:07:23 +03:00
Koitharu
00396f2e1b Quick filter for favorites 2024-08-12 15:28:14 +03:00
Koitharu
8b71f99666 Refactor quick filter implementation 2024-08-08 12:10:26 +03:00
Mac135135
f86ee7d5c2 Merge remote-tracking branch 'origin/devel' into devel 2024-08-04 19:46:52 +03:00
Koitharu
d00822a6c3 Quick filter in history draft implementation 2024-08-03 16:22:46 +03:00
Mac135135
6e5519419d Merge remote-tracking branch 'origin/devel' into devel 2024-07-31 19:16:41 +03:00
Mac135135
2c53b63847 Merge remote-tracking branch 'origin/devel' into devel
# Conflicts:
#	app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
2024-07-11 01:35:18 +03:00
Mac135135
45b5e48676 Add functionality to expand manga title on click 2024-07-05 21:23:15 +03:00
986 changed files with 30089 additions and 15743 deletions

BIN
.github/assets/vtuber.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@
.cxx
/.idea/deviceManager.xml
/.kotlin/
/.idea/AndroidProjectSystem.xml

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

4
.idea/gradle.xml generated
View File

@@ -4,15 +4,15 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="gradleJvm" value="jbr-21" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

110
README.md
View File

@@ -1,57 +1,107 @@
# Kotatsu
<div align="center">
Kotatsu is a free and open source manga reader for Android.
<a href="https://kotatsu.app">
<img src="./.github/assets/vtuber.png" alt="Kotatsu Logo" title="Kotatsu" width="600"/>
</a>
![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/) [![Telegram](https://img.shields.io/badge/chat-telegram-60ACFF)](https://t.me/kotatsuapp) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5)
# [Kotatsu](https://kotatsu.app)
**[Kotatsu](https://github.com/KotatsuApp/Kotatsu) is a free and open-source manga reader for Android with built-in online content sources.**
![Downloads count](https://img.shields.io/github/downloads/KotatsuApp/Kotatsu/total?color=1976d2) ![Latest Stable version](https://img.shields.io/github/v/release/KotatsuApp/Kotatsu?color=2596be&label=latest) ![Android 5.0](https://img.shields.io/badge/android-5.0+-brightgreen) [![Sources count](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2FKotatsuApp%2Fkotatsu-parsers%2Frefs%2Fheads%2Fmaster%2F.github%2Fsummary.yaml&query=total&label=manga%20sources&color=%23E9321C)](https://github.com/KotatsuApp/kotatsu-parsers) [![weblate](https://hosted.weblate.org/widgets/kotatsu/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/kotatsu/) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5) [![Telegram](https://img.shields.io/badge/chat-telegram-60ACFF?)](https://t.me/kotatsuapp) [![License](https://img.shields.io/github/license/KotatsuApp/Kotatsu)](https://github.com/KotatsuApp/Kotatsu/blob/devel/LICENSE)
### Download
- **Recommended:** Download and install APK from **[GitHub Releases](https://github.com/KotatsuApp/Kotatsu/releases/latest)**. Application has a built-in self-updating feature.
- Get it on **[F-Droid](https://f-droid.org/packages/org.koitharu.kotatsu)**. The F-Droid build may be a bit outdated and some fixes might be missing.
<div align="left">
* **Recommended:** Download and install APK from [GitHub Releases](https://github.com/KotatsuApp/Kotatsu/releases/latest). Application has a built-in self-updating feature.
* Get it on [F-Droid](https://f-droid.org/packages/org.koitharu.kotatsu). The F-Droid build may be a bit outdated and some fixes might be missing.
* Also [nightly builds](https://github.com/KotatsuApp/Kotatsu-nightly/releases) are available (very unstable, use at your own risk).
</div>
### Main Features
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers)
* 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 You UI
* Standard and Webtoon-optimized reader
* Notifications about new chapters with updates feed
<div align="left">
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers) (with 1100+ manga sources)
* Search manga by name, genres, and more filters
* Favorites organized by user-defined categories
* Reading history, bookmarks, and incognito mode support
* Download manga and read it offline. Third-party CBZ archives are also supported
* Clean and convenient Material You UI, optimized for phones, tablets, and desktop
* Standard and Webtoon-optimized customizable reader, gesture support on reading interface
* Notifications about new chapters with updates feed, manga recommendations (with filters)
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
* Password/fingerprint protect access to the app
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices
* Password / fingerprint-protected access to the app
* Automatically sync app data with other devices on the same account
* Support for older devices running Android 5+
### Screenshots
</div>
| ![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) |
### In-App Screenshots
| ![](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) |
|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
<div align="center">
<img src="./metadata/en-US/images/phoneScreenshots/1.png" alt="Mobile view" width="250"/>
<img src="./metadata/en-US/images/phoneScreenshots/2.png" alt="Mobile view" width="250"/>
<img src="./metadata/en-US/images/phoneScreenshots/3.png" alt="Mobile view" width="250"/>
<img src="./metadata/en-US/images/phoneScreenshots/4.png" alt="Mobile view" width="250"/>
<img src="./metadata/en-US/images/phoneScreenshots/5.png" alt="Mobile view" width="250"/>
<img src="./metadata/en-US/images/phoneScreenshots/6.png" alt="Mobile view" width="250"/>
</div>
<br>
<div align="center">
<img src="./metadata/en-US/images/tenInchScreenshots/1.png" alt="Tablet view" width="400"/>
<img src="./metadata/en-US/images/tenInchScreenshots/2.png" alt="Tablet view" width="400"/>
</div>
### Localization
[<img src="https://hosted.weblate.org/widgets/kotatsu/-/287x66-white.png" alt="Translation status">](https://hosted.weblate.org/engage/kotatsu/)
<a href="https://hosted.weblate.org/engage/kotatsu/">
<img src="https://hosted.weblate.org/widget/kotatsu/horizontal-auto.png" alt="Translation status" />
</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/)
**[Kotatsu](https://github.com/KotatsuApp/Kotatsu) is localized in a number of different languages.**<br>
**📌 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/)**
### Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the guidelines.
<br>
<a href="https://github.com/KotatsuApp/Kotatsu">
<picture>
<source srcset="https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu&bg_color=0d1117&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true" media="(prefers-color-scheme: dark)">
<img src="https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true" alt="Kotatsu GitHub Repository">
</picture>
</a>
<a href="https://github.com/KotatsuApp/Kotatsu-parsers">
<picture>
<source srcset="https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu-parsers&bg_color=0d1117&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true" media="(prefers-color-scheme: dark)">
<img src="https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu-parsers&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true" alt="Kotatsu-parsers GitHub Repository">
</picture>
</a><br></br>
</br>
**📌 Pull requests are welcome, if you want: See [CONTRIBUTING.md](https://github.com/KotatsuApp/Kotatsu/blob/devel/CONTRIBUTING.md) for the guidelines**
### License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
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.
<div align="left">
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.
</div>
### 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.
<div align="left">
The developers of this application do not have any affiliation with the content available in the app. It collects content from sources that are freely available through any web browser.
</div>

View File

@@ -1,3 +1,5 @@
import java.time.LocalDateTime
plugins {
id 'com.android.application'
id 'kotlin-android'
@@ -5,24 +7,24 @@ plugins {
id 'com.google.devtools.ksp'
id 'kotlin-parcelize'
id 'dagger.hilt.android.plugin'
id 'androidx.room'
}
android {
compileSdk = 34
buildToolsVersion = '34.0.0'
compileSdk = 35
buildToolsVersion = '35.0.0'
namespace = 'org.koitharu.kotatsu'
defaultConfig {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 35
versionCode = 660
versionName = '7.4.3'
versionCode = 1004
versionName = '8.0'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
arg('room.generateKotlin', 'true')
arg('room.schemaLocation', "$projectDir/schemas")
}
androidResources {
generateLocaleConfig true
@@ -37,33 +39,50 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
nightly {
initWith release
applicationIdSuffix = '.nightly'
}
}
buildFeatures {
viewBinding true
buildConfig true
}
packagingOptions {
resources {
excludes += [
'META-INF/README.md',
'META-INF/NOTICE.md'
]
}
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
main.java.srcDirs += 'src/main/kotlin/'
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
jvmTarget = JavaVersion.VERSION_11.toString()
freeCompilerArgs += [
'-opt-in=kotlin.ExperimentalStdlibApi',
'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
'-opt-in=kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi',
'-opt-in=kotlinx.coroutines.FlowPreview',
'-opt-in=kotlin.contracts.ExperimentalContracts',
'-opt-in=coil.annotation.ExperimentalCoilApi',
'-opt-in=coil3.annotation.ExperimentalCoilApi',
'-opt-in=coil3.annotation.InternalCoilApi',
]
}
room {
schemaDirectory "$projectDir/schemas"
}
lint {
abortOnError true
disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged', 'SetJavaScriptEnabled'
disable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled', 'SimpleDateFormat'
}
testOptions {
unitTests.includeAndroidResources true
@@ -72,6 +91,15 @@ android {
freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']
}
}
applicationVariants.configureEach { variant ->
if (variant.name == 'nightly') {
variant.outputs.each { output ->
def now = LocalDateTime.now()
output.versionCodeOverride = now.format("yyMMdd").toInteger()
output.versionNameOverride = 'N' + now.format("yyyyMMdd")
}
}
}
}
afterEvaluate {
compileDebugKotlin {
@@ -81,89 +109,92 @@ afterEvaluate {
}
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:98cbee11b9') {
def parsersVersion = libs.versions.parsers.get()
if (System.properties.containsKey('parsersVersionOverride')) {
// usage:
// -DparsersVersionOverride=$(curl -s https://api.github.com/repos/kotatsuapp/kotatsu-parsers/commits/master -H "Accept: application/vnd.github.sha" | cut -c -10)
parsersVersion = System.getProperty('parsersVersionOverride')
}
//noinspection UseTomlInstead
implementation("com.github.KotatsuApp:kotatsu-parsers:$parsersVersion") {
exclude group: 'org.json', module: 'json'
}
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.10'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0-RC.2'
coreLibraryDesugaring libs.desugar.jdk.libs
implementation libs.kotlin.stdlib
implementation libs.kotlinx.coroutines.android
implementation libs.kotlinx.coroutines.guava
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.activity:activity-ktx:1.9.1'
implementation 'androidx.fragment:fragment-ktx:1.8.2'
implementation 'androidx.transition:transition-ktx:1.5.1'
implementation 'androidx.collection:collection-ktx:1.4.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
implementation 'androidx.lifecycle:lifecycle-service:2.8.4'
implementation 'androidx.lifecycle:lifecycle-process:2.8.4'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.viewpager2:viewpager2:1.1.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.4'
implementation 'androidx.webkit:webkit:1.11.0'
implementation libs.androidx.appcompat
implementation libs.androidx.core
implementation libs.androidx.activity
implementation libs.androidx.fragment
implementation libs.androidx.transition
implementation libs.androidx.collection
implementation libs.lifecycle.viewmodel
implementation libs.lifecycle.service
implementation libs.lifecycle.process
implementation libs.androidx.constraintlayout
implementation libs.androidx.swiperefreshlayout
implementation libs.androidx.recyclerview
implementation libs.androidx.viewpager2
implementation libs.androidx.preference
implementation libs.androidx.biometric
implementation libs.material
implementation libs.androidx.lifecycle.common.java8
implementation libs.androidx.webkit
implementation 'androidx.work:work-runtime:2.9.1'
//noinspection GradleDependency
implementation('com.google.guava:guava:32.0.1-android') {
exclude group: 'com.google.guava', module: 'failureaccess'
exclude group: 'org.checkerframework', module: 'checker-qual'
exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
}
implementation libs.androidx.work.runtime
implementation libs.guava
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.room:room-ktx:2.6.1'
ksp 'androidx.room:room-compiler:2.6.1'
implementation libs.androidx.room.runtime
implementation libs.androidx.room.ktx
ksp libs.androidx.room.compiler
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0'
implementation 'com.squareup.okio:okio:3.9.0'
implementation libs.okhttp
implementation libs.okhttp.tls
implementation libs.okhttp.dnsoverhttps
implementation libs.okio
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
implementation libs.adapterdelegates
implementation libs.adapterdelegates.viewbinding
implementation 'com.google.dagger:hilt-android:2.51.1'
kapt 'com.google.dagger:hilt-compiler:2.51.1'
implementation 'androidx.hilt:hilt-work:1.2.0'
kapt 'androidx.hilt:hilt-compiler:1.2.0'
implementation libs.hilt.android
kapt libs.hilt.compiler
implementation libs.androidx.hilt.work
kapt libs.androidx.hilt.compiler
implementation 'io.coil-kt:coil-base:2.7.0'
implementation 'io.coil-kt:coil-svg:2.7.0'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:882bc0620c'
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'io.noties.markwon:core:4.6.2'
implementation libs.coil.core
implementation libs.coil.network
implementation libs.coil.gif
implementation libs.coil.svg
implementation libs.avif.decoder
implementation libs.ssiv
implementation libs.disk.lru.cache
implementation libs.markwon
implementation 'ch.acra:acra-http:5.11.3'
implementation 'ch.acra:acra-dialog:5.11.3'
compileOnly 'com.google.auto.service:auto-service-annotations:1.1.1'
ksp 'dev.zacsweers.autoservice:auto-service-ksp:1.1.0'
implementation libs.acra.http
implementation libs.acra.dialog
implementation 'org.conscrypt:conscrypt-android:2.5.2'
implementation libs.conscrypt.android
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
debugImplementation 'com.github.Koitharu:WorkInspector:5778dd1747'
debugImplementation libs.leakcanary.android
debugImplementation libs.workinspector
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20240303'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1'
testImplementation libs.junit
testImplementation libs.json
testImplementation libs.kotlinx.coroutines.test
androidTestImplementation 'androidx.test:runner:1.6.1'
androidTestImplementation 'androidx.test:rules:1.6.1'
androidTestImplementation 'androidx.test:core-ktx:1.6.1'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.1'
androidTestImplementation libs.androidx.runner
androidTestImplementation libs.androidx.rules
androidTestImplementation libs.androidx.test.core
androidTestImplementation libs.androidx.junit
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1'
androidTestImplementation libs.kotlinx.coroutines.test
androidTestImplementation 'androidx.room:room-testing:2.6.1'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1'
androidTestImplementation libs.androidx.room.testing
androidTestImplementation libs.moshi.kotlin
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.51.1'
androidTestImplementation libs.hilt.android.testing
kaptAndroidTest libs.hilt.android.compiler
}

View File

@@ -14,6 +14,8 @@
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
-dontwarn com.google.j2objc.annotations.**
-dontwarn coil3.PlatformContext
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
@@ -21,3 +23,8 @@
-keep class org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment { *; }
-keep class org.jsoup.parser.Tag
-keep class org.jsoup.internal.StringUtil
-keep class org.acra.security.NoKeyStoreFactory { *; }
-keep class org.acra.config.DefaultRetryPolicy { *; }
-keep class org.acra.attachment.DefaultAttachmentProvider { *; }
-keep class org.acra.sender.JobSenderService

View File

@@ -1,198 +0,0 @@
package org.koitharu.kotatsu.tracker.domain
import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import junit.framework.TestCase.*
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koitharu.kotatsu.SampleData
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.parsers.model.Manga
import javax.inject.Inject
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class TrackerTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Inject
lateinit var repository: TrackingRepository
@Inject
lateinit var dataRepository: MangaDataRepository
@Inject
lateinit var tracker: Tracker
@Before
fun setUp() {
hiltRule.inject()
}
@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) }
tracker.syncWithHistory(mangaFull, chapter.id)
assertEquals(1, repository.getNewChaptersCount(mangaFirst.id))
chapter = requireNotNull(mangaFull.chapters).run { get(lastIndex) }
tracker.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

@@ -1,45 +1,99 @@
package org.koitharu.kotatsu
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.os.StrictMode
import androidx.core.content.edit
import androidx.fragment.app.strictmode.FragmentStrictMode
import leakcanary.LeakCanary
import org.koitharu.kotatsu.core.BaseApp
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
class KotatsuApp : BaseApp() {
var isLeakCanaryEnabled: Boolean
get() = getDebugPreferences(this).getBoolean(KEY_LEAK_CANARY, true)
set(value) {
getDebugPreferences(this).edit { putBoolean(KEY_LEAK_CANARY, value) }
configureLeakCanary()
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
enableStrictMode()
configureLeakCanary()
}
private fun configureLeakCanary() {
LeakCanary.config = LeakCanary.config.copy(
dumpHeap = isLeakCanaryEnabled,
)
}
private fun enableStrictMode() {
val notifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
StrictModeNotifier(this)
} else {
null
}
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build(),
StrictMode.ThreadPolicy.Builder().apply {
detectNetwork()
detectDiskWrites()
detectCustomSlowCalls()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectUnbufferedIo()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) detectResourceMismatches()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) detectExplicitGc()
penaltyLog()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {
penaltyListener(notifier.executor, notifier)
}
}.build(),
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.setClassInstanceLimit(LocalMangaRepository::class.java, 1)
.setClassInstanceLimit(PagesCache::class.java, 1)
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
.setClassInstanceLimit(PageLoader::class.java, 1)
.penaltyLog()
.build(),
StrictMode.VmPolicy.Builder().apply {
detectActivityLeaks()
detectLeakedSqlLiteObjects()
detectLeakedClosableObjects()
detectLeakedRegistrationObjects()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectContentUriWithoutPermission()
detectFileUriExposure()
setClassInstanceLimit(LocalMangaRepository::class.java, 1)
setClassInstanceLimit(PagesCache::class.java, 1)
setClassInstanceLimit(MangaLoaderContext::class.java, 1)
setClassInstanceLimit(PageLoader::class.java, 1)
setClassInstanceLimit(ReaderViewModel::class.java, 1)
penaltyLog()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {
penaltyListener(notifier.executor, notifier)
}
}.build(),
)
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
.penaltyDeath()
.detectFragmentReuse()
.detectWrongFragmentContainer()
.detectRetainInstanceUsage()
.detectSetUserVisibleHint()
.detectFragmentTagUsage()
.build()
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder().apply {
detectWrongFragmentContainer()
detectFragmentTagUsage()
detectRetainInstanceUsage()
detectSetUserVisibleHint()
detectWrongNestedHierarchy()
detectFragmentReuse()
penaltyLog()
if (notifier != null) {
penaltyListener(notifier)
}
}.build()
}
private companion object {
const val PREFS_DEBUG = "_debug"
const val KEY_LEAK_CANARY = "leak_canary"
fun getDebugPreferences(context: Context): SharedPreferences =
context.getSharedPreferences(PREFS_DEBUG, MODE_PRIVATE)
}
}

View File

@@ -0,0 +1,73 @@
package org.koitharu.kotatsu
import android.app.Notification
import android.app.Notification.BigTextStyle
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.os.StrictMode
import android.os.strictmode.Violation
import androidx.annotation.RequiresApi
import androidx.core.app.PendingIntentCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.strictmode.FragmentStrictMode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import org.koitharu.kotatsu.core.util.ShareHelper
import kotlin.math.absoluteValue
import androidx.fragment.app.strictmode.Violation as FragmentViolation
@RequiresApi(Build.VERSION_CODES.P)
class StrictModeNotifier(
private val context: Context,
) : StrictMode.OnVmViolationListener, StrictMode.OnThreadViolationListener, FragmentStrictMode.OnViolationListener {
val executor = Dispatchers.Default.asExecutor()
private val notificationManager by lazy {
val nm = checkNotNull(context.getSystemService<NotificationManager>())
val channel = NotificationChannel(
CHANNEL_ID,
context.getString(R.string.strict_mode),
NotificationManager.IMPORTANCE_LOW,
)
nm.createNotificationChannel(channel)
nm
}
override fun onVmViolation(v: Violation) = showNotification(v)
override fun onThreadViolation(v: Violation) = showNotification(v)
override fun onViolation(violation: FragmentViolation) = showNotification(violation)
private fun showNotification(violation: Throwable) = Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_bug)
.setContentTitle(context.getString(R.string.strict_mode))
.setContentText(violation.message)
.setStyle(
BigTextStyle()
.setBigContentTitle(context.getString(R.string.strict_mode))
.setSummaryText(violation.message)
.bigText(violation.stackTraceToString()),
).setShowWhen(true)
.setContentIntent(
PendingIntentCompat.getActivity(
context,
violation.hashCode(),
ShareHelper(context).getShareTextIntent(violation.stackTraceToString()),
0,
false,
),
)
.setAutoCancel(true)
.setGroup(CHANNEL_ID)
.build()
.let { notificationManager.notify(CHANNEL_ID, violation.hashCode().absoluteValue, it) }
private companion object {
const val CHANNEL_ID = "strict_mode"
}
}

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.network
import android.util.Log
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okio.Buffer
import org.koitharu.kotatsu.core.network.CommonHeaders.ACCEPT_ENCODING
@@ -12,8 +13,11 @@ class CurlLoggingInterceptor(
private val escapeRegex = Regex("([\\[\\]\"])")
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request()).also {
logRequest(it.networkResponse?.request ?: it.request)
}
private fun logRequest(request: Request) {
var isCompressed = false
val curlCmd = StringBuilder()
@@ -46,16 +50,11 @@ class CurlLoggingInterceptor(
log("---cURL (" + request.url + ")")
log(curlCmd.toString())
return chain.proceed(request)
}
private fun String.escape() = replace(escapeRegex) { match ->
"\\" + match.value
}
// .replace("\"", "\\\"")
// .replace("[", "\\[")
// .replace("]", "\\]")
private fun log(msg: String) {
Log.d("CURL", msg)

View File

@@ -0,0 +1,21 @@
package org.koitharu.kotatsu.core.ui
import android.content.Context
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import leakcanary.AppWatcher
abstract class BaseService : LifecycleService() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(ContextCompat.getContextForLanguage(newBase))
}
override fun onDestroy() {
super.onDestroy()
AppWatcher.objectWatcher.watch(
watchedObject = this,
description = "${javaClass.simpleName} service received Service#onDestroy() callback",
)
}
}

View File

@@ -6,6 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import leakcanary.LeakCanary
import org.koitharu.kotatsu.KotatsuApp
import org.koitharu.kotatsu.R
import org.koitharu.workinspector.WorkInspector
@@ -13,10 +14,18 @@ class SettingsMenuProvider(
private val context: Context,
) : MenuProvider {
private val application: KotatsuApp
get() = context.applicationContext as KotatsuApp
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_settings, menu)
}
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
menu.findItem(R.id.action_leakcanary).isChecked = application.isLeakCanaryEnabled
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_leaks -> {
context.startActivity(LeakCanary.newLeakDisplayActivityIntent())
@@ -28,6 +37,13 @@ class SettingsMenuProvider(
true
}
R.id.action_leakcanary -> {
val checked = !menuItem.isChecked
menuItem.isChecked = checked
application.isLeakCanaryEnabled = checked
true
}
else -> false
}
}

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.98150784"
android:scaleY="0.98150784"
android:translateX="0.22190611"
android:translateY="-0.2688478">
<path
android:fillColor="@android:color/white"
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

View File

@@ -1,15 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@id/action_leaks"
android:id="@+id/action_leakcanary"
android:checkable="true"
android:title="LeakCanary"
app:showAsAction="never"
tools:ignore="HardcodedText" />
<item
android:id="@+id/action_leaks"
android:title="@string/leak_canary_display_activity_label"
app:showAsAction="never" />
<item
android:id="@id/action_works"
android:id="@+id/action_works"
android:title="@string/wi_lib_name"
app:showAsAction="never" />

View File

@@ -1,3 +1,4 @@
<resources>
<string name="app_name" translatable="false">Kotatsu Dev</string>
</resources>
<string name="strict_mode">Strict mode</string>
</resources>

File diff suppressed because it is too large Load Diff

View File

@@ -3,85 +3,76 @@ package org.koitharu.kotatsu.alternatives.domain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.ext.almostEquals
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.search.domain.SearchKind
import org.koitharu.kotatsu.search.domain.SearchV2Helper
import java.util.Locale
import javax.inject.Inject
private const val MAX_PARALLELISM = 4
private const val MATCH_THRESHOLD = 0.2f
class AlternativesUseCase @Inject constructor(
private val sourcesRepository: MangaSourcesRepository,
private val searchHelperFactory: SearchV2Helper.Factory,
private val mangaRepositoryFactory: MangaRepository.Factory,
) {
suspend operator fun invoke(manga: Manga): Flow<Manga> {
val sources = getSources(manga.source)
suspend operator fun invoke(manga: Manga, throughDisabledSources: Boolean): Flow<Manga> {
val sources = getSources(manga.source, throughDisabledSources)
if (sources.isEmpty()) {
return emptyFlow()
}
val semaphore = Semaphore(MAX_PARALLELISM)
return channelFlow {
for (source in sources) {
val repository = mangaRepositoryFactory.create(source)
if (!repository.isSearchSupported) {
continue
}
launch {
val searchHelper = searchHelperFactory.create(source)
val list = runCatchingCancellable {
semaphore.withPermit {
repository.getList(offset = 0, filter = MangaListFilter.Search(manga.title))
searchHelper(manga.title, SearchKind.TITLE)?.manga
}
}.getOrDefault(emptyList())
for (item in list) {
if (item.matches(manga)) {
send(item)
}.getOrNull()
list?.forEach { m ->
if (m.id != manga.id) {
launch {
val details = runCatchingCancellable {
mangaRepositoryFactory.create(m.source).getDetails(m)
}.getOrDefault(m)
send(details)
}
}
}
}
}
}.map {
runCatchingCancellable {
mangaRepositoryFactory.create(it.source).getDetails(it)
}.getOrDefault(it)
}
}
private suspend fun getSources(ref: MangaSource): List<MangaSource> {
val result = ArrayList<MangaSource>(MangaParserSource.entries.size - 2)
result.addAll(sourcesRepository.getEnabledSources())
result.sortByDescending { it.priority(ref) }
result.addAll(sourcesRepository.getDisabledSources().sortedByDescending { it.priority(ref) })
return result
}
private fun Manga.matches(ref: Manga): Boolean {
return matchesTitles(title, ref.title) ||
matchesTitles(title, ref.altTitle) ||
matchesTitles(altTitle, ref.title) ||
matchesTitles(altTitle, ref.altTitle)
}
private fun matchesTitles(a: String?, b: String?): Boolean {
return !a.isNullOrEmpty() && !b.isNullOrEmpty() && a.almostEquals(b, MATCH_THRESHOLD)
}
private suspend fun getSources(ref: MangaSource, disabled: Boolean): List<MangaSource> = if (disabled) {
sourcesRepository.getDisabledSources()
} else {
sourcesRepository.getEnabledSources()
}.sortedByDescending { it.priority(ref) }
private fun MangaSource.priority(ref: MangaSource): Int {
var res = 0
if (this is MangaParserSource && ref is MangaParserSource) {
if (locale == ref.locale) res += 2
if (contentType == ref.contentType) res++
if (locale == ref.locale) {
res += 4
} else if (locale.toLocale() == Locale.getDefault()) {
res += 2
}
if (contentType == ref.contentType) {
res++
}
}
return res
}

View File

@@ -0,0 +1,96 @@
package org.koitharu.kotatsu.alternatives.domain
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.lastOrNull
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.koitharu.kotatsu.core.model.chaptersCount
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.ext.concat
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
class AutoFixUseCase @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val alternativesUseCase: AlternativesUseCase,
private val migrateUseCase: MigrateUseCase,
private val mangaDataRepository: MangaDataRepository,
) {
suspend operator fun invoke(mangaId: Long): Pair<Manga, Manga?> {
val seed = checkNotNull(
mangaDataRepository.findMangaById(mangaId, withChapters = true),
) { "Manga $mangaId not found" }.getDetailsSafe()
if (seed.isHealthy()) {
return seed to null // no fix required
}
val replacement = alternativesUseCase(seed, throughDisabledSources = false)
.concat(alternativesUseCase(seed, throughDisabledSources = true))
.filter { it.isHealthy() }
.runningFold<Manga, Manga?>(null) { best, candidate ->
if (best == null || best < candidate) {
candidate
} else {
best
}
}.selectLastWithTimeout(4, 40, TimeUnit.SECONDS)
migrateUseCase(seed, replacement ?: throw NoAlternativesException(ParcelableManga(seed)))
return seed to replacement
}
private suspend fun Manga.isHealthy(): Boolean = runCatchingCancellable {
val repo = mangaRepositoryFactory.create(source)
val details = if (this.chapters != null) this else repo.getDetails(this)
val firstChapter = details.chapters?.firstOrNull() ?: return@runCatchingCancellable false
val pageUrl = repo.getPageUrl(repo.getPages(firstChapter).first())
pageUrl.toHttpUrlOrNull() != null
}.getOrDefault(false)
private suspend fun Manga.getDetailsSafe() = runCatchingCancellable {
mangaRepositoryFactory.create(source).getDetails(this)
}.getOrDefault(this)
private operator fun Manga.compareTo(other: Manga) = chaptersCount().compareTo(other.chaptersCount())
@Suppress("UNCHECKED_CAST", "OPT_IN_USAGE")
private suspend fun <T> Flow<T>.selectLastWithTimeout(
minCount: Int,
timeout: Long,
timeUnit: TimeUnit
): T? = channelFlow<T?> {
var lastValue: T? = null
launch {
delay(timeUnit.toMillis(timeout))
close(InternalTimeoutException(lastValue))
}
withIndex().transformWhile { (index, value) ->
lastValue = value
emit(value)
index < minCount && !isClosedForSend
}.collect {
send(it)
}
}.catch { e ->
if (e is InternalTimeoutException) {
emit(e.value as T?)
} else {
throw e
}
}.lastOrNull()
class NoAlternativesException(val seed: ParcelableManga) : NoSuchElementException()
private class InternalTimeoutException(val value: Any?) : CancellationException()
}

View File

@@ -7,8 +7,8 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.history.data.toMangaHistory
import org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -136,7 +136,7 @@ constructor(
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
updatedAt = history.updatedAt,
chapterId = currentChapter.id,
page = history.page,
scroll = history.scroll,
@@ -173,7 +173,7 @@ constructor(
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
updatedAt = history.updatedAt,
chapterId = newChapterId,
page = history.page,
scroll = history.scroll,

View File

@@ -4,10 +4,18 @@ import android.text.style.ForegroundColorSpan
import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import coil3.ImageLoader
import coil3.request.ImageRequest
import coil3.request.allowRgb565
import coil3.request.crossfade
import coil3.request.error
import coil3.request.fallback
import coil3.request.lifecycle
import coil3.request.placeholder
import coil3.request.transformations
import coil3.transform.RoundedCornersTransformation
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getTitle
@@ -19,8 +27,10 @@ import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaExtra
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemMangaAlternativeBinding
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -43,10 +53,22 @@ fun alternativeAD(
binding.chipSource.setOnClickListener(clickListener)
bind { payloads ->
binding.textViewTitle.text = item.manga.title
binding.textViewTitle.text = item.mangaModel.title
with(binding.iconsView) {
clearIcons()
if (item.mangaModel.isSaved) addIcon(R.drawable.ic_storage)
if (item.mangaModel.isFavorite) addIcon(R.drawable.ic_heart_outline)
isVisible = iconsCount > 0
}
binding.textViewSubtitle.text = buildSpannedString {
if (item.chaptersCount > 0) {
append(context.resources.getQuantityString(R.plurals.chapters, item.chaptersCount, item.chaptersCount))
append(
context.resources.getQuantityStringSafe(
R.plurals.chapters,
item.chaptersCount,
item.chaptersCount,
),
)
} else {
append(context.getString(R.string.no_chapters))
}
@@ -62,7 +84,10 @@ fun alternativeAD(
}
}
}
binding.progressView.setProgress(item.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads)
binding.progressView.setProgress(
item.mangaModel.progress,
ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads,
)
binding.chipSource.also { chip ->
chip.text = item.manga.source.getTitle(chip.context)
ImageRequest.Builder(context)
@@ -74,8 +99,8 @@ fun alternativeAD(
.placeholder(R.drawable.ic_web)
.fallback(R.drawable.ic_web)
.error(R.drawable.ic_web)
.source(item.manga.source)
.transformations(CircleCropTransformation())
.mangaSourceExtra(item.manga.source)
.transformations(RoundedCornersTransformation(context.resources.getDimension(R.dimen.chip_icon_corner)))
.allowRgb565(true)
.enqueueWith(coil)
}
@@ -84,8 +109,7 @@ fun alternativeAD(
defaultPlaceholders(context)
transformations(TrimTransformation())
allowRgb565(true)
tag(item.manga)
source(item.manga.source)
mangaExtra(item.manga)
enqueueWith(coil)
}
}

View File

@@ -1,41 +1,40 @@
package org.koitharu.kotatsu.alternatives.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import coil.ImageLoader
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
import org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.systemBarsInsets
import org.koitharu.kotatsu.databinding.ActivityAlternativesBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.adapter.buttonFooterAD
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.search.ui.SearchActivity
import javax.inject.Inject
@AndroidEntryPoint
class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
ListStateHolderListener,
OnListItemClickListener<MangaAlternativeModel> {
@Inject
@@ -55,6 +54,7 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
.addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, this, null))
.addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
.addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
.addDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(this))
with(viewBinding.recyclerView) {
setHasFixedSize(true)
addItemDecoration(TypedListSpacingDecoration(context, addHorizontalPadding = false))
@@ -62,54 +62,63 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
}
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
viewModel.content.observe(this, listAdapter)
viewModel.list.observe(this, listAdapter)
viewModel.onMigrated.observeEvent(this) {
Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show()
startActivity(DetailsActivity.newIntent(this, it))
router.openDetails(it)
finishAfterTransition()
}
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
)
override fun onApplyWindowInsets(
v: View,
insets: WindowInsetsCompat
): WindowInsetsCompat {
val barsInsets = insets.systemBarsInsets
viewBinding.recyclerView.updatePadding(
bottom = insets.bottom + viewBinding.recyclerView.paddingTop,
left = barsInsets.left,
right = barsInsets.right,
bottom = barsInsets.bottom,
)
viewBinding.appbar.updatePadding(
left = barsInsets.left,
right = barsInsets.right,
top = barsInsets.top,
)
return insets.consumeAllSystemBarsInsets()
}
override fun onItemClick(item: MangaAlternativeModel, view: View) {
when (view.id) {
R.id.chip_source -> startActivity(SearchActivity.newIntent(this, item.manga.source, viewModel.manga.title))
R.id.chip_source -> router.openSearch(item.manga.source, viewModel.manga.title)
R.id.button_migrate -> confirmMigration(item.manga)
else -> startActivity(DetailsActivity.newIntent(this, item.manga))
else -> router.openDetails(item.manga)
}
}
override fun onRetryClick(error: Throwable) = viewModel.retry()
override fun onEmptyActionClick() = Unit
override fun onFooterButtonClick() = viewModel.continueSearch()
private fun confirmMigration(target: Manga) {
MaterialAlertDialogBuilder(this, DIALOG_THEME_CENTERED)
.setIcon(R.drawable.ic_replace)
.setTitle(R.string.manga_migration)
.setMessage(
buildAlertDialog(this, isCentered = true) {
setIcon(R.drawable.ic_replace)
setTitle(R.string.manga_migration)
setMessage(
getString(
R.string.migrate_confirmation,
viewModel.manga.title,
viewModel.manga.source.getTitle(this),
viewModel.manga.source.getTitle(context),
target.title,
target.source.getTitle(this),
target.source.getTitle(context),
),
)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.migrate) { _, _ ->
setNegativeButton(android.R.string.cancel, null)
setPositiveButton(R.string.migrate) { _, _ ->
viewModel.migrate(target)
}.show()
}
companion object {
fun newIntent(context: Context, manga: Manga) = Intent(context, AlternativesActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
}.show()
}
}

View File

@@ -1,33 +1,40 @@
package org.koitharu.kotatsu.alternatives.ui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEmpty
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase
import org.koitharu.kotatsu.alternatives.domain.MigrateUseCase
import org.koitharu.kotatsu.core.model.chaptersCount
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.append
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.list.domain.ReadingProgress
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.ButtonFooter
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.parsers.util.suspendlazy.getOrDefault
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
import javax.inject.Inject
@HiltViewModel
@@ -36,46 +43,67 @@ class AlternativesViewModel @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val alternativesUseCase: AlternativesUseCase,
private val migrateUseCase: MigrateUseCase,
private val historyRepository: HistoryRepository,
private val settings: AppSettings,
private val mangaListMapper: MangaListMapper,
) : BaseViewModel() {
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
private var includeDisabledSources = MutableStateFlow(false)
private val results = MutableStateFlow<List<MangaAlternativeModel>>(emptyList())
private var migrationJob: Job? = null
private var searchJob: Job? = null
private val mangaDetails = suspendLazy {
mangaRepositoryFactory.create(manga.source).getDetails(manga)
}
val onMigrated = MutableEventFlow<Manga>()
val content = MutableStateFlow<List<ListModel>>(listOf(LoadingState))
private var migrationJob: Job? = null
val list: StateFlow<List<ListModel>> = combine(
results,
isLoading,
includeDisabledSources,
) { list, loading, includeDisabled ->
when {
list.isEmpty() -> listOf(
when {
loading -> LoadingState
else -> EmptyState(
icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_search_holder_secondary,
actionStringRes = 0,
)
},
)
loading -> list + LoadingFooter()
includeDisabled -> list
else -> list + ButtonFooter(R.string.search_disabled_sources)
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
init {
launchJob(Dispatchers.Default) {
val ref = runCatchingCancellable {
mangaRepositoryFactory.create(manga.source).getDetails(manga)
}.getOrDefault(manga)
val refCount = ref.chaptersCount()
alternativesUseCase(ref)
.map {
MangaAlternativeModel(
manga = it,
progress = getProgress(it.id),
referenceChapters = refCount,
)
}.runningFold<MangaAlternativeModel, List<ListModel>>(listOf(LoadingState)) { acc, item ->
acc.filterIsInstance<MangaAlternativeModel>() + item + LoadingFooter()
}.onEmpty {
emit(
listOf(
EmptyState(
icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_search_holder_secondary,
actionStringRes = 0,
),
),
)
}.collect {
content.value = it
}
content.value = content.value.filterNot { it is LoadingFooter }
doSearch(throughDisabledSources = false)
}
fun retry() {
searchJob?.cancel()
results.value = emptyList()
includeDisabledSources.value = false
doSearch(throughDisabledSources = false)
}
fun continueSearch() {
if (includeDisabledSources.value) {
return
}
val prevJob = searchJob
searchJob = launchLoadingJob(Dispatchers.Default) {
includeDisabledSources.value = true
prevJob?.join()
doSearch(throughDisabledSources = true)
}
}
@@ -89,7 +117,20 @@ class AlternativesViewModel @Inject constructor(
}
}
private suspend fun getProgress(mangaId: Long): ReadingProgress? {
return historyRepository.getProgress(mangaId, settings.progressIndicatorMode)
private fun doSearch(throughDisabledSources: Boolean) {
val prevJob = searchJob
searchJob = launchLoadingJob(Dispatchers.Default) {
prevJob?.cancelAndJoin()
val ref = mangaDetails.getOrDefault(manga)
val refCount = ref.chaptersCount()
alternativesUseCase.invoke(ref, throughDisabledSources)
.collect {
val model = MangaAlternativeModel(
mangaModel = mangaListMapper.toListModel(it, ListMode.GRID) as MangaGridModel,
referenceChapters = refCount,
)
results.append(model)
}
}
}
}

View File

@@ -0,0 +1,196 @@
package org.koitharu.kotatsu.alternatives.ui
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import coil3.ImageLoader
import coil3.request.ImageRequest
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase
import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.powerManager
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class AutoFixService : CoroutineIntentService() {
@Inject
lateinit var autoFixUseCase: AutoFixUseCase
@Inject
lateinit var coil: ImageLoader
private lateinit var notificationManager: NotificationManagerCompat
override fun onCreate() {
super.onCreate()
notificationManager = NotificationManagerCompat.from(applicationContext)
}
override suspend fun IntentJobContext.processIntent(intent: Intent) {
val ids = requireNotNull(intent.getLongArrayExtra(DATA_IDS))
startForeground(this)
for (mangaId in ids) {
powerManager.withPartialWakeLock(TAG) {
val result = runCatchingCancellable {
autoFixUseCase.invoke(mangaId)
}
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
val notification = buildNotification(result)
notificationManager.notify(TAG, startId, notification)
}
}
}
}
override fun IntentJobContext.onError(error: Throwable) {
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
val notification = runBlocking { buildNotification(Result.failure(error)) }
notificationManager.notify(TAG, startId, notification)
}
}
@SuppressLint("InlinedApi")
private fun startForeground(jobContext: IntentJobContext) {
val title = applicationContext.getString(R.string.fixing_manga)
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_MIN)
.setName(title)
.setShowBadge(false)
.setVibrationEnabled(false)
.setSound(null, null)
.setLightsEnabled(false)
.build()
notificationManager.createNotificationChannel(channel)
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setContentTitle(title)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setDefaults(0)
.setSilent(true)
.setOngoing(true)
.setProgress(0, 0, true)
.setSmallIcon(R.drawable.ic_stat_auto_fix)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(
materialR.drawable.material_ic_clear_black_24dp,
applicationContext.getString(android.R.string.cancel),
jobContext.getCancelIntent(),
)
.build()
jobContext.setForeground(
FOREGROUND_NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
}
private suspend fun buildNotification(result: Result<Pair<Manga, Manga?>>): Notification {
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(0)
.setSilent(true)
.setAutoCancel(true)
result.onSuccess { (seed, replacement) ->
if (replacement != null) {
notification.setLargeIcon(
coil.execute(
ImageRequest.Builder(applicationContext)
.data(replacement.coverUrl)
.mangaSourceExtra(replacement.source)
.build(),
).toBitmapOrNull(),
)
notification.setSubText(replacement.title)
val intent = AppRouter.detailsIntent(applicationContext, replacement)
notification.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
replacement.id.toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT,
false,
),
).setVisibility(
if (replacement.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
)
notification
.setContentTitle(applicationContext.getString(R.string.fixed))
.setContentText(
applicationContext.getString(
R.string.manga_replaced,
seed.title,
seed.source.getTitle(applicationContext),
replacement.title,
replacement.source.getTitle(applicationContext),
),
)
.setSmallIcon(R.drawable.ic_stat_done)
} else {
notification
.setContentTitle(applicationContext.getString(R.string.fixing_manga))
.setContentText(applicationContext.getString(R.string.no_fix_required, seed.title))
.setSmallIcon(android.R.drawable.stat_sys_warning)
}
}.onFailure { error ->
notification
.setContentTitle(applicationContext.getString(R.string.error_occurred))
.setContentText(
if (error is AutoFixUseCase.NoAlternativesException) {
applicationContext.getString(R.string.no_alternatives_found, error.seed.manga.title)
} else {
error.getDisplayMessage(applicationContext.resources)
},
).setSmallIcon(android.R.drawable.stat_notify_error)
ErrorReporterReceiver.getPendingIntent(applicationContext, error)?.let { reportIntent ->
notification.addAction(
R.drawable.ic_alert_outline,
applicationContext.getString(R.string.report),
reportIntent,
)
}
}
return notification.build()
}
companion object {
private const val DATA_IDS = "ids"
private const val TAG = "auto_fix"
private const val CHANNEL_ID = "auto_fix"
private const val FOREGROUND_NOTIFICATION_ID = 38
fun start(context: Context, mangaIds: Collection<Long>): Boolean = try {
val intent = Intent(context, AutoFixService::class.java)
intent.putExtra(DATA_IDS, mangaIds.toLongArray())
ContextCompat.startForegroundService(context, intent)
true
} catch (e: Exception) {
e.printStackTraceDebug()
false
}
}
}

View File

@@ -1,16 +1,18 @@
package org.koitharu.kotatsu.alternatives.ui
import org.koitharu.kotatsu.core.model.chaptersCount
import org.koitharu.kotatsu.list.domain.ReadingProgress
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.parsers.model.Manga
data class MangaAlternativeModel(
val manga: Manga,
val progress: ReadingProgress?,
val mangaModel: MangaGridModel,
private val referenceChapters: Int,
) : ListModel {
val manga: Manga
get() = mangaModel.manga
val chaptersCount = manga.chaptersCount()
val chaptersDiff: Int
@@ -19,4 +21,10 @@ data class MangaAlternativeModel(
override fun areItemsTheSame(other: ListModel): Boolean {
return other is MangaAlternativeModel && other.manga.id == manga.id
}
override fun getChangePayload(previousState: ListModel): Any? = if (previousState is MangaAlternativeModel) {
mangaModel.getChangePayload(previousState.mangaModel)
} else {
null
}
}

View File

@@ -17,9 +17,6 @@ data class Bookmark(
val percent: Float,
) : ListModel {
val directImageUrl: String?
get() = if (isImageUrlDirect()) imageUrl else null
val imageLoadData: Any
get() = if (isImageUrlDirect()) imageUrl else toMangaPage()

View File

@@ -1,54 +1,5 @@
package org.koitharu.kotatsu.bookmarks.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import com.google.android.material.appbar.AppBarLayout
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.SnackbarOwner
import org.koitharu.kotatsu.core.ui.FragmentContainerActivity
@AndroidEntryPoint
class AllBookmarksActivity :
BaseActivity<ActivityContainerBinding>(),
AppBarOwner,
SnackbarOwner {
override val appBar: AppBarLayout
get() = viewBinding.appbar
override val snackbarHost: CoordinatorLayout
get() = viewBinding.root
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityContainerBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
replace(R.id.container, AllBookmarksFragment::class.java, null)
}
}
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
)
}
companion object {
fun newIntent(context: Context) = Intent(context, AllBookmarksActivity::class.java)
}
}
class AllBookmarksActivity : FragmentContainerActivity(AllBookmarksFragment::class.java)

View File

@@ -3,33 +3,35 @@ package org.koitharu.kotatsu.bookmarks.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import coil.ImageLoader
import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.systemBarsInsets
import org.koitharu.kotatsu.databinding.FragmentListSimpleBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.GridSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
@@ -38,7 +40,6 @@ import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import javax.inject.Inject
@AndroidEntryPoint
@@ -46,7 +47,7 @@ class AllBookmarksFragment :
BaseFragment<FragmentListSimpleBinding>(),
ListStateHolderListener,
OnListItemClickListener<Bookmark>,
ListSelectionController.Callback2,
ListSelectionController.Callback,
FastScroller.FastScrollListener, ListHeaderClickListener {
@Inject
@@ -106,6 +107,18 @@ class AllBookmarksFragment :
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))
}
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val barsInsets = insets.systemBarsInsets
val basePadding = resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)
viewBinding?.recyclerView?.setPadding(
barsInsets.left + basePadding,
barsInsets.top + basePadding,
barsInsets.right + basePadding,
barsInsets.bottom + basePadding,
)
return insets.consumeAllSystemBarsInsets()
}
override fun onDestroyView() {
super.onDestroyView()
bookmarksAdapter = null
@@ -114,22 +127,26 @@ class AllBookmarksFragment :
override fun onItemClick(item: Bookmark, view: View) {
if (selectionController?.onItemClick(item.pageId) != true) {
val intent = ReaderActivity.IntentBuilder(view.context)
val intent = ReaderIntent.Builder(view.context)
.bookmark(item)
.incognito(true)
.build()
startActivity(intent)
router.openReader(intent)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
}
override fun onListHeaderClick(item: ListHeader, view: View) {
val manga = item.payload as? Manga ?: return
startActivity(DetailsActivity.newIntent(view.context, manga))
router.openDetails(manga)
}
override fun onItemLongClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemLongClick(item.pageId) ?: false
return selectionController?.onItemLongClick(view, item.pageId) == true
}
override fun onItemContextClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.pageId) == true
}
override fun onRetryClick(error: Throwable) = Unit
@@ -148,23 +165,23 @@ class AllBookmarksFragment :
override fun onCreateActionMode(
controller: ListSelectionController,
mode: ActionMode,
menuInflater: MenuInflater,
menu: Menu,
): Boolean {
mode.menuInflater.inflate(R.menu.mode_bookmarks, menu)
menuInflater.inflate(R.menu.mode_bookmarks, menu)
return true
}
override fun onActionItemClicked(
controller: ListSelectionController,
mode: ActionMode,
mode: ActionMode?,
item: MenuItem,
): Boolean {
return when (item.itemId) {
R.id.action_remove -> {
val ids = selectionController?.snapshot() ?: return false
viewModel.removeBookmarks(ids)
mode.finish()
mode?.finish()
true
}
@@ -172,16 +189,6 @@ class AllBookmarksFragment :
}
}
override fun onWindowInsetsChanged(insets: Insets) {
val rv = requireViewBinding().recyclerView
rv.updatePadding(
bottom = insets.bottom + rv.paddingTop,
)
rv.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = insets.bottom
}
}
private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup(), Runnable {
init {
@@ -203,16 +210,4 @@ class AllBookmarksFragment :
invalidateSpanIndexCache()
}
}
companion object {
@Deprecated(
"",
ReplaceWith(
"BookmarksFragment()",
"org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment",
),
)
fun newInstance() = AllBookmarksFragment()
}
}

View File

@@ -1,17 +1,18 @@
package org.koitharu.kotatsu.bookmarks.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil3.ImageLoader
import coil3.request.allowRgb565
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.bookmarkExtra
import org.koitharu.kotatsu.core.util.ext.decodeRegion
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemBookmarkLargeBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -22,19 +23,15 @@ fun bookmarkLargeAD(
) = adapterDelegateViewBinding<Bookmark, ListModel, ItemBookmarkLargeBinding>(
{ inflater, parent -> ItemBookmarkLargeBinding.inflate(inflater, parent, false) },
) {
val listener = AdapterDelegateClickListenerAdapter(this, clickListener)
binding.root.setOnClickListener(listener)
binding.root.setOnLongClickListener(listener)
AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
bind {
binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run {
size(CoverSizeResolver(binding.imageViewThumb))
defaultPlaceholders(context)
allowRgb565(true)
tag(item)
bookmarkExtra(item)
decodeRegion(item.scroll)
source(item.manga.source)
enqueueWith(coil)
}
binding.progressView.setProgress(item.percent, false)

View File

@@ -1,40 +0,0 @@
package org.koitharu.kotatsu.bookmarks.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.decodeRegion
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
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(lifecycleOwner, item.imageLoadData)?.run {
size(CoverSizeResolver(binding.imageViewThumb))
defaultPlaceholders(context)
allowRgb565(true)
tag(item)
decodeRegion(item.scroll)
source(item.manga.source)
enqueueWith(coil)
}
}
}

View File

@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.bookmarks.ui.adapter
import android.content.Context
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil3.ImageLoader
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
@@ -25,6 +26,7 @@ class BookmarksAdapter(
init {
addDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(coil, lifecycleOwner, clickListener))
addDelegate(ListItemType.HEADER, listHeaderAD(headerClickListener))
addDelegate(ListItemType.STATE_ERROR, errorStateListAD(null))
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, null))

View File

@@ -0,0 +1,82 @@
package org.koitharu.kotatsu.browser
import android.os.Bundle
import android.view.View
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.consumeAll
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import javax.inject.Inject
@AndroidEntryPoint
abstract class BaseBrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
@Inject
lateinit var proxyProvider: ProxyProvider
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
return
}
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
onBackPressedDispatcher.addCallback(onBackPressedCallback)
}
override fun onApplyWindowInsets(
v: View,
insets: WindowInsetsCompat
): WindowInsetsCompat {
val type = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()
val barsInsets = insets.getInsets(type)
viewBinding.webView.updatePadding(
left = barsInsets.left,
right = barsInsets.right,
bottom = barsInsets.bottom,
)
viewBinding.appbar.updatePadding(
left = barsInsets.left,
right = barsInsets.right,
top = barsInsets.top,
)
return insets.consumeAll(type)
}
override fun onPause() {
viewBinding.webView.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
viewBinding.webView.onResume()
}
override fun onDestroy() {
super.onDestroy()
if (hasViewBinding()) {
viewBinding.webView.stopLoading()
viewBinding.webView.destroy()
}
}
override fun onLoadingStateChanged(isLoading: Boolean) {
viewBinding.progressBar.isVisible = isLoading
}
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
this.title = title
supportActionBar?.subtitle = subtitle
}
override fun onHistoryChanged() {
onBackPressedCallback.onHistoryChanged()
}
}

View File

@@ -1,69 +1,57 @@
package org.koitharu.kotatsu.browser
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.webkit.CookieManager
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import okhttp3.internal.userAgent
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
class BrowserActivity : BaseBrowserActivity() {
@Inject
lateinit var mangaRepositoryFactory: MangaRepository.Factory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
return
}
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
}
val mangaSource = MangaSource(intent?.getStringExtra(EXTRA_SOURCE))
setDisplayHomeAsUp(true, true)
val mangaSource = MangaSource(intent?.getStringExtra(AppRouter.KEY_SOURCE))
val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository
repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)
val userAgent = repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)
viewBinding.webView.configureForParser(userAgent)
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)
viewBinding.webView.webViewClient = BrowserClient(this)
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
onBackPressedDispatcher.addCallback(onBackPressedCallback)
if (savedInstanceState != null) {
return
}
val url = intent?.dataString
if (url.isNullOrEmpty()) {
finishAfterTransition()
} else {
onTitleChanged(
intent?.getStringExtra(EXTRA_TITLE) ?: getString(R.string.loading_),
url,
)
viewBinding.webView.loadUrl(url)
viewBinding.webView.webViewClient = BrowserClient(proxyProvider, this)
lifecycleScope.launch {
try {
proxyProvider.applyWebViewConfig()
} catch (e: Exception) {
e.printStackTraceDebug()
Snackbar.make(viewBinding.webView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}
if (savedInstanceState == null) {
val url = intent?.dataString
if (url.isNullOrEmpty()) {
finishAfterTransition()
} else {
onTitleChanged(
intent?.getStringExtra(AppRouter.KEY_TITLE) ?: getString(R.string.loading_),
url,
)
viewBinding.webView.loadUrl(url)
}
}
}
}
@@ -81,73 +69,12 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
}
R.id.action_browser -> {
val url = viewBinding.webView.url?.toUriOrNull()
if (url != null) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = url
try {
startActivity(Intent.createChooser(intent, item.title))
} catch (_: ActivityNotFoundException) {
}
if (!router.openExternalBrowser(viewBinding.webView.url.orEmpty(), item.title)) {
Snackbar.make(viewBinding.webView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onPause() {
viewBinding.webView.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
viewBinding.webView.onResume()
}
override fun onDestroy() {
super.onDestroy()
if (hasViewBinding()) {
viewBinding.webView.stopLoading()
viewBinding.webView.destroy()
}
}
override fun onLoadingStateChanged(isLoading: Boolean) {
viewBinding.progressBar.isVisible = isLoading
}
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
this.title = title
supportActionBar?.subtitle = subtitle
}
override fun onHistoryChanged() {
onBackPressedCallback.onHistoryChanged()
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.appbar.updatePadding(
top = insets.top,
)
viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom,
)
}
companion object {
private const val EXTRA_TITLE = "title"
private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, url: String, source: MangaSource?, title: String?): Intent {
return Intent(context, BrowserActivity::class.java)
.setData(Uri.parse(url))
.putExtra(EXTRA_TITLE, title)
.putExtra(EXTRA_SOURCE, source?.name)
}
}
}

View File

@@ -2,9 +2,13 @@ package org.koitharu.kotatsu.browser
import android.graphics.Bitmap
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.webkit.WebViewClientCompat
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
open class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
open class BrowserClient(
private val proxyProvider: ProxyProvider,
private val callback: BrowserCallback
) : WebViewClientCompat() {
override fun onPageFinished(webView: WebView, url: String) {
super.onPageFinished(webView, url)
@@ -16,7 +20,7 @@ open class BrowserClient(private val callback: BrowserCallback) : WebViewClient(
callback.onLoadingStateChanged(isLoading = true)
}
override fun onPageCommitVisible(view: WebView, url: String?) {
override fun onPageCommitVisible(view: WebView, url: String) {
super.onPageCommitVisible(view, url)
callback.onTitleChanged(view.title.orEmpty(), url)
}

View File

@@ -9,26 +9,32 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.net.toUri
import coil.EventListener
import coil.request.ErrorResult
import coil.request.ImageRequest
import coil3.EventListener
import coil3.Extras
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.parsers.model.MangaSource
class CaptchaNotifier(
private val context: Context,
) : EventListener {
) : EventListener() {
fun notify(exception: CloudFlareProtectedException) {
if (!context.checkNotificationPermission(CHANNEL_ID)) {
return
}
if (exception.source != null && SourceSettings(context, exception.source).isCaptchaNotificationsDisabled) {
return
}
val manager = NotificationManagerCompat.from(context)
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
.setName(context.getString(R.string.captcha_required))
.setShowBadge(true)
.setVibrationEnabled(false)
@@ -37,13 +43,13 @@ class CaptchaNotifier(
.build()
manager.createNotificationChannel(channel)
val intent = CloudFlareActivity.newIntent(context, exception)
val intent = AppRouter.cloudFlareResolveIntent(context, exception)
.setData(exception.url.toUri())
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(channel.name)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(NotificationCompat.DEFAULT_SOUND)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setDefaults(0)
.setSmallIcon(R.drawable.ic_bot)
.setGroup(GROUP_CAPTCHA)
.setAutoCancel(true)
.setVisibility(
@@ -84,20 +90,19 @@ class CaptchaNotifier(
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
val e = result.throwable
if (e is CloudFlareProtectedException && request.parameters.value<Boolean>(PARAM_IGNORE_CAPTCHA) != true) {
if (e is CloudFlareProtectedException && request.extras[ignoreCaptchaKey] != true) {
notify(e)
}
}
companion object {
fun ImageRequest.Builder.ignoreCaptchaErrors() = setParameter(
key = PARAM_IGNORE_CAPTCHA,
value = true,
memoryCacheKey = null,
)
fun ImageRequest.Builder.ignoreCaptchaErrors() = apply {
extras[ignoreCaptchaKey] = true
}
val ignoreCaptchaKey = Extras.Key(false)
private const val PARAM_IGNORE_CAPTCHA = "ignore_captcha"
private const val CHANNEL_ID = "captcha"
private const val TAG = CHANNEL_ID
private const val GROUP_CAPTCHA = "org.koitharu.kotatsu.CAPTCHA"

View File

@@ -5,38 +5,30 @@ import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.webkit.CookieManager
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.graphics.Insets
import androidx.core.net.toUri
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.yield
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.browser.BaseBrowserActivity
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.TaggedActivityResult
import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCallback {
class CloudFlareActivity : BaseBrowserActivity(), CloudFlareCallback {
private var pendingResult = RESULT_CANCELED
@@ -44,43 +36,29 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
lateinit var cookieJar: MutableCookieJar
private lateinit var cfClient: CloudFlareClient
private var onBackPressedCallback: WebViewBackPressedCallback? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
return
}
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
}
setDisplayHomeAsUp(true, true)
val url = intent?.dataString
if (url.isNullOrEmpty()) {
finishAfterTransition()
return
}
cfClient = CloudFlareClient(cookieJar, this, url)
viewBinding.webView.configureForParser(intent?.getStringExtra(ARG_UA))
cfClient = CloudFlareClient(proxyProvider, cookieJar, this, url)
viewBinding.webView.configureForParser(intent?.getStringExtra(AppRouter.KEY_USER_AGENT))
viewBinding.webView.webViewClient = cfClient
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also {
onBackPressedDispatcher.addCallback(it)
lifecycleScope.launch {
try {
proxyProvider.applyWebViewConfig()
} catch (e: Exception) {
Snackbar.make(viewBinding.webView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}
if (savedInstanceState == null) {
onTitleChanged(getString(R.string.loading_), url)
viewBinding.webView.loadUrl(url)
}
}
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)
if (savedInstanceState == null) {
onTitleChanged(getString(R.string.loading_), url)
viewBinding.webView.loadUrl(url)
}
}
override fun onDestroy() {
runCatching {
viewBinding.webView
}.onSuccess {
it.stopLoading()
it.destroy()
}
super.onDestroy()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@@ -88,17 +66,6 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
return super.onCreateOptionsMenu(menu)
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.appbar.updatePadding(
top = insets.top,
)
viewBinding.root.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom,
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> {
viewBinding.webView.stopLoading()
@@ -114,21 +81,13 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
else -> super.onOptionsItemSelected(item)
}
override fun onResume() {
super.onResume()
viewBinding.webView.onResume()
}
override fun onPause() {
viewBinding.webView.onPause()
super.onPause()
}
override fun finish() {
setResult(pendingResult)
super.finish()
}
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
override fun onPageLoaded() {
viewBinding.progressBar.isInvisible = true
}
@@ -139,21 +98,13 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
override fun onCheckPassed() {
pendingResult = RESULT_OK
val source = intent?.getStringExtra(ARG_SOURCE)
val source = intent?.getStringExtra(AppRouter.KEY_SOURCE)
if (source != null) {
CaptchaNotifier(this).dismiss(MangaSource(source))
}
finishAfterTransition()
}
override fun onLoadingStateChanged(isLoading: Boolean) {
viewBinding.progressBar.isVisible = isLoading
}
override fun onHistoryChanged() {
onBackPressedCallback?.onHistoryChanged()
}
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
setTitle(title)
supportActionBar?.subtitle =
@@ -175,45 +126,22 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
private suspend fun clearCfCookies(url: HttpUrl) = runInterruptible(Dispatchers.Default) {
cookieJar.removeCookies(url) { cookie ->
val name = cookie.name
name.startsWith("cf_") || name.startsWith("_cf") || name.startsWith("__cf") || name == "csrftoken"
CloudFlareHelper.isCloudFlareCookie(cookie.name)
}
}
class Contract : ActivityResultContract<CloudFlareProtectedException, TaggedActivityResult>() {
class Contract : ActivityResultContract<CloudFlareProtectedException, Boolean>() {
override fun createIntent(context: Context, input: CloudFlareProtectedException): Intent {
return newIntent(context, input)
return AppRouter.cloudFlareResolveIntent(context, input)
}
override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult {
return TaggedActivityResult(TAG, resultCode)
override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
return resultCode == RESULT_OK
}
}
companion object {
const val TAG = "CloudFlareActivity"
private const val ARG_UA = "ua"
private const val ARG_SOURCE = "_source"
fun newIntent(context: Context, exception: CloudFlareProtectedException) = newIntent(
context = context,
url = exception.url,
source = exception.source,
headers = exception.headers,
)
private fun newIntent(
context: Context,
url: String,
source: MangaSource?,
headers: Headers?,
) = Intent(context, CloudFlareActivity::class.java).apply {
data = url.toUri()
putExtra(ARG_SOURCE, source?.name)
headers?.get(CommonHeaders.USER_AGENT)?.let {
putExtra(ARG_UA, it)
}
}
}
}

View File

@@ -4,8 +4,6 @@ import org.koitharu.kotatsu.browser.BrowserCallback
interface CloudFlareCallback : BrowserCallback {
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) = Unit
fun onPageLoaded()

View File

@@ -2,18 +2,19 @@ package org.koitharu.kotatsu.browser.cloudflare
import android.graphics.Bitmap
import android.webkit.WebView
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
private const val CF_CLEARANCE = "cf_clearance"
private const val LOOP_COUNTER = 3
class CloudFlareClient(
proxyProvider: ProxyProvider,
private val cookieJar: MutableCookieJar,
private val callback: CloudFlareCallback,
private val targetUrl: String,
) : BrowserClient(callback) {
) : BrowserClient(proxyProvider, callback) {
private val oldClearance = getClearance()
private var counter = 0
@@ -23,7 +24,7 @@ class CloudFlareClient(
checkClearance()
}
override fun onPageCommitVisible(view: WebView, url: String?) {
override fun onPageCommitVisible(view: WebView, url: String) {
super.onPageCommitVisible(view, url)
callback.onPageLoaded()
}
@@ -50,8 +51,5 @@ class CloudFlareClient(
}
}
private fun getClearance(): String? {
return cookieJar.loadForRequest(targetUrl.toHttpUrl())
.find { it.name == CF_CLEARANCE }?.value
}
private fun getClearance() = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)
}

View File

@@ -1,18 +1,23 @@
package org.koitharu.kotatsu.core
import android.app.Application
import android.content.ContentResolver
import android.content.Context
import android.os.Build
import android.provider.SearchRecentSuggestions
import android.text.Html
import androidx.collection.arraySetOf
import androidx.core.content.ContextCompat
import androidx.room.InvalidationTracker
import androidx.work.WorkManager
import coil.ComponentRegistry
import coil.ImageLoader
import coil.decode.SvgDecoder
import coil.disk.DiskCache
import coil.util.DebugLogger
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.gif.AnimatedImageDecoder
import coil3.gif.GifDecoder
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowRgb565
import coil3.svg.SvgDecoder
import coil3.util.DebugLogger
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -28,6 +33,9 @@ import okhttp3.OkHttpClient
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.image.AvifImageDecoder
import org.koitharu.kotatsu.core.image.CbzFetcher
import org.koitharu.kotatsu.core.image.MangaSourceHeaderInterceptor
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
import org.koitharu.kotatsu.core.os.AppShortcutManager
@@ -44,7 +52,6 @@ import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer
import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.CbzFetcher
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
@@ -70,6 +77,12 @@ interface AppModule {
companion object {
@Provides
@LocalizedAppContext
fun provideLocalizedContext(
@ApplicationContext context: Context,
): Context = ContextCompat.getContextForLanguage(context)
@Provides
@Singleton
fun provideNetworkState(
@@ -81,19 +94,18 @@ interface AppModule {
@Singleton
fun provideMangaDatabase(
@ApplicationContext context: Context,
): MangaDatabase {
return MangaDatabase(context)
}
): MangaDatabase = MangaDatabase(context)
@Provides
@Singleton
fun provideCoil(
@ApplicationContext context: Context,
@LocalizedAppContext context: Context,
@MangaHttpClient okHttpClientProvider: Provider<OkHttpClient>,
mangaRepositoryFactory: MangaRepository.Factory,
imageProxyInterceptor: ImageProxyInterceptor,
pageFetcherFactory: MangaPageFetcher.Factory,
coverRestoreInterceptor: CoverRestoreInterceptor,
networkStateProvider: Provider<NetworkState>,
): ImageLoader {
val diskCacheFactory = {
val rootDir = context.externalCacheDir ?: context.cacheDir
@@ -105,36 +117,39 @@ interface AppModule {
okHttpClientProvider.get().newBuilder().cache(null).build()
}
return ImageLoader.Builder(context)
.okHttpClient { okHttpClientLazy.value }
.interceptorDispatcher(Dispatchers.Default)
.fetcherDispatcher(Dispatchers.Default)
.decoderDispatcher(Dispatchers.IO)
.transformationDispatcher(Dispatchers.Default)
.interceptorCoroutineContext(Dispatchers.Default)
.diskCache(diskCacheFactory)
.respectCacheHeaders(false)
.networkObserverEnabled(false)
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
.allowRgb565(context.isLowRamDevice())
.eventListener(CaptchaNotifier(context))
.components(
ComponentRegistry.Builder()
.add(SvgDecoder.Factory())
.add(CbzFetcher.Factory())
.add(FaviconFetcher.Factory(context, okHttpClientLazy, mangaRepositoryFactory))
.add(MangaPageKeyer())
.add(pageFetcherFactory)
.add(imageProxyInterceptor)
.add(coverRestoreInterceptor)
.build(),
).build()
.components {
add(
OkHttpNetworkFetcherFactory(
callFactory = okHttpClientLazy::value,
connectivityChecker = { networkStateProvider.get() },
),
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(AnimatedImageDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
add(SvgDecoder.Factory())
add(CbzFetcher.Factory())
add(AvifImageDecoder.Factory())
add(FaviconFetcher.Factory(mangaRepositoryFactory))
add(MangaPageKeyer())
add(pageFetcherFactory)
add(imageProxyInterceptor)
add(coverRestoreInterceptor)
add(MangaSourceHeaderInterceptor())
}.build()
}
@Provides
fun provideSearchSuggestions(
@ApplicationContext context: Context,
): SearchRecentSuggestions {
return MangaSuggestionsProvider.createSuggestions(context)
}
): SearchRecentSuggestions = MangaSuggestionsProvider.createSuggestions(context)
@Provides
@ElementsIntoSet

View File

@@ -11,8 +11,8 @@ import androidx.work.Configuration
import androidx.work.WorkManager
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.dialog
@@ -25,9 +25,14 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.os.AppValidator
import org.koitharu.kotatsu.core.os.RomCompat
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.WorkServiceStopHelper
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.index.LocalMangaIndex
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull
import org.koitharu.kotatsu.settings.work.WorkScheduleManager
import java.security.Security
import javax.inject.Inject
@@ -60,6 +65,13 @@ open class BaseApp : Application(), Configuration.Provider {
@Inject
lateinit var workManagerProvider: Provider<WorkManager>
@Inject
lateinit var localMangaIndexProvider: Provider<LocalMangaIndex>
@Inject
@LocalStorageChanges
lateinit var localStorageChanges: MutableSharedFlow<LocalManga?>
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
@@ -67,21 +79,22 @@ open class BaseApp : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()
if (ACRA.isACRASenderServiceProcess()) {
return
}
AppCompatDelegate.setDefaultNightMode(settings.theme)
AppCompatDelegate.setApplicationLocales(settings.appLocales)
// TLS 1.3 support for Android < 10
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Security.insertProviderAt(Conscrypt.newProvider(), 1)
}
setupActivityLifecycleCallbacks()
processLifecycleScope.launch {
val isOriginalApp = withContext(Dispatchers.Default) {
appValidator.isOriginalApp
}
ACRA.errorReporter.putCustomData("isOriginalApp", isOriginalApp.toString())
ACRA.errorReporter.putCustomData("isOriginalApp", appValidator.isOriginalApp.getOrNull().toString())
ACRA.errorReporter.putCustomData("isMiui", RomCompat.isMiui.getOrNull().toString())
}
processLifecycleScope.launch(Dispatchers.Default) {
setupDatabaseObservers()
localStorageChanges.collect(localMangaIndexProvider.get())
}
workScheduleManager.init()
WorkServiceStopHelper(workManagerProvider).setup()

View File

@@ -5,29 +5,34 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.BadParcelableException
import androidx.core.app.PendingIntentCompat
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.report
class ErrorReporterReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val e = intent?.getSerializableExtraCompat<Throwable>(EXTRA_ERROR) ?: return
val e = intent?.getSerializableExtraCompat<Throwable>(AppRouter.KEY_ERROR) ?: return
e.report()
}
companion object {
private const val EXTRA_ERROR = "err"
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
fun getPendingIntent(context: Context, e: Throwable): PendingIntent {
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = try {
val intent = Intent(context, ErrorReporterReceiver::class.java)
intent.setAction(ACTION_REPORT)
intent.setData(Uri.parse("err://${e.hashCode()}"))
intent.putExtra(EXTRA_ERROR, e)
return checkNotNull(PendingIntentCompat.getBroadcast(context, 0, intent, 0, false))
intent.putExtra(AppRouter.KEY_ERROR, e)
PendingIntentCompat.getBroadcast(context, 0, intent, 0, false)
} catch (e: BadParcelableException) {
e.printStackTraceDebug()
null
}
}
}

View File

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

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.core
import javax.inject.Qualifier
@Qualifier
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.FIELD,
)
annotation class LocalizedAppContext

View File

@@ -16,6 +16,7 @@ class BackupEntry(
CATEGORIES("categories"),
FAVOURITES("favourites"),
SETTINGS("settings"),
SETTINGS_READER_GRID("reader_grid"),
BOOKMARKS("bookmarks"),
SOURCES("sources"),
}

View File

@@ -0,0 +1,12 @@
package org.koitharu.kotatsu.core.backup
import android.net.Uri
import java.util.Date
data class BackupFile(
val uri: Uri,
val dateTime: Date,
): Comparable<BackupFile> {
override fun compareTo(other: BackupFile): Int = compareValues(dateTime, other.dateTime)
}

View File

@@ -1,15 +1,18 @@
package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction
import kotlinx.coroutines.flow.FlowCollector
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.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
import org.koitharu.kotatsu.core.util.progress.Progress
import org.koitharu.kotatsu.parsers.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.data.TapGridSettings
import java.util.Date
import javax.inject.Inject
@@ -18,6 +21,7 @@ private const val PAGE_SIZE = 10
class BackupRepository @Inject constructor(
private val db: MangaDatabase,
private val settings: AppSettings,
private val tapGridSettings: TapGridSettings,
) {
suspend fun dumpHistory(): BackupEntry {
@@ -103,6 +107,14 @@ class BackupRepository @Inject constructor(
return entry
}
fun dumpReaderGridSettings(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.SETTINGS_READER_GRID, JSONArray())
val settingsDump = tapGridSettings.getAllValues()
val json = JsonSerializer(settingsDump).toJson()
entry.data.put(json)
return entry
}
suspend fun dumpSources(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.SOURCES, JSONArray())
val all = db.getSourcesDao().findAll()
@@ -128,9 +140,11 @@ class BackupRepository @Inject constructor(
return if (timestamp == 0L) null else Date(timestamp)
}
suspend fun restoreHistory(entry: BackupEntry): CompositeResult {
suspend fun restoreHistory(entry: BackupEntry, outProgress: FlowCollector<Progress>?): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val list = entry.data.asTypedList<JSONObject>()
outProgress?.emit(Progress(progress = 0, total = list.size))
for ((index, item) in list.withIndex()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = mangaJson.getJSONArray("tags").mapJSON {
@@ -144,13 +158,14 @@ class BackupRepository @Inject constructor(
db.getHistoryDao().upsert(history)
}
}
outProgress?.emit(Progress(progress = index, total = list.size))
}
return result
}
suspend fun restoreCategories(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
for (item in entry.data.asTypedList<JSONObject>()) {
val category = JsonDeserializer(item).toFavouriteCategoryEntity()
result += runCatchingCancellable {
db.getFavouriteCategoriesDao().upsert(category)
@@ -159,9 +174,11 @@ class BackupRepository @Inject constructor(
return result
}
suspend fun restoreFavourites(entry: BackupEntry): CompositeResult {
suspend fun restoreFavourites(entry: BackupEntry, outProgress: FlowCollector<Progress>?): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val list = entry.data.asTypedList<JSONObject>()
outProgress?.emit(Progress(progress = 0, total = list.size))
for ((index, item) in list.withIndex()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = mangaJson.getJSONArray("tags").mapJSON {
@@ -175,13 +192,14 @@ class BackupRepository @Inject constructor(
db.getFavouritesDao().upsert(favourite)
}
}
outProgress?.emit(Progress(progress = index, total = list.size))
}
return result
}
suspend fun restoreBookmarks(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
for (item in entry.data.asTypedList<JSONObject>()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = item.getJSONArray("tags").mapJSON {
@@ -203,7 +221,7 @@ class BackupRepository @Inject constructor(
suspend fun restoreSources(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
for (item in entry.data.asTypedList<JSONObject>()) {
val source = JsonDeserializer(item).toMangaSourceEntity()
result += runCatchingCancellable {
db.getSourcesDao().upsert(source)
@@ -214,11 +232,21 @@ class BackupRepository @Inject constructor(
fun restoreSettings(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
for (item in entry.data.asTypedList<JSONObject>()) {
result += runCatchingCancellable {
settings.upsertAll(JsonDeserializer(item).toMap())
}
}
return result
}
fun restoreReaderGridSettings(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
result += runCatchingCancellable {
tapGridSettings.upsertAll(JsonDeserializer(item).toMap())
}
}
return result
}
}

View File

@@ -1,13 +1,11 @@
package org.koitharu.kotatsu.core.backup
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import okhttp3.internal.closeQuietly
import okio.Closeable
import org.json.JSONArray
import org.koitharu.kotatsu.core.exceptions.BadBackupFormatException
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import java.io.File
import java.util.EnumSet
import java.util.zip.ZipException
@@ -35,25 +33,29 @@ class BackupZipInput private constructor(val file: File) : Closeable {
zipFile.close()
}
fun cleanupAsync() {
processLifecycleScope.launch(Dispatchers.IO, CoroutineStart.ATOMIC) {
runCatching {
close()
file.delete()
}
}
fun closeAndDelete() {
closeQuietly()
file.delete()
}
companion object {
fun from(file: File): BackupZipInput = try {
val res = BackupZipInput(file)
if (res.zipFile.getEntry("index") == null) {
throw BadBackupFormatException(null)
fun from(file: File): BackupZipInput {
var res: BackupZipInput? = null
return try {
res = BackupZipInput(file)
if (res.zipFile.getEntry("index") == null) {
throw BadBackupFormatException(null)
}
res
} catch (exception: Throwable) {
res?.closeQuietly()
throw if (exception is ZipException) {
BadBackupFormatException(exception)
} else {
exception
}
}
res
} catch (e: ZipException) {
throw BadBackupFormatException(e)
}
}
}

View File

@@ -5,10 +5,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.zip.ZipOutput
import java.io.File
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.zip.Deflater
@@ -27,20 +29,32 @@ class BackupZipOutput(val file: File) : Closeable {
override fun close() {
output.close()
}
}
const val DIR_BACKUPS = "backups"
companion object {
suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptible(Dispatchers.IO) {
val dir = context.run {
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
const val DIR_BACKUPS = "backups"
private val dateTimeFormat = SimpleDateFormat("yyyyMMdd-HHmm")
fun generateFileName(context: Context) = buildString {
append(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))
append('_')
append(dateTimeFormat.format(Date()))
append(".bk.zip")
}
fun parseBackupDateTime(fileName: String): Date? = try {
dateTimeFormat.parse(fileName.substringAfterLast('_').substringBefore('.'))
} catch (e: ParseException) {
e.printStackTraceDebug()
null
}
suspend fun createTemp(context: Context): BackupZipOutput = runInterruptible(Dispatchers.IO) {
val dir = context.run {
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
}
dir.mkdirs()
BackupZipOutput(File(dir, generateFileName(context)))
}
}
dir.mkdirs()
val filename = buildString {
append(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))
append('_')
append(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
append(".bk.zip")
}
BackupZipOutput(File(dir, filename))
}

View File

@@ -27,6 +27,10 @@ class CompositeResult {
}
}
operator fun plusAssign(error: Throwable) {
errors.add(error)
}
operator fun plusAssign(other: CompositeResult) {
this.successCount += other.successCount
this.errors += other.errors

View File

@@ -0,0 +1,91 @@
package org.koitharu.kotatsu.core.backup
import android.content.Context
import android.net.Uri
import androidx.annotation.CheckResult
import androidx.documentfile.provider.DocumentFile
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.buffer
import okio.sink
import okio.source
import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File
import javax.inject.Inject
class ExternalBackupStorage @Inject constructor(
@ApplicationContext private val context: Context,
private val settings: AppSettings,
) {
suspend fun list(): List<BackupFile> = runInterruptible(Dispatchers.IO) {
getRootOrThrow().listFiles().mapNotNull {
if (it.isFile && it.canRead()) {
BackupFile(
uri = it.uri,
dateTime = it.name?.let { fileName ->
BackupZipOutput.parseBackupDateTime(fileName)
} ?: return@mapNotNull null,
)
} else {
null
}
}.sortedDescending()
}
suspend fun listOrNull() = runCatchingCancellable {
list()
}.onFailure { e ->
e.printStackTraceDebug()
}.getOrNull()
suspend fun put(file: File): Uri = runInterruptible(Dispatchers.IO) {
val out = checkNotNull(getRootOrThrow().createFile("application/zip", file.nameWithoutExtension)) {
"Cannot create target backup file"
}
checkNotNull(context.contentResolver.openOutputStream(out.uri, "wt")).sink().use { sink ->
file.source().buffer().use { src ->
src.readAll(sink)
}
}
out.uri
}
@CheckResult
suspend fun delete(victim: BackupFile) = runInterruptible(Dispatchers.IO) {
val df = DocumentFile.fromSingleUri(context, victim.uri)
df != null && df.delete()
}
suspend fun getLastBackupDate() = listOrNull()?.maxOfOrNull { it.dateTime }
suspend fun trim(maxCount: Int): Boolean {
if (maxCount == Int.MAX_VALUE) {
return false
}
val list = listOrNull()
if (list == null || list.size <= maxCount) {
return false
}
var result = false
for (i in maxCount until list.size) {
if (delete(list[i])) {
result = true
}
}
return result
}
@Blocking
private fun getRootOrThrow(): DocumentFile {
val uri = checkNotNull(settings.periodicalBackupDirectory) {
"Backup directory is not specified"
}
val root = DocumentFile.fromTreeUri(context, uri)
return checkNotNull(root) { "Cannot obtain DocumentFile from $uri" }
}
}

View File

@@ -28,15 +28,16 @@ class JsonDeserializer(private val json: JSONObject) {
fun toMangaEntity() = MangaEntity(
id = json.getLong("id"),
title = json.getString("title"),
altTitle = json.getStringOrNull("alt_title"),
altTitles = json.getStringOrNull("alt_title"),
url = json.getString("url"),
publicUrl = json.getStringOrNull("public_url").orEmpty(),
rating = json.getDouble("rating").toFloat(),
isNsfw = json.getBooleanOrDefault("nsfw", false),
contentRating = json.getStringOrNull("content_rating"),
coverUrl = json.getString("cover_url"),
largeCoverUrl = json.getStringOrNull("large_cover_url"),
state = json.getStringOrNull("state"),
author = json.getStringOrNull("author"),
authors = json.getStringOrNull("author"),
source = json.getString("source"),
)

View File

@@ -58,15 +58,16 @@ class JsonSerializer private constructor(private val json: JSONObject) {
JSONObject().apply {
put("id", e.id)
put("title", e.title)
put("alt_title", e.altTitle)
put("alt_title", e.altTitles)
put("url", e.url)
put("public_url", e.publicUrl)
put("rating", e.rating)
put("nsfw", e.isNsfw)
put("content_rating", e.contentRating)
put("cover_url", e.coverUrl)
put("large_cover_url", e.largeCoverUrl)
put("state", e.state)
put("author", e.author)
put("author", e.authors)
put("source", e.source)
},
)

View File

@@ -0,0 +1,93 @@
package org.koitharu.kotatsu.core.backup
import android.content.Context
import androidx.annotation.CheckResult
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.Response
import okhttp3.internal.closeQuietly
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.parseJson
import java.io.File
import javax.inject.Inject
class TelegramBackupUploader @Inject constructor(
private val settings: AppSettings,
@BaseHttpClient private val client: OkHttpClient,
@ApplicationContext private val context: Context,
) {
private val botToken = context.getString(R.string.tg_backup_bot_token)
suspend fun uploadBackup(file: File) {
val requestBody = file.asRequestBody("application/zip".toMediaTypeOrNull())
val multipartBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("chat_id", requireChatId())
.addFormDataPart("document", file.name, requestBody)
.build()
val request = Request.Builder()
.url(urlOf("sendDocument").build())
.post(multipartBody)
.build()
client.newCall(request).await().consume()
}
suspend fun sendTestMessage() {
val request = Request.Builder()
.url(urlOf("getMe").build())
.build()
client.newCall(request).await().consume()
sendMessage(context.getString(R.string.backup_tg_echo))
}
@CheckResult
fun openBotInApp(router: AppRouter): Boolean {
val botUsername = context.getString(R.string.tg_backup_bot_name)
return router.openExternalBrowser("tg://resolve?domain=$botUsername") ||
router.openExternalBrowser("https://t.me/$botUsername")
}
private suspend fun sendMessage(message: String) {
val url = urlOf("sendMessage")
.addQueryParameter("chat_id", requireChatId())
.addQueryParameter("text", message)
.build()
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).await().consume()
}
private fun requireChatId() = checkNotNull(settings.backupTelegramChatId) {
"Telegram chat ID not set in settings"
}
private fun Response.consume() {
if (isSuccessful) {
closeQuietly()
return
}
val jo = parseJson()
if (!jo.getBooleanOrDefault("ok", true)) {
throw RuntimeException(jo.getStringOrNull("description"))
}
}
private fun urlOf(method: String) = HttpUrl.Builder()
.scheme("https")
.host("api.telegram.org")
.addPathSegment("bot$botToken")
.addPathSegment(method)
}

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.cache
import androidx.collection.LruCache
import org.koitharu.kotatsu.core.util.SynchronizedSieveCache
import org.koitharu.kotatsu.parsers.model.MangaSource
import java.util.concurrent.TimeUnit
import org.koitharu.kotatsu.core.cache.MemoryContentCache.Key as CacheKey
@@ -8,11 +9,9 @@ class ExpiringLruCache<T>(
val maxSize: Int,
private val lifetime: Long,
private val timeUnit: TimeUnit,
) : Iterable<CacheKey> {
) {
private val cache = LruCache<CacheKey, ExpiringValue<T>>(maxSize)
override fun iterator(): Iterator<CacheKey> = cache.snapshot().keys.iterator()
private val cache = SynchronizedSieveCache<CacheKey, ExpiringValue<T>>(maxSize)
operator fun get(key: CacheKey): T? {
val value = cache[key] ?: return null
@@ -23,7 +22,8 @@ class ExpiringLruCache<T>(
}
operator fun set(key: CacheKey, value: T) {
cache.put(key, ExpiringValue(value, lifetime, timeUnit))
val value = ExpiringValue(value, lifetime, timeUnit)
cache.put(key, value)
}
fun clear() {
@@ -37,4 +37,8 @@ class ExpiringLruCache<T>(
fun remove(key: CacheKey) {
cache.remove(key)
}
fun removeAll(source: MangaSource) {
cache.removeIf { key, _ -> key.source == source }
}
}

View File

@@ -81,11 +81,7 @@ class MemoryContentCache @Inject constructor(application: Application) : Compone
}
private fun clearCache(cache: ExpiringLruCache<*>, source: MangaSource) {
cache.forEach { key ->
if (key.source == source) {
cache.remove(key)
}
}
cache.removeAll(source)
}
data class Key(

View File

@@ -12,11 +12,13 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.bookmarks.data.BookmarksDao
import org.koitharu.kotatsu.core.db.dao.ChaptersDao
import org.koitharu.kotatsu.core.db.dao.MangaDao
import org.koitharu.kotatsu.core.db.dao.MangaSourcesDao
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.ChapterEntity
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
@@ -35,6 +37,10 @@ import org.koitharu.kotatsu.core.db.migrations.Migration19To20
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
import org.koitharu.kotatsu.core.db.migrations.Migration20To21
import org.koitharu.kotatsu.core.db.migrations.Migration21To22
import org.koitharu.kotatsu.core.db.migrations.Migration22To23
import org.koitharu.kotatsu.core.db.migrations.Migration23To24
import org.koitharu.kotatsu.core.db.migrations.Migration24To23
import org.koitharu.kotatsu.core.db.migrations.Migration24To25
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
@@ -50,6 +56,8 @@ 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.local.data.index.LocalMangaIndexDao
import org.koitharu.kotatsu.local.data.index.LocalMangaIndexEntity
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingDao
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
import org.koitharu.kotatsu.stats.data.StatsDao
@@ -60,14 +68,14 @@ 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 = 22
const val DATABASE_VERSION = 25
@Database(
entities = [
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class,
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class,
ScrobblingEntity::class, MangaSourceEntity::class, StatsEntity::class,
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class, ChapterEntity::class,
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, TrackEntity::class,
TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class, ScrobblingEntity::class,
MangaSourceEntity::class, StatsEntity::class, LocalMangaIndexEntity::class,
],
version = DATABASE_VERSION,
)
@@ -98,6 +106,10 @@ abstract class MangaDatabase : RoomDatabase() {
abstract fun getSourcesDao(): MangaSourcesDao
abstract fun getStatsDao(): StatsDao
abstract fun getLocalMangaIndexDao(): LocalMangaIndexDao
abstract fun getChaptersDao(): ChaptersDao
}
fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
@@ -122,6 +134,10 @@ fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
Migration19To20(),
Migration20To21(),
Migration21To22(),
Migration22To23(),
Migration23To24(),
Migration24To23(),
Migration24To25(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room

View File

@@ -0,0 +1,113 @@
package org.koitharu.kotatsu.core.db
import androidx.sqlite.db.SimpleSQLiteQuery
import org.koitharu.kotatsu.list.domain.ListFilterOption
import java.util.LinkedList
class MangaQueryBuilder(
private val table: String,
private val conditionCallback: ConditionCallback
) {
private var filterOptions: Collection<ListFilterOption> = emptyList()
private var whereConditions = LinkedList<String>()
private var orderBy: String? = null
private var groupBy: String? = null
private var extraJoins: String? = null
private var limit: Int = 0
fun filters(options: Collection<ListFilterOption>) = apply {
filterOptions = options
}
fun where(condition: String) = apply {
whereConditions.add(condition)
}
fun orderBy(orderBy: String?) = apply {
this@MangaQueryBuilder.orderBy = orderBy
}
fun groupBy(groupBy: String?) = apply {
this@MangaQueryBuilder.groupBy = groupBy
}
fun limit(limit: Int) = apply {
this@MangaQueryBuilder.limit = limit
}
fun join(join: String?) = apply {
extraJoins = join
}
fun build() = buildString {
append("SELECT * FROM ")
append(table)
extraJoins?.let {
append(' ')
append(it)
}
if (whereConditions.isNotEmpty()) {
whereConditions.joinTo(
buffer = this,
prefix = " WHERE ",
separator = " AND ",
)
}
if (filterOptions.isNotEmpty()) {
if (whereConditions.isEmpty()) {
append(" WHERE")
} else {
append(" AND")
}
var isFirst = true
val groupedOptions = filterOptions.groupBy { it.groupKey }
for ((_, group) in groupedOptions) {
if (group.isEmpty()) {
continue
}
if (isFirst) {
isFirst = false
append(' ')
} else {
append(" AND ")
}
if (group.size > 1) {
group.joinTo(
buffer = this,
separator = " OR ",
prefix = "(",
postfix = ")",
transform = ::getConditionOrThrow,
)
} else {
append(getConditionOrThrow(group.single()))
}
}
}
groupBy?.let {
append(" GROUP BY ")
append(it)
}
orderBy?.let {
append(" ORDER BY ")
append(it)
}
if (limit > 0) {
append(" LIMIT ")
append(limit)
}
}.let { SimpleSQLiteQuery(it) }
private fun getConditionOrThrow(option: ListFilterOption): String = when (option) {
is ListFilterOption.Inverted -> "NOT(${getConditionOrThrow(option.option)})"
else -> requireNotNull(conditionCallback.getCondition(option)) {
"Unsupported filter option $option"
}
}
fun interface ConditionCallback {
fun getCondition(option: ListFilterOption): String?
}
}

View File

@@ -7,3 +7,4 @@ const val TABLE_FAVOURITE_CATEGORIES = "favourite_categories"
const val TABLE_HISTORY = "history"
const val TABLE_MANGA_TAGS = "manga_tags"
const val TABLE_SOURCES = "sources"
const val TABLE_CHAPTERS = "chapters"

View File

@@ -0,0 +1,30 @@
package org.koitharu.kotatsu.core.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import org.koitharu.kotatsu.core.db.entity.ChapterEntity
@Dao
abstract class ChaptersDao {
@Query("SELECT * FROM chapters WHERE manga_id = :mangaId ORDER BY `index` ASC")
abstract suspend fun findAll(mangaId: Long): List<ChapterEntity>
@Query("DELETE FROM chapters WHERE manga_id = :mangaId")
abstract suspend fun deleteAll(mangaId: Long)
@Query("DELETE FROM chapters WHERE manga_id NOT IN (SELECT manga_id FROM history WHERE deleted_at = 0) AND manga_id NOT IN (SELECT manga_id FROM favourites WHERE deleted_at = 0)")
abstract suspend fun gc()
@Transaction
open suspend fun replaceAll(mangaId: Long, entities: Collection<ChapterEntity>) {
deleteAll(mangaId)
insert(entities)
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun insert(entities: Collection<ChapterEntity>)
}

View File

@@ -20,6 +20,9 @@ abstract class MangaDao {
@Query("SELECT * FROM manga WHERE manga_id = :id")
abstract suspend fun find(id: Long): MangaWithTags?
@Query("SELECT EXISTS(SELECT * FROM manga WHERE manga_id = :id)")
abstract suspend operator fun contains(id: Long): Boolean
@Transaction
@Query("SELECT * FROM manga WHERE public_url = :publicUrl")
abstract suspend fun findByPublicUrl(publicUrl: String): MangaWithTags?
@@ -55,6 +58,19 @@ abstract class MangaDao {
@Delete
abstract suspend fun delete(subjects: Collection<MangaEntity>)
@Query(
"""
DELETE FROM manga WHERE NOT EXISTS(SELECT * FROM history WHERE history.manga_id == manga.manga_id)
AND NOT EXISTS(SELECT * FROM favourites WHERE favourites.manga_id == manga.manga_id)
AND NOT EXISTS(SELECT * FROM bookmarks WHERE bookmarks.manga_id == manga.manga_id)
AND NOT EXISTS(SELECT * FROM suggestions WHERE suggestions.manga_id == manga.manga_id)
AND NOT EXISTS(SELECT * FROM scrobblings WHERE scrobblings.manga_id == manga.manga_id)
AND NOT EXISTS(SELECT * FROM local_index WHERE local_index.manga_id == manga.manga_id)
AND manga.manga_id NOT IN (:idsToKeep)
""",
)
abstract suspend fun cleanup(idsToKeep: Set<Long>)
@Transaction
open suspend fun upsert(manga: MangaEntity, tags: Iterable<TagEntity>? = null) {
upsert(manga)

View File

@@ -10,7 +10,6 @@ import androidx.room.Upsert
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
@@ -61,21 +60,11 @@ abstract class MangaSourcesDao {
@Query("SELECT * FROM sources WHERE pinned = 1")
abstract suspend fun findAllPinned(): List<MangaSourceEntity>
fun observeEnabled(order: SourcesSortOrder): Flow<List<MangaSourceEntity>> {
val orderBy = getOrderBy(order)
fun observeAll(enabledOnly: Boolean, order: SourcesSortOrder): Flow<List<MangaSourceEntity>> =
observeImpl(getQuery(enabledOnly, order))
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY pinned DESC, $orderBy")
return observeImpl(query)
}
suspend fun findAllEnabled(order: SourcesSortOrder): List<MangaSourceEntity> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY pinned DESC, $orderBy")
return findAllImpl(query)
}
suspend fun findAll(enabledOnly: Boolean, order: SourcesSortOrder): List<MangaSourceEntity> =
findAllImpl(getQuery(enabledOnly, order))
@Transaction
open suspend fun setEnabled(source: String, isEnabled: Boolean) {
@@ -101,6 +90,17 @@ abstract class MangaSourcesDao {
@RawQuery
protected abstract suspend fun findAllImpl(query: SupportSQLiteQuery): List<MangaSourceEntity>
private fun getQuery(enabledOnly: Boolean, order: SourcesSortOrder) = SimpleSQLiteQuery(
buildString {
append("SELECT * FROM sources ")
if (enabledOnly) {
append("WHERE enabled = 1 ")
}
append("ORDER BY pinned DESC, ")
append(getOrderBy(order))
},
)
private fun getOrderBy(order: SourcesSortOrder) = when (order) {
SourcesSortOrder.ALPHABETIC -> "source ASC"
SourcesSortOrder.POPULARITY -> "(SELECT COUNT(*) FROM manga WHERE source = sources.source) DESC"

View File

@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.core.db.dao
import androidx.room.*
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
@@ -13,6 +15,9 @@ abstract class PreferencesDao {
@Query("SELECT * FROM preferences WHERE manga_id = :mangaId")
abstract fun observe(mangaId: Long): Flow<MangaPrefsEntity?>
@Query("UPDATE preferences SET cf_brightness = 0, cf_contrast = 0, cf_invert = 0, cf_grayscale = 0")
abstract suspend fun resetColorFilters()
@Upsert
abstract suspend fun upsert(pref: MangaPrefsEntity)
}

View File

@@ -4,36 +4,59 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.MangaQueryBuilder
import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TrackLogWithManga
@Dao
interface TrackLogsDao {
abstract class TrackLogsDao : MangaQueryBuilder.ConditionCallback {
@Transaction
@Query("SELECT * FROM track_logs ORDER BY created_at DESC LIMIT :limit OFFSET 0")
fun observeAll(limit: Int): Flow<List<TrackLogWithManga>>
fun observeAll(
limit: Int,
filterOptions: Set<ListFilterOption>,
): Flow<List<TrackLogWithManga>> = observeAllImpl(
MangaQueryBuilder("track_logs", this)
.filters(filterOptions)
.limit(limit)
.orderBy("created_at DESC")
.build(),
)
@Query("SELECT COUNT(*) FROM track_logs WHERE unread = 1")
fun observeUnreadCount(): Flow<Int>
abstract fun observeUnreadCount(): Flow<Int>
@Query("DELETE FROM track_logs")
suspend fun clear()
abstract suspend fun clear()
@Query("UPDATE track_logs SET unread = 0 WHERE id = :id")
suspend fun markAsRead(id: Long)
abstract suspend fun markAsRead(id: Long)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(entity: TrackLogEntity): Long
abstract suspend fun insert(entity: TrackLogEntity): Long
@Query("DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)")
suspend fun gc()
abstract suspend fun gc()
@Query("DELETE FROM track_logs WHERE id IN (SELECT id FROM track_logs ORDER BY created_at DESC LIMIT 0 OFFSET :size)")
suspend fun trim(size: Int)
abstract suspend fun trim(size: Int)
@Query("SELECT COUNT(*) FROM track_logs")
suspend fun count(): Int
abstract suspend fun count(): Int
@Transaction
@RawQuery(observedEntities = [TrackLogEntity::class])
protected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<TrackLogWithManga>>
override fun getCondition(option: ListFilterOption): String? = when (option) {
ListFilterOption.Macro.FAVORITE -> "EXISTS(SELECT * FROM favourites WHERE favourites.manga_id = track_logs.manga_id)"
is ListFilterOption.Favorite -> "EXISTS(SELECT * FROM favourites WHERE favourites.manga_id = track_logs.manga_id AND favourites.category_id = ${option.category.id})"
is ListFilterOption.Tag -> "EXISTS(SELECT * FROM manga_tags WHERE manga_tags.manga_id = track_logs.manga_id AND tag_id = ${option.tagId})"
ListFilterOption.Macro.NSFW -> "(SELECT nsfw FROM manga WHERE manga.manga_id = track_logs.manga_id) = 1"
else -> null
}
}

View File

@@ -0,0 +1,32 @@
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_CHAPTERS
@Entity(
tableName = TABLE_CHAPTERS,
primaryKeys = ["manga_id", "chapter_id"],
foreignKeys = [
ForeignKey(
entity = MangaEntity::class,
parentColumns = ["manga_id"],
childColumns = ["manga_id"],
onDelete = ForeignKey.CASCADE,
),
],
)
data class ChapterEntity(
@ColumnInfo(name = "chapter_id") val chapterId: Long,
@ColumnInfo(name = "manga_id") val mangaId: Long,
@ColumnInfo(name = "name") val title: String,
@ColumnInfo(name = "number") val number: Float,
@ColumnInfo(name = "volume") val volume: Int,
@ColumnInfo(name = "url") val url: String,
@ColumnInfo(name = "scanlator") val scanlator: String?,
@ColumnInfo(name = "upload_date") val uploadDate: Long,
@ColumnInfo(name = "branch") val branch: String?,
@ColumnInfo(name = "source") val source: String,
@ColumnInfo(name = "index") val index: Int,
)

View File

@@ -1,14 +1,20 @@
package org.koitharu.kotatsu.core.db.entity
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.util.ext.longHashCode
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.longHashCode
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.parsers.util.toArraySet
import org.koitharu.kotatsu.parsers.util.toTitleCase
private const val VALUES_DIVIDER = '\n'
// Entity to model
fun TagEntity.toMangaTag() = MangaTag(
@@ -21,23 +27,41 @@ fun Collection<TagEntity>.toMangaTags() = mapToSet(TagEntity::toMangaTag)
fun Collection<TagEntity>.toMangaTagsList() = map(TagEntity::toMangaTag)
fun MangaEntity.toManga(tags: Set<MangaTag>) = Manga(
fun MangaEntity.toManga(tags: Set<MangaTag>, chapters: List<ChapterEntity>?) = Manga(
id = this.id,
title = this.title,
altTitle = this.altTitle,
altTitles = this.altTitles?.split(VALUES_DIVIDER)?.toArraySet().orEmpty(),
state = this.state?.let { MangaState(it) },
rating = this.rating,
isNsfw = this.isNsfw,
contentRating = ContentRating(this.contentRating)
?: if (isNsfw) ContentRating.ADULT else null,
url = this.url,
publicUrl = this.publicUrl,
coverUrl = this.coverUrl,
largeCoverUrl = this.largeCoverUrl,
author = this.author,
authors = this.authors?.split(VALUES_DIVIDER)?.toArraySet().orEmpty(),
source = MangaSource(this.source),
tags = tags,
chapters = chapters?.toMangaChapters(),
)
fun MangaWithTags.toManga() = manga.toManga(tags.toMangaTags())
fun MangaWithTags.toManga(chapters: List<ChapterEntity>? = null) = manga.toManga(tags.toMangaTags(), chapters)
fun Collection<MangaWithTags>.toMangaList() = map { it.toManga() }
fun ChapterEntity.toMangaChapter() = MangaChapter(
id = chapterId,
title = title.nullIfEmpty(),
number = number,
volume = volume,
url = url,
scanlator = scanlator,
uploadDate = uploadDate,
branch = branch,
source = MangaSource(source),
)
fun Collection<ChapterEntity>.toMangaChapters() = map { it.toMangaChapter() }
// Model to entity
@@ -47,13 +71,14 @@ fun Manga.toEntity() = MangaEntity(
publicUrl = publicUrl,
source = source.name,
largeCoverUrl = largeCoverUrl,
coverUrl = coverUrl,
altTitle = altTitle,
coverUrl = coverUrl.orEmpty(),
altTitles = altTitles.joinToString(VALUES_DIVIDER.toString()),
rating = rating,
isNsfw = isNsfw,
contentRating = contentRating?.name,
state = state?.name,
title = title,
author = author,
authors = authors.joinToString(VALUES_DIVIDER.toString()),
)
fun MangaTag.toEntity() = TagEntity(
@@ -65,6 +90,22 @@ fun MangaTag.toEntity() = TagEntity(
fun Collection<MangaTag>.toEntities() = map(MangaTag::toEntity)
fun Iterable<IndexedValue<MangaChapter>>.toEntities(mangaId: Long) = map { (index, chapter) ->
ChapterEntity(
chapterId = chapter.id,
mangaId = mangaId,
title = chapter.title.orEmpty(),
number = chapter.number,
volume = chapter.volume,
url = chapter.url,
scanlator = chapter.scanlator,
uploadDate = chapter.uploadDate,
branch = chapter.branch,
source = chapter.source.name,
index = index,
)
}
// Other
fun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching {
@@ -74,3 +115,7 @@ fun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching {
fun MangaState(name: String): MangaState? = runCatching {
MangaState.valueOf(name)
}.getOrNull()
fun ContentRating(name: String?): ContentRating? = runCatching {
ContentRating.valueOf(name ?: return@runCatching null)
}.getOrNull()

View File

@@ -10,14 +10,15 @@ data class MangaEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val id: Long,
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "alt_title") val altTitle: String?,
@ColumnInfo(name = "alt_title") val altTitles: String?,
@ColumnInfo(name = "url") val url: String,
@ColumnInfo(name = "public_url") val publicUrl: String,
@ColumnInfo(name = "rating") val rating: Float, // normalized value [0..1] or -1
@ColumnInfo(name = "nsfw") val isNsfw: Boolean,
@ColumnInfo(name = "content_rating") val contentRating: String?,
@ColumnInfo(name = "cover_url") val coverUrl: String,
@ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?,
@ColumnInfo(name = "state") val state: String?,
@ColumnInfo(name = "author") val author: String?,
@ColumnInfo(name = "author") val authors: String?,
@ColumnInfo(name = "source") val source: String,
)

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration22To23 : Migration(22, 23) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `local_index` (`manga_id` INTEGER NOT NULL, `path` TEXT NOT NULL, PRIMARY KEY(`manga_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
}
}

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration23To24 : Migration(23, 24) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `chapters` (`chapter_id` INTEGER NOT NULL, `manga_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `number` REAL NOT NULL, `volume` INTEGER NOT NULL, `url` TEXT NOT NULL, `scanlator` TEXT, `upload_date` INTEGER NOT NULL, `branch` TEXT, `source` TEXT NOT NULL, `index` INTEGER NOT NULL, PRIMARY KEY(`manga_id`, `chapter_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
}
}

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration24To23 : Migration(24, 23) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS `chapters`")
}
}

View File

@@ -0,0 +1,12 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration24To25 : Migration(24, 25) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE manga ADD COLUMN content_rating TEXT DEFAULT NULL")
db.execSQL("UPDATE manga SET content_rating = 'ADULT' WHERE nsfw = 1")
}
}

View File

@@ -1,3 +1,5 @@
package org.koitharu.kotatsu.core.exceptions
class CaughtException(cause: Throwable, override val message: String?) : RuntimeException(cause)
class CaughtException(
override val cause: Throwable
) : RuntimeException("${cause.javaClass.simpleName}(${cause.message})", cause)

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.exceptions
import okhttp3.Headers
import okio.IOException
import org.koitharu.kotatsu.parsers.model.MangaSource

View File

@@ -3,5 +3,5 @@ package org.koitharu.kotatsu.core.exceptions
import okio.IOException
class NoDataReceivedException(
private val url: String,
val url: String,
) : IOException("No data has been received from $url")

View File

@@ -0,0 +1,5 @@
package org.koitharu.kotatsu.core.exceptions
import java.net.ProtocolException
class ProxyConfigException : ProtocolException("Wrong proxy configuration")

View File

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

View File

@@ -0,0 +1,5 @@
package org.koitharu.kotatsu.core.exceptions
import okio.IOException
class WrapperIOException(override val cause: Exception) : IOException(cause)

View File

@@ -6,8 +6,8 @@ import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.parsers.exception.ParseException
class DialogErrorObserver(
@@ -31,10 +31,10 @@ class DialogErrorObserver(
if (canResolve(value)) {
dialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(value), listener)
} else if (value is ParseException) {
val fm = fragmentManager
if (fm != null) {
val router = router()
if (router != null && value.isSerializable()) {
dialogBuilder.setPositiveButton(R.string.details) { _, _ ->
ErrorDetailsDialog.show(fm, value, value.url)
router.showErrorDialog(value)
}
}
}

View File

@@ -4,6 +4,7 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
@@ -11,6 +12,7 @@ import androidx.lifecycle.coroutineScope
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
@@ -33,6 +35,8 @@ abstract class ErrorObserver(
return resolver != null && ExceptionResolver.canResolve(error)
}
protected fun router() = fragment?.router ?: (activity as? FragmentActivity)?.router
private fun isAlive(): Boolean {
return when {
fragment != null -> fragment.view != null
@@ -44,7 +48,7 @@ abstract class ErrorObserver(
protected fun resolve(error: Throwable) {
if (isAlive()) {
lifecycleScope.launch {
val isResolved = resolver?.resolve(error) ?: false
val isResolved = resolver?.resolve(error) == true
if (isActive) {
onResolved?.accept(isResolved)
}

View File

@@ -2,67 +2,55 @@ package org.koitharu.kotatsu.core.exceptions.resolve
import android.content.Context
import android.widget.Toast
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultCaller
import androidx.annotation.StringRes
import androidx.collection.MutableScatterMap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.EntryPointAccessors
import androidx.fragment.app.FragmentManager
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseActivity.BaseActivityEntryPoint
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.TaggedActivityResult
import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.core.util.ext.restartApplication
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException
import org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import java.security.cert.CertPathValidatorException
import javax.inject.Provider
import javax.net.ssl.SSLException
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
class ExceptionResolver @AssistedInject constructor(
@Assisted private val host: Host,
private val settings: AppSettings,
private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,
) {
private val continuations = MutableScatterMap<String, Continuation<Boolean>>(1)
private val activity: FragmentActivity?
private val fragment: Fragment?
private val sourceAuthContract: ActivityResultLauncher<MangaSource>
private val cloudflareContract: ActivityResultLauncher<CloudFlareProtectedException>
val context: Context?
get() = activity ?: fragment?.context
constructor(activity: FragmentActivity) {
this.activity = activity
fragment = null
sourceAuthContract = activity.registerForActivityResult(SourceAuthActivity.Contract(), this)
cloudflareContract = activity.registerForActivityResult(CloudFlareActivity.Contract(), this)
private val sourceAuthContract = host.registerForActivityResult(SourceAuthActivity.Contract()) {
handleActivityResult(SourceAuthActivity.TAG, it)
}
private val cloudflareContract = host.registerForActivityResult(CloudFlareActivity.Contract()) {
handleActivityResult(CloudFlareActivity.TAG, it)
}
constructor(fragment: Fragment) {
this.fragment = fragment
activity = null
sourceAuthContract = fragment.registerForActivityResult(SourceAuthActivity.Contract(), this)
cloudflareContract = fragment.registerForActivityResult(CloudFlareActivity.Contract(), this)
}
override fun onActivityResult(result: TaggedActivityResult) {
continuations.remove(result.tag)?.resume(result.isSuccess)
}
fun showDetails(e: Throwable, url: String?) {
ErrorDetailsDialog.show(getFragmentManager(), e, url)
fun showErrorDetails(e: Throwable, url: String? = null) {
host.router()?.showErrorDialog(e, url)
}
suspend fun resolve(e: Throwable): Boolean = when (e) {
@@ -74,6 +62,11 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
false
}
is ProxyConfigException -> {
host.router()?.openProxySettings()
false
}
is NotFoundException -> {
openInBrowser(e.url)
false
@@ -84,6 +77,18 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
false
}
is ScrobblerAuthRequiredException -> {
val authHelper = scrobblerAuthHelperProvider.get()
if (authHelper.isAuthorized(e.scrobbler)) {
true
} else {
host.withContext {
authHelper.startAuth(this, e.scrobbler).onFailure(::showErrorDetails)
}
false
}
}
else -> false
}
@@ -98,52 +103,73 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
}
private fun openInBrowser(url: String) {
context?.run {
startActivity(BrowserActivity.newIntent(this, url, null, null))
}
host.router()?.openBrowser(url, null, null)
}
private fun openAlternatives(manga: Manga) {
context?.run {
startActivity(AlternativesActivity.newIntent(this, manga))
}
host.router()?.openAlternatives(manga)
}
private fun handleActivityResult(tag: String, result: Boolean) {
continuations.remove(tag)?.resume(result)
}
private fun showSslErrorDialog() {
val ctx = context ?: return
val settings = getAppSettings(ctx)
val ctx = host.getContext() ?: return
if (settings.isSSLBypassEnabled) {
Toast.makeText(ctx, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
return
}
MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.ignore_ssl_errors)
.setMessage(R.string.ignore_ssl_errors_summary)
.setPositiveButton(R.string.apply) { _, _ ->
buildAlertDialog(ctx) {
setTitle(R.string.ignore_ssl_errors)
setMessage(R.string.ignore_ssl_errors_summary)
setPositiveButton(R.string.apply) { _, _ ->
settings.isSSLBypassEnabled = true
Toast.makeText(ctx, R.string.settings_apply_restart_required, Toast.LENGTH_SHORT).show()
ctx.findActivity()?.finishAffinity()
}.setNegativeButton(android.R.string.cancel, null)
.show()
Toast.makeText(ctx, R.string.settings_apply_restart_required, Toast.LENGTH_LONG).show()
ctx.restartApplication()
}
setNegativeButton(android.R.string.cancel, null)
}.show()
}
private fun getAppSettings(context: Context): AppSettings {
return EntryPointAccessors.fromApplication<BaseActivityEntryPoint>(context).settings
private inline fun Host.withContext(block: Context.() -> Unit) {
getContext()?.apply(block)
}
private fun getFragmentManager() = checkNotNull(fragment?.childFragmentManager ?: activity?.supportFragmentManager)
private fun Host.router(): AppRouter? = when (this) {
is FragmentActivity -> router
is Fragment -> router
else -> null
}
interface Host : ActivityResultCaller {
fun getChildFragmentManager(): FragmentManager
fun getContext(): Context?
}
@AssistedFactory
interface Factory {
fun create(host: Host): ExceptionResolver
}
companion object {
@StringRes
fun getResolveStringId(e: Throwable) = when (e) {
is CloudFlareProtectedException -> R.string.captcha_solve
is ScrobblerAuthRequiredException,
is AuthRequiredException -> R.string.sign_in
is NotFoundException -> if (e.url.isNotEmpty()) R.string.open_in_browser else 0
is UnsupportedSourceException -> if (e.manga != null) R.string.alternatives else 0
is SSLException,
is CertPathValidatorException -> R.string.fix
is ProxyConfigException -> R.string.settings
else -> 0
}

View File

@@ -5,8 +5,8 @@ import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -32,10 +32,10 @@ class SnackbarErrorObserver(
resolve(value)
}
} else if (value is ParseException) {
val fm = fragmentManager
if (fm != null) {
val router = router()
if (router != null && value.isSerializable()) {
snackbar.setAction(R.string.details) {
ErrorDetailsDialog.show(fm, value, value.url)
router.showErrorDialog(value)
}
}
}

View File

@@ -1,20 +1,31 @@
package org.koitharu.kotatsu.core.fs
import android.os.Build
import org.koitharu.kotatsu.core.util.iterator.CloseableIterator
import androidx.annotation.RequiresApi
import org.koitharu.kotatsu.core.util.CloseableSequence
import org.koitharu.kotatsu.core.util.iterator.MappingIterator
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
class FileSequence(private val dir: File) : Sequence<File> {
sealed interface FileSequence : CloseableSequence<File> {
override fun iterator(): Iterator<File> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val stream = Files.newDirectoryStream(dir.toPath())
CloseableIterator(MappingIterator(stream.iterator(), Path::toFile), stream)
} else {
dir.listFiles().orEmpty().iterator()
}
@RequiresApi(Build.VERSION_CODES.O)
class StreamImpl(dir: File) : FileSequence {
private val stream = Files.newDirectoryStream(dir.toPath())
override fun iterator(): Iterator<File> = MappingIterator(stream.iterator(), Path::toFile)
override fun close() = stream.close()
}
class ListImpl(dir: File) : FileSequence {
private val list = dir.listFiles().orEmpty()
override fun iterator(): Iterator<File> = list.iterator()
override fun close() = Unit
}
}

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.core.github
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -9,6 +11,7 @@ import okhttp3.Request
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.os.AppValidator
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -18,26 +21,37 @@ import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.parseJsonArray
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull
import javax.inject.Inject
import javax.inject.Singleton
private const val CONTENT_TYPE_APK = "application/vnd.android.package-archive"
private const val BUILD_TYPE_RELEASE = "release"
@Singleton
class AppUpdateRepository @Inject constructor(
private val appValidator: AppValidator,
private val settings: AppSettings,
@BaseHttpClient private val okHttp: OkHttpClient,
@ApplicationContext context: Context,
) {
private val availableUpdate = MutableStateFlow<AppVersion?>(null)
private val releasesUrl = buildString {
append("https://api.github.com/repos/")
append(context.getString(R.string.github_updates_repo))
append("/releases?page=1&per_page=10")
}
val isUpdateAvailable: Boolean
get() = availableUpdate.value != null
fun observeAvailableUpdate() = availableUpdate.asStateFlow()
suspend fun getAvailableVersions(): List<AppVersion> {
val request = Request.Builder()
.get()
.url("https://api.github.com/repos/KotatsuApp/Kotatsu/releases?page=1&per_page=10")
.url(releasesUrl)
val jsonArray = okHttp.newCall(request.build()).await().parseJsonArray()
return jsonArray.mapJSONNotNull { json ->
val asset = json.optJSONArray("assets")?.find { jo ->
@@ -74,8 +88,9 @@ class AppUpdateRepository @Inject constructor(
}.getOrNull()
}
fun isUpdateSupported(): Boolean {
return BuildConfig.DEBUG || appValidator.isOriginalApp
@Suppress("KotlinConstantConditions")
suspend fun isUpdateSupported(): Boolean {
return BuildConfig.BUILD_TYPE != BUILD_TYPE_RELEASE || appValidator.isOriginalApp.getOrNull() == true
}
suspend fun getCurrentVersionChangelog(): String? {

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.github
import java.util.*
import org.koitharu.kotatsu.parsers.util.digits
import java.util.Locale
data class VersionId(
val major: Int,
@@ -43,6 +44,16 @@ val VersionId.isStable: Boolean
get() = variantType.isEmpty()
fun VersionId(versionName: String): VersionId {
if (versionName.startsWith('n', ignoreCase = true)) {
// Nightly build
return VersionId(
major = 0,
minor = 0,
build = versionName.digits().toIntOrNull() ?: 0,
variantType = "n",
variantNumber = 0,
)
}
val parts = versionName.substringBeforeLast('-').split('.')
val variant = versionName.substringAfterLast('-', "")
return VersionId(

View File

@@ -0,0 +1,66 @@
package org.koitharu.kotatsu.core.image
import android.graphics.Bitmap
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.DecodeResult
import coil3.decode.Decoder
import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
import kotlinx.coroutines.runInterruptible
import org.aomedia.avif.android.AvifDecoder
import org.aomedia.avif.android.AvifDecoder.Info
import org.koitharu.kotatsu.core.util.ext.toByteBuffer
class AvifImageDecoder(
private val source: ImageSource,
private val options: Options,
) : Decoder {
override suspend fun decode(): DecodeResult = runInterruptible {
val bytes = source.source().use {
it.inputStream().toByteBuffer()
}
val info = Info()
if (!AvifDecoder.getInfo(bytes, bytes.remaining(), info)) {
throw ImageDecodeException(
null,
"avif",
"Requested to decode byte buffer which cannot be handled by AvifDecoder",
)
}
val config = if (info.depth == 8 || info.alphaPresent) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
val bitmap = Bitmap.createBitmap(info.width, info.height, config)
if (!AvifDecoder.decode(bytes, bytes.remaining(), bitmap)) {
bitmap.recycle()
throw ImageDecodeException(null, "avif")
}
DecodeResult(
image = bitmap.asImage(),
isSampled = false,
)
}
class Factory : Decoder.Factory {
override fun create(
result: SourceFetchResult,
options: Options,
imageLoader: ImageLoader
): Decoder? = if (isApplicable(result)) {
AvifImageDecoder(result.source, options)
} else {
null
}
override fun equals(other: Any?) = other is Factory
override fun hashCode() = javaClass.hashCode()
private fun isApplicable(result: SourceFetchResult): Boolean {
return result.mimeType == "image/avif"
}
}
}

View File

@@ -0,0 +1,102 @@
package org.koitharu.kotatsu.core.image
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.os.Build
import androidx.annotation.RequiresApi
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
import org.aomedia.avif.android.AvifDecoder
import org.aomedia.avif.android.AvifDecoder.Info
import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.core.util.MimeTypes
import org.koitharu.kotatsu.core.util.ext.MimeType
import org.koitharu.kotatsu.core.util.ext.toByteBuffer
import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File
import java.io.InputStream
import java.nio.ByteBuffer
object BitmapDecoderCompat {
private const val FORMAT_AVIF = "avif"
@Blocking
fun decode(file: File): Bitmap = when (val format = probeMimeType(file)?.subtype) {
FORMAT_AVIF -> file.inputStream().use { decodeAvif(it.toByteBuffer()) }
else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(file))
} else {
checkBitmapNotNull(BitmapFactory.decodeFile(file.absolutePath), format)
}
}
@Blocking
fun decode(stream: InputStream, type: MimeType?, isMutable: Boolean = false): Bitmap {
val format = type?.subtype
if (format == FORMAT_AVIF) {
return decodeAvif(stream.toByteBuffer())
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val opts = BitmapFactory.Options()
opts.inMutable = isMutable
return checkBitmapNotNull(BitmapFactory.decodeStream(stream, null, opts), format)
}
val byteBuffer = stream.toByteBuffer()
return if (AvifDecoder.isAvifImage(byteBuffer)) {
decodeAvif(byteBuffer)
} else {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(byteBuffer), DecoderConfigListener(isMutable))
}
}
@Blocking
fun probeMimeType(file: File): MimeType? {
return MimeTypes.probeMimeType(file) ?: detectBitmapType(file)
}
@Blocking
private fun detectBitmapType(file: File): MimeType? = runCatchingCancellable {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(file.path, options)?.recycle()
return options.outMimeType?.toMimeTypeOrNull()
}.getOrNull()
private fun checkBitmapNotNull(bitmap: Bitmap?, format: String?): Bitmap =
bitmap ?: throw ImageDecodeException(null, format)
private fun decodeAvif(bytes: ByteBuffer): Bitmap {
val info = Info()
if (!AvifDecoder.getInfo(bytes, bytes.remaining(), info)) {
throw ImageDecodeException(
null,
FORMAT_AVIF,
"Requested to decode byte buffer which cannot be handled by AvifDecoder",
)
}
val config = if (info.depth == 8 || info.alphaPresent) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
val bitmap = Bitmap.createBitmap(info.width, info.height, config)
if (!AvifDecoder.decode(bytes, bytes.remaining(), bitmap)) {
bitmap.recycle()
throw ImageDecodeException(null, FORMAT_AVIF)
}
return bitmap
}
@RequiresApi(Build.VERSION_CODES.P)
private class DecoderConfigListener(
private val isMutable: Boolean,
) : ImageDecoder.OnHeaderDecodedListener {
override fun onHeaderDecoded(
decoder: ImageDecoder,
info: ImageDecoder.ImageInfo,
source: ImageDecoder.Source
) {
decoder.isMutableRequired = isMutable
}
}
}

View File

@@ -0,0 +1,49 @@
package org.koitharu.kotatsu.core.image
import android.net.Uri
import coil3.ImageLoader
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.fetch.Fetcher
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import coil3.toAndroidUri
import kotlinx.coroutines.runInterruptible
import okio.Path.Companion.toPath
import okio.openZip
import org.koitharu.kotatsu.core.util.MimeTypes
import org.koitharu.kotatsu.core.util.ext.isZipUri
import coil3.Uri as CoilUri
class CbzFetcher(
private val uri: Uri,
private val options: Options,
) : Fetcher {
override suspend fun fetch() = runInterruptible {
val filePath = uri.schemeSpecificPart.toPath()
val entryName = requireNotNull(uri.fragment)
val fs = options.fileSystem.openZip(filePath)
SourceFetchResult(
source = ImageSource(entryName.toPath(), fs, closeable = fs),
mimeType = MimeTypes.getMimeTypeFromExtension(entryName)?.toString(),
dataSource = DataSource.DISK,
)
}
class Factory : Fetcher.Factory<CoilUri> {
override fun create(
data: CoilUri,
options: Options,
imageLoader: ImageLoader
): Fetcher? {
val androidUri = data.toAndroidUri()
return if (androidUri.isZipUri()) {
CbzFetcher(androidUri, options)
} else {
null
}
}
}
}

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.core.image
import coil3.intercept.Interceptor
import coil3.network.httpHeaders
import coil3.request.ImageResult
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.util.ext.mangaSourceKey
import org.koitharu.kotatsu.parsers.model.MangaParserSource
class MangaSourceHeaderInterceptor : Interceptor {
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val mangaSource = chain.request.extras[mangaSourceKey] as? MangaParserSource ?: return chain.proceed()
val request = chain.request
val newHeaders = request.httpHeaders.newBuilder()
.set(CommonHeaders.MANGA_SOURCE, mangaSource.name)
.build()
val newRequest = request.newBuilder()
.httpHeaders(newHeaders)
.build()
return chain.withRequest(newRequest).proceed()
}
}

View File

@@ -1,39 +1,39 @@
package org.koitharu.kotatsu.core.ui.image
package org.koitharu.kotatsu.core.image
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Rect
import android.os.Build
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.DecodeResult
import coil.decode.DecodeUtils
import coil.decode.Decoder
import coil.decode.ImageSource
import coil.fetch.SourceResult
import coil.request.Options
import coil.size.Dimension
import coil.size.Scale
import coil.size.Size
import coil.size.isOriginal
import coil.size.pxOrElse
import coil3.Extras
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.DecodeResult
import coil3.decode.DecodeUtils
import coil3.decode.Decoder
import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult
import coil3.getExtra
import coil3.request.Options
import coil3.request.allowRgb565
import coil3.request.bitmapConfig
import coil3.request.colorSpace
import coil3.request.premultipliedAlpha
import coil3.size.Dimension
import coil3.size.Precision
import coil3.size.Scale
import coil3.size.Size
import coil3.size.isOriginal
import coil3.size.pxOrElse
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlin.math.roundToInt
class RegionBitmapDecoder(
private val source: ImageSource,
private val options: Options,
private val parallelismLock: Semaphore,
) : Decoder {
override suspend fun decode() = parallelismLock.withPermit {
runInterruptible { BitmapFactory.Options().decode() }
}
private fun BitmapFactory.Options.decode(): DecodeResult {
override suspend fun decode(): DecodeResult = runInterruptible {
val regionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BitmapRegionDecoder.newInstance(source.source().inputStream())
} else {
@@ -41,13 +41,14 @@ class RegionBitmapDecoder(
BitmapRegionDecoder.newInstance(source.source().inputStream(), false)
}
checkNotNull(regionDecoder)
val bitmapOptions = BitmapFactory.Options()
try {
val rect = configureScale(regionDecoder.width, regionDecoder.height)
configureConfig()
val bitmap = regionDecoder.decodeRegion(rect, this)
val rect = bitmapOptions.configureScale(regionDecoder.width, regionDecoder.height)
bitmapOptions.configureConfig()
val bitmap = regionDecoder.decodeRegion(rect, bitmapOptions)
bitmap.density = options.context.resources.displayMetrics.densityDpi
return DecodeResult(
drawable = bitmap.toDrawable(options.context.resources),
DecodeResult(
image = bitmap.asImage(),
isSampled = true,
)
} finally {
@@ -55,29 +56,6 @@ class RegionBitmapDecoder(
}
}
private fun BitmapFactory.Options.configureConfig() {
var config = options.config
inMutable = false
if (Build.VERSION.SDK_INT >= 26 && options.colorSpace != null) {
inPreferredColorSpace = options.colorSpace
}
inPremultiplied = options.premultipliedAlpha
// Decode the image as RGB_565 as an optimization if allowed.
if (options.allowRgb565 && config == Bitmap.Config.ARGB_8888 && outMimeType == "image/jpeg") {
config = Bitmap.Config.RGB_565
}
// High color depth images must be decoded as either RGBA_F16 or HARDWARE.
if (Build.VERSION.SDK_INT >= 26 && outConfig == Bitmap.Config.RGBA_F16 && config != Bitmap.Config.HARDWARE) {
config = Bitmap.Config.RGBA_F16
}
inPreferredConfig = config
}
/** Compute and set the scaling properties for [BitmapFactory.Options]. */
private fun BitmapFactory.Options.configureScale(srcWidth: Int, srcHeight: Int): Rect {
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
@@ -91,7 +69,7 @@ class RegionBitmapDecoder(
} else {
Rect(0, 0, (srcHeight / dstRatio).toInt().coerceAtLeast(1), srcHeight)
}
val scroll = options.parameters.value(PARAM_SCROLL) ?: SCROLL_UNDEFINED
val scroll = options.getExtra(regionScrollKey)
if (scroll == SCROLL_UNDEFINED) {
rect.offsetTo(
(srcWidth - rect.width()) / 2,
@@ -123,7 +101,7 @@ class RegionBitmapDecoder(
)
// Only upscale the image if the options require an exact size.
if (options.allowInexactSize) {
if (options.precision == Precision.INEXACT) {
scale = scale.coerceAtMost(1.0)
}
@@ -142,19 +120,36 @@ class RegionBitmapDecoder(
return rect
}
class Factory(
maxParallelism: Int = DEFAULT_MAX_PARALLELISM,
) : Decoder.Factory {
private fun BitmapFactory.Options.configureConfig() {
var config = options.bitmapConfig
@Suppress("NEWER_VERSION_IN_SINCE_KOTLIN")
@SinceKotlin("999.9") // Only public in Java.
constructor() : this()
inMutable = false
private val parallelismLock = Semaphore(maxParallelism)
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder {
return RegionBitmapDecoder(result.source, options, parallelismLock)
if (Build.VERSION.SDK_INT >= 26 && options.colorSpace != null) {
inPreferredColorSpace = options.colorSpace
}
inPremultiplied = options.premultipliedAlpha
// Decode the image as RGB_565 as an optimization if allowed.
if (options.allowRgb565 && config == Bitmap.Config.ARGB_8888 && outMimeType == "image/jpeg") {
config = Bitmap.Config.RGB_565
}
// High color depth images must be decoded as either RGBA_F16 or HARDWARE.
if (Build.VERSION.SDK_INT >= 26 && outConfig == Bitmap.Config.RGBA_F16 && config != Bitmap.Config.HARDWARE) {
config = Bitmap.Config.RGBA_F16
}
inPreferredConfig = config
}
object Factory : Decoder.Factory {
override fun create(
result: SourceFetchResult,
options: Options,
imageLoader: ImageLoader
): Decoder = RegionBitmapDecoder(result.source, options)
override fun equals(other: Any?) = other is Factory
@@ -163,9 +158,8 @@ class RegionBitmapDecoder(
companion object {
const val PARAM_SCROLL = "scroll"
const val SCROLL_UNDEFINED = -1
private const val DEFAULT_MAX_PARALLELISM = 4
val regionScrollKey = Extras.Key(SCROLL_UNDEFINED)
private inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
return if (isOriginal) original() else width.toPx(scale)

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.core.io
import java.io.OutputStream
import java.util.Objects
class NullOutputStream : OutputStream() {
override fun write(b: Int) = Unit
override fun write(b: ByteArray, off: Int, len: Int) {
Objects.checkFromIndexSize(off, len, b.size)
}
}

View File

@@ -1,148 +0,0 @@
package org.koitharu.kotatsu.core.logs
import android.content.Context
import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.core.util.ext.subdir
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import java.io.File
import java.io.FileOutputStream
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
import java.util.concurrent.ConcurrentLinkedQueue
private const val DIR = "logs"
private const val FLUSH_DELAY = 2_000L
private const val MAX_SIZE_BYTES = 1024 * 1024 // 1 MB
class FileLogger(
context: Context,
private val settings: AppSettings,
name: String,
) {
val file by lazy {
val dir = context.getExternalFilesDir(DIR) ?: context.filesDir.subdir(DIR)
File(dir, "$name.log")
}
val isEnabled: Boolean
get() = settings.isLoggingEnabled
private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(Locale.ROOT)
private val buffer = ConcurrentLinkedQueue<String>()
private val mutex = Mutex()
private var flushJob: Job? = null
fun log(message: String, e: Throwable? = null) {
if (!isEnabled) {
return
}
val text = buildString {
append(dateTimeFormatter.format(LocalDateTime.now()))
append(": ")
if (e != null) {
append("E!")
}
append(message)
if (e != null) {
append(' ')
append(e.stackTraceToString())
appendLine()
}
}
buffer.add(text)
postFlush()
}
inline fun log(messageProducer: () -> String) {
if (isEnabled) {
log(messageProducer())
}
}
suspend fun flush() {
if (!isEnabled) {
return
}
flushJob?.cancelAndJoin()
flushImpl()
}
@WorkerThread
fun flushBlocking() {
if (!isEnabled) {
return
}
runBlockingSafe { flushJob?.cancelAndJoin() }
runBlockingSafe { flushImpl() }
}
private fun postFlush() {
if (flushJob?.isActive == true) {
return
}
flushJob = processLifecycleScope.launch(Dispatchers.Default) {
delay(FLUSH_DELAY)
runCatchingCancellable {
flushImpl()
}.onFailure {
it.printStackTraceDebug()
}
}
}
private suspend fun flushImpl() = withContext(NonCancellable) {
mutex.withLock {
if (buffer.isEmpty()) {
return@withContext
}
runInterruptible(Dispatchers.IO) {
if (file.length() > MAX_SIZE_BYTES) {
rotate()
}
FileOutputStream(file, true).use {
while (true) {
val message = buffer.poll() ?: break
it.write(message.toByteArray())
it.write('\n'.code)
}
it.flush()
}
}
}
}
@WorkerThread
private fun rotate() {
val length = file.length()
val bakFile = File(file.parentFile, file.name + ".bak")
file.renameTo(bakFile)
bakFile.inputStream().use { input ->
input.skip(length - MAX_SIZE_BYTES / 2)
file.outputStream().use { output ->
input.copyTo(output)
output.flush()
}
}
bakFile.delete()
}
private inline fun runBlockingSafe(crossinline block: suspend () -> Unit) = try {
runBlocking(NonCancellable) { block() }
} catch (_: InterruptedException) {
}
}

View File

@@ -1,11 +0,0 @@
package org.koitharu.kotatsu.core.logs
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class TrackerLogger
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class SyncLogger

View File

@@ -1,40 +0,0 @@
package org.koitharu.kotatsu.core.logs
import android.content.Context
import androidx.collection.arraySetOf
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ElementsIntoSet
import org.koitharu.kotatsu.core.prefs.AppSettings
@Module
@InstallIn(SingletonComponent::class)
object LoggersModule {
@Provides
@TrackerLogger
fun provideTrackerLogger(
@ApplicationContext context: Context,
settings: AppSettings,
) = FileLogger(context, settings, "tracker")
@Provides
@SyncLogger
fun provideSyncLogger(
@ApplicationContext context: Context,
settings: AppSettings,
) = FileLogger(context, settings, "sync")
@Provides
@ElementsIntoSet
fun provideAllLoggers(
@TrackerLogger trackerLogger: FileLogger,
@SyncLogger syncLogger: FileLogger,
): Set<@JvmSuppressWildcards FileLogger> = arraySetOf(
trackerLogger,
syncLogger,
)
}

View File

@@ -0,0 +1,32 @@
package org.koitharu.kotatsu.core.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.model.SortOrder
@Deprecated("")
enum class GenericSortOrder(
@StringRes val titleResId: Int,
val ascending: SortOrder,
val descending: SortOrder,
) {
UPDATED(R.string.updated, SortOrder.UPDATED_ASC, SortOrder.UPDATED),
RATING(R.string.by_rating, SortOrder.RATING_ASC, SortOrder.RATING),
POPULARITY(R.string.popularity, SortOrder.POPULARITY_ASC, SortOrder.POPULARITY),
DATE(R.string.by_date, SortOrder.NEWEST_ASC, SortOrder.NEWEST),
NAME(R.string.by_name, SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC),
;
operator fun get(direction: SortDirection): SortOrder = when (direction) {
SortDirection.ASC -> ascending
SortDirection.DESC -> descending
}
companion object {
fun of(order: SortOrder): GenericSortOrder = entries.first { e ->
e.ascending == order || e.descending == order
}
}
}

View File

@@ -1,18 +1,25 @@
package org.koitharu.kotatsu.core.model
import android.content.res.Resources
import android.net.Uri
import android.text.SpannableStringBuilder
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.collection.MutableObjectIntMap
import androidx.core.net.toUri
import androidx.core.os.LocaleListCompat
import androidx.core.text.buildSpannedString
import androidx.core.text.strikeThrough
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.iterator
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Demographic
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.formatSimple
import org.koitharu.kotatsu.parsers.util.findById
import org.koitharu.kotatsu.parsers.util.mapToSet
import com.google.android.material.R as materialR
@@ -24,8 +31,6 @@ fun Collection<Manga>.distinctById() = distinctBy { it.id }
@JvmName("chaptersIds")
fun Collection<MangaChapter>.ids() = mapToSet { it.id }
fun Collection<MangaChapter>.findById(id: Long) = find { x -> x.id == id }
fun Collection<ChapterListItem>.countChaptersByBranch(): Int {
if (size <= 1) {
return size
@@ -68,9 +73,16 @@ val ContentRating.titleResId: Int
ContentRating.ADULT -> R.string.rating_adult
}
fun Manga.findChapter(id: Long): MangaChapter? {
return chapters?.findById(id)
}
@get:StringRes
val Demographic.titleResId: Int
get() = when (this) {
Demographic.SHOUNEN -> R.string.demographic_shounen
Demographic.SHOUJO -> R.string.demographic_shoujo
Demographic.SEINEN -> R.string.demographic_seinen
Demographic.JOSEI -> R.string.demographic_josei
Demographic.KODOMO -> R.string.demographic_kodomo
Demographic.NONE -> R.string.none
}
fun Manga.getPreferredBranch(history: MangaHistory?): String? {
val ch = chapters
@@ -110,19 +122,17 @@ fun Manga.getPreferredBranch(history: MangaHistory?): String? {
val Manga.isLocal: Boolean
get() = source == LocalMangaSource
val Manga.isBroken: Boolean
get() = source == UnknownMangaSource
val Manga.appUrl: Uri
get() = Uri.parse("https://kotatsu.app/manga").buildUpon()
get() = "https://kotatsu.app/manga".toUri()
.buildUpon()
.appendQueryParameter("source", source.name)
.appendQueryParameter("name", title)
.appendQueryParameter("url", url)
.build()
fun MangaChapter.formatNumber(): String? = if (number > 0f) {
number.formatSimple()
} else {
null
}
fun Manga.chaptersCount(): Int {
if (chapters.isNullOrEmpty()) {
return 0
@@ -138,3 +148,47 @@ fun Manga.chaptersCount(): Int {
}
return max
}
fun MangaListFilter.getSummary() = buildSpannedString {
if (!query.isNullOrEmpty()) {
append(query)
if (tags.isNotEmpty() || tagsExclude.isNotEmpty()) {
append(' ')
append('(')
appendTagsSummary(this@getSummary)
append(')')
}
} else {
appendTagsSummary(this@getSummary)
}
}
private fun SpannableStringBuilder.appendTagsSummary(filter: MangaListFilter) {
filter.tags.joinTo(this) { it.title }
if (filter.tagsExclude.isNotEmpty()) {
strikeThrough {
filter.tagsExclude.joinTo(this) { it.title }
}
}
}
fun MangaChapter.getLocalizedTitle(resources: Resources, index: Int = -1): String {
title?.let {
if (it.isNotBlank()) {
return it
}
}
val num = numberString()
val vol = volumeString()
return when {
num != null && vol != null -> resources.getString(R.string.chapter_volume_number, vol, num)
num != null -> resources.getString(R.string.chapter_number, num)
index > 0 -> resources.getString(
R.string.chapters_time_pattern,
resources.getString(R.string.unnamed_chapter),
index.toString(),
)
else -> resources.getString(R.string.unnamed_chapter)
}
}

View File

@@ -12,4 +12,5 @@ data class MangaHistory(
val page: Int,
val scroll: Int,
val percent: Float,
val chaptersCount: Int,
) : Parcelable

View File

@@ -2,11 +2,16 @@ package org.koitharu.kotatsu.core.model
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.text.style.RelativeSizeSpan
import android.text.style.SuperscriptSpan
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.text.inSpans
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
@@ -43,6 +48,8 @@ fun MangaSource(name: String?): MangaSource {
return UnknownMangaSource
}
fun Collection<String>.toMangaSources() = map(::MangaSource)
fun MangaSource.isNsfw(): Boolean = when (this) {
is MangaSourceInfo -> mangaSource.isNsfw()
is MangaParserSource -> contentType == ContentType.HENTAI
@@ -56,13 +63,26 @@ val ContentType.titleResId
ContentType.HENTAI -> R.string.content_type_hentai
ContentType.COMICS -> R.string.content_type_comics
ContentType.OTHER -> R.string.content_type_other
ContentType.MANHWA -> R.string.content_type_manhwa
ContentType.MANHUA -> R.string.content_type_manhua
ContentType.NOVEL -> R.string.content_type_novel
ContentType.ONE_SHOT -> R.string.content_type_one_shot
ContentType.DOUJINSHI -> R.string.content_type_doujinshi
ContentType.IMAGE_SET -> R.string.content_type_image_set
ContentType.ARTIST_CG -> R.string.content_type_artist_cg
ContentType.GAME_CG -> R.string.content_type_game_cg
}
fun MangaSource.getSummary(context: Context): String? = when (this) {
is MangaSourceInfo -> mangaSource.getSummary(context)
tailrec fun MangaSource.unwrap(): MangaSource = if (this is MangaSourceInfo) {
mangaSource.unwrap()
} else {
this
}
fun MangaSource.getSummary(context: Context): String? = when (val source = unwrap()) {
is MangaParserSource -> {
val type = context.getString(contentType.titleResId)
val locale = locale.toLocale().getDisplayName(context)
val type = context.getString(source.contentType.titleResId)
val locale = source.locale.toLocale().getDisplayName(context)
context.getString(R.string.source_summary_pattern, type, locale)
}
@@ -71,11 +91,10 @@ fun MangaSource.getSummary(context: Context): String? = when (this) {
else -> null
}
fun MangaSource.getTitle(context: Context): String = when (this) {
is MangaSourceInfo -> mangaSource.getTitle(context)
is MangaParserSource -> title
fun MangaSource.getTitle(context: Context): String = when (val source = unwrap()) {
is MangaParserSource -> source.title
LocalMangaSource -> context.getString(R.string.local_storage)
is ExternalMangaSource -> resolveName(context)
is ExternalMangaSource -> source.resolveName(context)
else -> context.getString(R.string.unknown)
}
@@ -86,3 +105,16 @@ fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
) {
append(context.getString(R.string.nsfw))
}
fun SpannableStringBuilder.appendIcon(textView: TextView, @DrawableRes resId: Int): SpannableStringBuilder {
val icon = ContextCompat.getDrawable(textView.context, resId) ?: return this
icon.setTintList(textView.textColors)
val size = textView.lineHeight
icon.setBounds(0, 0, size, size)
val alignment = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ImageSpan.ALIGN_CENTER
} else {
ImageSpan.ALIGN_BOTTOM
}
return inSpans(ImageSpan(icon, alignment)) { append(' ') }
}

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