Compare commits

...

913 Commits

Author SHA1 Message Date
Koitharu
34f6e5232b Update readme 2025-11-04 10:40:44 +02:00
Koitharu
f205c1b3dc Merge branch 'devel' of github.com:KotatsuApp/Kotatsu into devel 2025-11-04 10:35:35 +02:00
Milo Ivir
4b2a487c37 Translated using Weblate (Croatian)
Currently translated at 99.8% (895 of 896 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2025-11-04 10:34:14 +02:00
MuhamadSyabitHidayattulloh
726ac21974 Translated using Weblate (Indonesian)
Currently translated at 99.8% (895 of 896 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-11-04 10:34:14 +02:00
Robert Broketa
6b35216949 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (896 of 896 strings)

Co-authored-by: Robert Broketa <robert@broketa.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-11-04 10:34:14 +02:00
João Augusto Casagrande
22cae62f17 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.8% (895 of 896 strings)

Co-authored-by: João Augusto Casagrande <joao.augusto1809@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-11-04 10:34:14 +02:00
Oğuz Ersen
4733caf2e6 Translated using Weblate (Turkish)
Currently translated at 100.0% (896 of 896 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-11-04 10:34:14 +02:00
Максим Горпиніч
d49103de1f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (894 of 894 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2025-11-04 10:34:14 +02:00
Koitharu
414bab7ce3 Update readme 2025-11-04 10:34:03 +02:00
Koitharu
64c1873eb5 Merge branch 'master' into devel 2025-11-04 09:49:07 +02:00
Koitharu
06a0b5829b Fix crashes
(cherry picked from commit 1d32f53bdd)
2025-11-03 20:27:53 +02:00
Koitharu
0ce2870c8b Fix chapters list not accessible
(cherry picked from commit 5701862661)
2025-11-03 20:27:24 +02:00
Koitharu
f59027666b Fix loading empty manga
(cherry picked from commit 5590ab7c8a)
2025-11-03 20:27:18 +02:00
Nathan Bapin
8513bc6daf Fix forget page when the screen is rotated (#1674)
(cherry picked from commit e2fcfcc7a8)
2025-11-03 20:27:10 +02:00
Koitharu
cceaefc896 Avoid memory leak in ExceptionResolver
(cherry picked from commit 7a3b2a9bb4)
2025-11-03 20:27:05 +02:00
Koitharu
1d32f53bdd Fix crashes 2025-11-03 20:26:15 +02:00
Koitharu
0e98dd8695 Refactor SearchMenuProvider 2025-11-03 20:23:43 +02:00
MuhamadSyabitHidayattulloh
119b7c2ac7 Add filtering options for pinned sources and empty results in search menu 2025-11-02 15:28:24 +02:00
Koitharu
5701862661 Fix chapters list not accessible 2025-11-02 15:26:56 +02:00
Koitharu
5590ab7c8a Fix loading empty manga 2025-11-02 15:12:32 +02:00
Koitharu
9fde0106be Fix code formatting 2025-11-02 11:05:14 +02:00
skepsun
e73f077dc5 remove unnecessary summary 2025-11-02 10:57:46 +02:00
skepsun
c37458d43a Add foldable device support (auto two-page) 2025-11-02 10:57:46 +02:00
Nathan Bapin
e2fcfcc7a8 Fix forget page when the screen is rotated (#1674) 2025-11-02 10:57:25 +02:00
Koitharu
7a3b2a9bb4 Avoid memory leak in ExceptionResolver 2025-11-02 10:56:23 +02:00
Koitharu
881f154b5e Update parsers 2025-11-02 09:47:46 +02:00
Koitharu
34be5d16f2 Merge pull request #1701 from weblate/weblate-kotatsu-strings 2025-11-02 09:22:10 +02:00
Milo Ivir
e7e554648d Translated using Weblate (Croatian)
Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2025-10-30 04:25:59 +00:00
Draken
89a4180b46 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-10-30 04:25:55 +00:00
MuhamadSyabitHidayattulloh
4e2e190547 Translated using Weblate (Indonesian)
Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-10-30 04:25:53 +00:00
João Augusto Casagrande
3c557aae6c Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: João Augusto Casagrande <joao.augusto1809@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-10-30 04:25:50 +00:00
Nicola Bortoletto
0b00a3675d Translated using Weblate (Italian)
Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-10-30 04:25:41 +00:00
Alvoracz
8f20be6953 Translated using Weblate (Czech)
Currently translated at 97.8% (874 of 893 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-10-30 04:25:35 +00:00
Kanta Sekiguchi
26875c01c6 Translated using Weblate (Japanese)
Currently translated at 90.8% (811 of 893 strings)

Co-authored-by: Kanta Sekiguchi <kanta.sekiguchi360@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2025-10-30 04:25:32 +00:00
Koitharu
4beb34c1a5 Translated using Weblate (Russian)
Currently translated at 99.7% (891 of 893 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Conrado
1d50ab00c4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (889 of 889 strings)

Co-authored-by: Conrado <deadlocked53.89@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Ruffghanor
299cd229ec Translated using Weblate (Portuguese)
Currently translated at 100.0% (889 of 889 strings)

Co-authored-by: Ruffghanor <ruffghanor20@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Nahid hasan Limon
b02f394cd4 Translated using Weblate (Bengali)
Currently translated at 22.9% (204 of 889 strings)

Co-authored-by: Nahid hasan Limon <nahidhasanlimon401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Oğuz Ersen
7352f06564 Translated using Weblate (Turkish)
Currently translated at 100.0% (893 of 893 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (889 of 889 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (882 of 882 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Milo Ivir
1e4861367e Translated using Weblate (Croatian)
Currently translated at 100.0% (882 of 882 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Draken
bc3208946b Translated using Weblate (Vietnamese)
Currently translated at 100.0% (889 of 889 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (880 of 880 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
MuhamadSyabitHidayattulloh
d5fbb00676 Translated using Weblate (Indonesian)
Currently translated at 100.0% (889 of 889 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (880 of 880 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-10-26 17:51:58 +02:00
Hecker_01
7514362ca4 Translated using Weblate (Dutch)
Currently translated at 4.0% (36 of 880 strings)

Translated using Weblate (Dutch)

Currently translated at 88.8% (8 of 9 strings)

Added translation using Weblate (Dutch)

Added translation using Weblate (Dutch)

Co-authored-by: Hecker_01 <jesseflantua@icloud.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/nl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nl/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-10-26 17:51:58 +02:00
Infy's Tagalog Translations
e76a04bea0 Translated using Weblate (Filipino)
Currently translated at 98.9% (871 of 880 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-10-26 17:51:58 +02:00
Максим Горпиніч
732a6e7c26 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (889 of 889 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (882 of 882 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (880 of 880 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Макар Разин
f3111dc636 Translated using Weblate (Russian)
Currently translated at 100.0% (876 of 876 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Nicola Bortoletto
e0e0cf4ecd Translated using Weblate (Italian)
Currently translated at 100.0% (889 of 889 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (882 of 882 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (876 of 876 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
Nataniel Dika Kurniawan
50f302a7f8 Translated using Weblate (Indonesian)
Currently translated at 100.0% (876 of 876 strings)

Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-10-26 17:51:58 +02:00
google-labs-jules[bot]
500995a9d8 feat(settings): Add "Every 6 hours" option for periodic backups
Adds a new "Every 6 hours" frequency option to the periodic backup settings.

To maintain consistency with the existing preference values, which are stored in days, this new option is represented internally as a fractional value of `0.25` days.

The implementation includes:
- Adding the new string resource and updating the preference arrays.
- Changing the preference type in `AppSettings.kt` from `Long` to `Float` to accommodate the fractional value.
- Updating the millisecond conversion logic to correctly calculate the interval from a float value in days.

This approach avoids a complex data migration and is simpler and safer than changing the base unit for all values from days to hours.
2025-10-26 17:36:51 +02:00
Koitharu
beaf5cc0d5 Remove SavedFilterBackup class 2025-10-26 17:35:29 +02:00
google-labs-jules[bot]
6377de470d feat: Add saved filters to backup and restore
This commit adds support for backing up and restoring saved filters.

- Added a new `SAVED_FILTERS` section to the backup process.
- Implemented the logic to read filters from SharedPreferences during backup and write them back during restore.
- Fixed compilation errors in `AppBackupAgent` and `BackupSectionModel`.
2025-10-26 17:30:49 +02:00
google-labs-jules[bot]
dec45f7851 feat: Add saved filters to backup and restore
This commit adds support for backing up and restoring saved filters.

- Added a new `SAVED_FILTERS` section to the backup process.
- Implemented the logic to read filters from SharedPreferences during backup and write them back during restore.
2025-10-26 17:30:49 +02:00
Koitharu
dbada34a43 Move pull gesture option to reader settings 2025-10-26 17:27:33 +02:00
Koitharu
b62467964e Fix filters on tablet 2025-10-26 17:03:37 +02:00
Koitharu
3249e10931 Exclude broken sources from catalog 2025-10-26 16:47:54 +02:00
Koitharu
0d5229b112 Improve local manga directories config screen 2025-10-26 16:33:59 +02:00
Koitharu
d0ed1fb85f Notify about broken source on list screen 2025-10-21 18:04:05 +03:00
Koitharu
9e5664da3a Reorganize settings 2025-10-21 17:51:11 +03:00
Koitharu
35c158d35a Update readme 2025-10-21 15:51:12 +03:00
Koitharu
464f24e9f0 Fix unwanted touch events when chapters sheet is collapsed 2025-10-21 15:46:34 +03:00
Koitharu
c8a8203c39 Add authors to filter 2025-10-21 14:45:10 +03:00
Koitharu
b414758f32 Improve saved filters 2025-10-21 12:22:30 +03:00
Koitharu
1181860e41 Improve saved filters 2025-10-20 14:18:08 +03:00
Koitharu
e35521f16f Fix code formatting 2025-10-20 09:08:04 +03:00
MuhamadSyabitHidayattulloh
5fb8ff53f9 Feat: Add Saved Filters Feature 2025-10-20 09:07:56 +03:00
Vicente
a66283d035 Backup Restore reading stats 2025-10-20 08:45:40 +03:00
Vicente
a1ba0b8c21 Backup scrobblings 2025-10-20 08:45:40 +03:00
Koitharu
f3b42b9a42 Small improvement for chapter toast setting 2025-10-20 08:42:02 +03:00
google-labs-jules[bot]
aa2f2c17fc feat(reader): Add setting to toggle chapter toast 2025-10-20 08:37:18 +03:00
Koitharu
ebc17b645b Fix build 2025-10-19 16:08:37 +03:00
Koitharu
cc14e1abcf Fix crash when fast go to background 2025-10-19 15:26:25 +03:00
Koitharu
b1b474e2e7 Update readme 2025-10-19 15:08:41 +03:00
Koitharu
8ca3bece5d Fix crashes 2025-10-19 14:53:31 +03:00
Frosted
90bd9023d5 Translated using Weblate (Turkish)
Currently translated at 100.0% (876 of 876 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-10-19 14:41:29 +03:00
Максим Горпиніч
986627f24d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (876 of 876 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2025-10-19 14:41:29 +03:00
kota fujimi
cf2b8e2481 Translated using Weblate (Japanese)
Currently translated at 76.4% (669 of 875 strings)

Co-authored-by: kota fujimi <urakids@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2025-10-19 14:41:29 +03:00
Koitharu
b9435de5cd Update parsers 2025-10-19 14:33:31 +03:00
Koitharu
861c21faea Merge pull request #1673 from EmilieHascoet/Feat/auto-scroll-speed 2025-10-16 11:03:46 +03:00
Emilie Hascoët
9b4d014b21 Change valueTo from 0.95 to 0.97 in XML layout 2025-10-16 09:32:02 +02:00
Koitharu
c6da7de699 Fix backup rules 2025-10-15 15:24:02 +03:00
Koitharu
ef3aa40acc Fix some warnings 2025-10-14 16:25:28 +03:00
Koitharu
07af3ea703 Backup-restore fixes 2025-10-14 14:38:54 +03:00
Koitharu
391c8ab649 Fix sync issues 2025-10-14 14:04:17 +03:00
Koitharu
6b1885c89d Revert "Fix screen rotation causing reader to jump back to initial page"
This reverts commit aeb3732d75.
2025-10-14 13:49:02 +03:00
Koitharu
8423b48fb9 Revert "Fix page position loss when switching reader modes"
This reverts commit 5f879f6c83.
2025-10-14 13:49:02 +03:00
Koitharu
803c825d91 Fix ProgressUpdateUseCase 2025-10-14 13:46:16 +03:00
Koitharu
6a9682a077 Refactor reader sensitivity settings #1576 2025-10-14 10:23:47 +03:00
Koitharu
9197b9cc3a Merge branch 'devel' of github.com:puargs/Kotatsu into devel 2025-10-14 09:47:17 +03:00
google-labs-jules[bot]
02ea804874 Fix(reader): Fix incorrect scaling for short images in webtoon reader
When an image in the webtoon reader is shorter than the screen height, it was being incorrectly scaled, causing it to appear zoomed in or cropped.

This was caused by the `scrollTo` function in `WebtoonImageView.kt` calling `resetScaleAndCenter()` for images with a scroll range of zero. This method, from the underlying SubsamplingScaleImageView library, resets the image to a default scale instead of using the custom logic required by the reader.

The fix replaces the call to `resetScaleAndCenter()` with `scrollToInternal(0)`. This ensures that the custom scaling logic, which fits the image to the screen width, is applied consistently to all images, regardless of their height.
2025-10-14 09:45:41 +03:00
Koitharu
c424466198 Debounce Discord RPC 2025-10-14 09:44:18 +03:00
Quentinho199
18b312dde6 "fix/UI bug about finding chapter" 2025-10-14 09:44:01 +03:00
Koitharu
f78262b1a0 Udpate dependencies 2025-10-13 19:21:18 +03:00
Koitharu
c557a51c4d Fix loading local manga in some corner cases 2025-10-13 19:21:18 +03:00
kota fujimi
8995762935 Translated using Weblate (Japanese)
Currently translated at 76.0% (665 of 875 strings)

Co-authored-by: kota fujimi <urakids@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2025-10-11 15:04:54 +03:00
Milo Ivir
ed2664db78 Translated using Weblate (Croatian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-10-11 15:04:54 +03:00
Infy's Tagalog Translations
f5a5e53b5a Translated using Weblate (Filipino)
Currently translated at 99.0% (867 of 875 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-10-11 15:04:54 +03:00
MuhamadSyabitHidayattulloh
9ef961590d Translated using Weblate (Indonesian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-10-11 15:04:54 +03:00
Jinzhou Huang
9b569615ee Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 75.4% (660 of 875 strings)

Co-authored-by: Jinzhou Huang <2314662431@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2025-10-11 15:04:54 +03:00
Antonio Sanchez Castellón
f48cf2efe4 Translated using Weblate (Spanish)
Currently translated at 98.5% (862 of 875 strings)

Co-authored-by: Antonio Sanchez Castellón <angelfx19@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-10-11 15:04:54 +03:00
Ruthwik
18094a310c Translated using Weblate (Telugu)
Currently translated at 15.4% (135 of 875 strings)

Translated using Weblate (Telugu)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Telugu)

Added translation using Weblate (Telugu)

Co-authored-by: Ruthwik <rtwk03@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/te/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/te/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-10-11 15:04:54 +03:00
Anon
320c49a831 Translated using Weblate (Serbian)
Currently translated at 98.5% (862 of 875 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-10-11 15:04:54 +03:00
return_null
2a971d5dae Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: return_null <demolang@dismail.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-10-11 15:04:54 +03:00
Dragibus Noir
4467e79ae6 Translated using Weblate (French)
Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-10-11 15:04:54 +03:00
Nataniel Dika Kurniawan
c68b180bf6 Translated using Weblate (Malay)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Malay)

Currently translated at 53.1% (465 of 875 strings)

Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-10-11 15:04:54 +03:00
copilot-swe-agent[bot]
5f879f6c83 Fix page position loss when switching reader modes
- Compare content.state with getCurrentState() to detect configuration changes vs intentional updates
- Use content.state when they match (mode switch case) to preserve explicit state updates
- Use getCurrentState() when they differ (rotation case) to restore saved position
- This ensures both screen rotation and mode switching work correctly

Co-authored-by: NathanBap <101987516+NathanBap@users.noreply.github.com>
2025-10-11 15:04:37 +03:00
copilot-swe-agent[bot]
aeb3732d75 Fix screen rotation causing reader to jump back to initial page
- Modified BaseReaderFragment to always use getCurrentState() when available
- getCurrentState() contains the most recent reading position saved in onPause/onDestroyView
- content.state may contain the initial state from when content was first loaded
- This ensures the current page position is preserved across configuration changes like screen rotation

Co-authored-by: NathanBap <101987516+NathanBap@users.noreply.github.com>
2025-10-11 15:04:37 +03:00
dragonx943
6292a0fd6b Fix margin 2025-10-11 14:46:34 +03:00
dragonx943
8985b4135d Make button in error dialog centered 2025-10-11 14:46:34 +03:00
Koitharu
f8a5397542 Update dependencies 2025-09-28 11:50:25 +03:00
Koitharu
5f51041220 Fix crash: add error handling for scrobbling info 2025-09-26 08:55:00 +03:00
Koitharu
5a14412b62 Fix appying webtoon pull gesture settings 2025-09-26 08:52:09 +03:00
Koitharu
be012f631a Update parsers 2025-09-23 18:50:44 +03:00
Koitharu
0165f43603 Fix crash 2025-09-23 18:43:20 +03:00
Koitharu
55801a1488 Update parsers 2025-09-21 13:28:38 +03:00
Koitharu
77103f016f Update parsers 2025-09-21 13:27:22 +03:00
DashZero
6b6719a259 Translated using Weblate (Thai)
Currently translated at 50.9% (446 of 875 strings)

Co-authored-by: DashZero <mee_original@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Joe
822642abb0 Translated using Weblate (Belarusian)
Currently translated at 99.6% (872 of 875 strings)

Co-authored-by: Joe <happenstance@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
தமிழ்நேரம்
260745fb95 Translated using Weblate (Tamil)
Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ta/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
MuhamadSyabitHidayattulloh
024ec0388f Translated using Weblate (Indonesian)
Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Draken
5345998eec Translated using Weblate (Vietnamese)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Nicola Bortoletto
3d56190e71 Translated using Weblate (Italian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Shams deen
954431d0a5 Added translation using Weblate (Arabic (Egyptian))
Added translation using Weblate (Arabic (Egyptian))

Co-authored-by: Shams deen <shamsdeen84@gmail.com>
2025-09-21 13:26:53 +03:00
return_null
afec63b443 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (869 of 869 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.5% (865 of 869 strings)

Co-authored-by: return_null <demolang@dismail.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Nataniel Dika Kurniawan
ac5b29c35a Translated using Weblate (Malay)
Currently translated at 51.7% (453 of 875 strings)

Translated using Weblate (Malay)

Currently translated at 51.6% (452 of 875 strings)

Translated using Weblate (Malay)

Currently translated at 49.5% (431 of 869 strings)

Translated using Weblate (Javanese)

Currently translated at 9.4% (82 of 869 strings)

Translated using Weblate (Javanese)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Malay)

Currently translated at 40.5% (352 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (869 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Javanese)

Added translation using Weblate (Javanese)

Translated using Weblate (Malay)

Currently translated at 38.4% (334 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (869 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/jv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/jv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-09-21 13:26:53 +03:00
Frosted
59f5578b66 Translated using Weblate (Turkish)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (869 of 869 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-09-21 13:26:53 +03:00
Roel v
391dbb4237 Translated using Weblate (Gothic)
Currently translated at 2.8% (25 of 869 strings)

Translated using Weblate (Gothic)

Currently translated at 44.4% (4 of 9 strings)

Added translation using Weblate (Gothic)

Added translation using Weblate (Gothic)

Co-authored-by: Roel v <roel11112@live.nl>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/got/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/got/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-09-21 13:26:53 +03:00
gekka
7d4505eb78 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (865 of 869 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Максим Горпиніч
e6ceb20cf7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
lenn
8004f8c093 Translated using Weblate (Polish)
Currently translated at 97.8% (849 of 868 strings)

Co-authored-by: lenn <l3ennec@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2025-09-21 13:26:53 +03:00
Koitharu
61bf2abb6c Change inset handling in reader 2025-09-21 12:28:32 +03:00
Koitharu
d9612f3427 Webtoon pull gesture refactoring 2025-09-21 11:57:27 +03:00
Koitharu
435c3824f7 Remove A5 compatibility code 2025-09-20 09:56:03 +03:00
Koitharu
c846693570 Cleanup R8 rules 2025-09-20 09:37:23 +03:00
Koitharu
123937cd01 Fix SearchView closing on back pressed (close #1532, close #1487) 2025-09-20 09:31:10 +03:00
Koitharu
9f56554313 Reduce apk size 2025-09-20 07:55:27 +03:00
Koitharu
f8687bb697 Improve background WebView usage 2025-09-17 09:27:10 +03:00
Koitharu
43d3a2cc6a Fix crash on WebView.stopLoading() 2025-09-17 09:14:28 +03:00
MuhamadSyabitHidayattulloh
a95db6ed21 chore: Show description in offline mode (#1597) 2025-09-15 11:02:52 +07:00
Koitharu
fd0bb57338 Background captcha resolving 2025-09-14 20:11:55 +03:00
Koitharu
6b94bc2632 Update dependencies 2025-09-14 20:11:55 +03:00
Koitharu
c8b91599c6 Fix OkHttp initialization 2025-09-14 20:11:55 +03:00
Draken
3a8b0f9e93 Merge pull request #1586 from MuhamadSyabitHidayattulloh/feat/pull-gesture-navigate-chapter
feat: Add Pull Gesture Navigate Chapter
2025-09-13 23:23:12 +07:00
MuhamadSyabitHidayattulloh
17a0725666 feat: Realtime Favorite and Storage Badges 2025-09-13 09:27:46 +03:00
Draken
3be7848ad9 Revert distributionSha256Sum 2025-09-13 09:25:58 +03:00
dragonx943
08202c11a3 build: migrate to gradle 9 2025-09-13 09:25:58 +03:00
MuhamadSyabitHidayattulloh
5ef907d046 fix: Ui not visible if Control Panel show 2025-09-11 09:36:24 +07:00
MuhamadSyabitHidayattulloh
c3776ea3c6 feat: Add Pull Gesture Navigate Chapter 2025-09-10 14:41:28 +07:00
Koitharu
a624bffea3 Upgrade minSdk to 23 2025-09-07 10:56:40 +03:00
Koitharu
8f38b4fe30 Replace DummyParser with TestMangaRepository 2025-09-07 10:31:13 +03:00
Koitharu
71a2de5358 Update dependencies 2025-09-06 10:41:53 +03:00
Koitharu
5478f8fb59 Fix crash in ListSelectionController 2025-09-06 10:41:49 +03:00
ViAnh
5155c9a33d Reduce gaps between webtoon pages 2025-09-06 10:12:37 +03:00
puargs
1d1e49123a feat(reader): Add sensitivity setting for double-page mode
This commit introduces a new setting to control the scroll sensitivity of the double-page reader mode.

- A SeekBar has been added to the reader configuration sheet to adjust the sensitivity.
- The DoublePageSnapHelper now uses this setting to calculate the scroll distance.
- The setting is stored in AppSettings.
2025-09-04 22:40:08 -06:00
Koitharu
f7a461a9d8 Fix sync auth buttons (close #1556) 2025-08-30 09:25:55 +03:00
Aray LXa
3a02d22e02 Translated using Weblate (Persian)
Currently translated at 70.2% (610 of 868 strings)

Translated using Weblate (Persian)

Currently translated at 66.9% (581 of 868 strings)

Co-authored-by: Aray LXa <araylxa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2025-08-30 09:24:58 +03:00
Koitharu
2b8a29e2a6 Update parsers 2025-08-30 09:24:05 +03:00
Koitharu
bc68441585 Update parsers 2025-08-24 11:06:16 +03:00
Koitharu
1cc51b6a88 Refactor usage WebView for parsers 2025-08-24 10:39:23 +03:00
Kusou
fd5aca7252 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Kusou <orion26br@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-08-24 10:14:59 +03:00
Abay Emes
e447245fac Translated using Weblate (Kazakh)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Kazakh)

Currently translated at 62.5% (543 of 868 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/kk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-08-24 10:14:59 +03:00
Draken
5af0ee1c69 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-08-24 10:14:59 +03:00
Shayan
c02d1641ab Translated using Weblate (Persian)
Currently translated at 59.2% (514 of 868 strings)

Co-authored-by: Shayan <shayans31516@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2025-08-24 10:14:59 +03:00
Aray LXa
f55c525c8a Translated using Weblate (Persian)
Currently translated at 59.2% (514 of 868 strings)

Co-authored-by: Aray LXa <araylxa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2025-08-24 10:14:59 +03:00
Максим Горпиніч
a42fc87a9a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2025-08-24 10:14:59 +03:00
Juan Rubin
6b6905fd71 Translated using Weblate (Portuguese)
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Juan Rubin <juancrubin08@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-08-24 10:14:59 +03:00
Koitharu
b7f57856db Author search support for external manga sources 2025-08-24 10:13:12 +03:00
Koitharu
1d6d626b62 Update parsers 2025-08-24 09:47:29 +03:00
Koitharu
d93ff92cc9 Update parsers and fix some deprecations 2025-08-16 08:11:44 +03:00
Koitharu
8eda113f3b Captcha group notification intent 2025-08-14 15:57:18 +03:00
Koitharu
3916c2619e Update parsers 2025-08-14 11:51:33 +03:00
Milo Ivir
1d3e8e55ca Translated using Weblate (Croatian)
Currently translated at 94.0% (816 of 868 strings)

Translated using Weblate (Croatian)

Currently translated at 92.7% (805 of 868 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Manuela Silva
2c3b4f29eb Translated using Weblate (Portuguese)
Currently translated at 97.8% (849 of 868 strings)

Co-authored-by: Manuela Silva <mmsrs@sky.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Marco Ramazzotti
ee530002b6 Translated using Weblate (Japanese)
Currently translated at 55.1% (479 of 868 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Marco Ramazzotti <cuordilava@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Lorenzo Stella
59d530e0dc Translated using Weblate (Italian)
Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Lorenzo Stella <lorenzo.stella.1408@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Nicola Bortoletto
52a132caed Translated using Weblate (Italian)
Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Макар Разин
379d2dd8d4 Translated using Weblate (Russian)
Currently translated at 100.0% (868 of 868 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (868 of 868 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-08-14 11:24:16 +03:00
R2E
f8cefa3e8d Translated using Weblate (Arabic)
Currently translated at 92.9% (807 of 868 strings)

Co-authored-by: R2E <mokhalad875@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Champ0999
5e1eda850c Translated using Weblate (Italian)
Currently translated at 99.8% (867 of 868 strings)

Co-authored-by: Champ0999 <champ0999@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
gekka
18cc0ad0fb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (864 of 868 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Kuraki
11dd49c626 Translated using Weblate (Turkish)
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Kuraki <qkuraki@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Draken
2ad8ab0258 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Dragibus Noir
4f8c5325a4 Translated using Weblate (French)
Currently translated at 100.0% (868 of 868 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-08-14 11:24:16 +03:00
Koitharu
6e181a59a3 Update sync auth activity ui 2025-08-14 10:39:06 +03:00
Koitharu
7a7d20dbf4 AutoFixService fixes 2025-08-10 16:09:26 +03:00
Koitharu
83d5f8e378 UI fixes 2025-08-05 15:50:39 +03:00
Koitharu
5ac9bad728 Fix MultiMutex unlock when cancelled 2025-08-05 14:02:46 +03:00
Дмитро Крук
a090965a2d Translated using Weblate (Ukrainian)
Currently translated at 99.1% (860 of 867 strings)

Co-authored-by: Дмитро Крук <dimka89050@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
yunyi
1e376754bc Added translation using Weblate (Baoulé)
Co-authored-by: yunyi <1967158164@qq.com>
2025-08-04 16:12:21 +03:00
Hidayat
2cdbe52056 Translated using Weblate (Indonesian)
Currently translated at 98.7% (856 of 867 strings)

Co-authored-by: Hidayat <elbert.herry11@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
fadom06
1e09ac3ecb Translated using Weblate (German)
Currently translated at 74.9% (650 of 867 strings)

Translated using Weblate (German)

Currently translated at 74.9% (650 of 867 strings)

Co-authored-by: fadom06 <fadom06@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Draken
acc76c931a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (867 of 867 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (864 of 864 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Infy's Tagalog Translations
59c12d35c1 Translated using Weblate (Filipino)
Currently translated at 99.3% (861 of 867 strings)

Translated using Weblate (Filipino)

Currently translated at 99.1% (857 of 864 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-08-04 16:12:21 +03:00
周笑然
0e3cad1af1 Added translation using Weblate (Cantonese (Traditional Han script))
Added translation using Weblate (Cantonese (Traditional Han script))

Co-authored-by: 周笑然 <3140609186@qq.com>
2025-08-04 16:12:21 +03:00
Reptalica
ba8766b32d Translated using Weblate (Vietnamese)
Currently translated at 100.0% (863 of 863 strings)

Co-authored-by: Reptalica <reptalica20@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Dragibus Noir
35421cb71e Translated using Weblate (French)
Currently translated at 100.0% (867 of 867 strings)

Translated using Weblate (French)

Currently translated at 100.0% (863 of 863 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
aicha roun souleiman
8cecd9a0e2 Translated using Weblate (French)
Currently translated at 100.0% (863 of 863 strings)

Co-authored-by: aicha roun souleiman <louqman078@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Nicola Bortoletto
523057f3e1 Translated using Weblate (Italian)
Currently translated at 99.8% (866 of 867 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (861 of 863 strings)

Translated using Weblate (Italian)

Currently translated at 98.3% (849 of 863 strings)

Translated using Weblate (Italian)

Currently translated at 97.9% (844 of 862 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Frosted
337d196bc3 Translated using Weblate (Turkish)
Currently translated at 100.0% (867 of 867 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (867 of 867 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (863 of 863 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (862 of 862 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-08-04 16:12:21 +03:00
Hosted Weblate
c3b4c032bb 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-08-04 16:12:21 +03:00
zmni
4590c753ed Translated using Weblate (Indonesian)
Currently translated at 99.8% (848 of 849 strings)

Co-authored-by: zmni <zmni@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Dragibus Noir
9733101f0c Translated using Weblate (French)
Currently translated at 100.0% (862 of 862 strings)

Translated using Weblate (French)

Currently translated at 100.0% (849 of 849 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Robert Broketa
8cd71cc98d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (849 of 849 strings)

Co-authored-by: Robert Broketa <robert@broketa.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Kanta Sekiguchi
42748d9c98 Translated using Weblate (Japanese)
Currently translated at 54.6% (464 of 849 strings)

Co-authored-by: Kanta Sekiguchi <kanta.sekiguchi360@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Bai
8043574314 Translated using Weblate (Turkish)
Currently translated at 100.0% (849 of 849 strings)

Co-authored-by: Bai <bai@baturax.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Shayan
44d1fdb9d3 Translated using Weblate (Persian)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Persian)

Currently translated at 32.9% (280 of 849 strings)

Co-authored-by: Shayan <shayans31516@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
2025-08-04 16:12:21 +03:00
gekka
bc7054de4a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (863 of 867 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.5% (860 of 864 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.5% (859 of 863 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.5% (858 of 862 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.4% (843 of 848 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.4% (843 of 848 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Halbast Abdullah
4971e8ab0f Translated using Weblate (Kurdish (Central))
Currently translated at 2.5% (22 of 848 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 66.6% (6 of 9 strings)

Added translation using Weblate (Kurdish (Central))

Added translation using Weblate (Kurdish (Central))

Co-authored-by: Halbast Abdullah <halbastabdullah7@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ckb/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ckb/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-08-04 16:12:21 +03:00
Anon
df038b1edb Translated using Weblate (Serbian)
Currently translated at 99.5% (841 of 845 strings)

Translated using Weblate (Serbian)

Currently translated at 99.5% (841 of 845 strings)

Translated using Weblate (Serbian)

Currently translated at 99.5% (841 of 845 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Juan Rubin
7e7aabc1d1 Translated using Weblate (Portuguese)
Currently translated at 100.0% (845 of 845 strings)

Co-authored-by: Juan Rubin <juancrubin08@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Akhil Raj
9605ff89fb Translated using Weblate (Malayalam)
Currently translated at 4.7% (40 of 845 strings)

Translated using Weblate (Malayalam)

Currently translated at 4.7% (40 of 845 strings)

Translated using Weblate (Malayalam)

Currently translated at 3.5% (30 of 845 strings)

Translated using Weblate (Malayalam)

Currently translated at 3.5% (30 of 845 strings)

Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ml/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Draken
4ed177d29f Translated using Weblate (Vietnamese)
Currently translated at 100.0% (862 of 862 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (849 of 849 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (848 of 848 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (848 of 848 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (848 of 848 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (845 of 845 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-08-04 16:12:21 +03:00
Frosted
61cefefd10 Translated using Weblate (Turkish)
Currently translated at 100.0% (845 of 845 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-08-04 16:12:21 +03:00
Koitharu
9f965c5269 Remove Telegram bot token from public source 2025-08-04 16:11:28 +03:00
Koitharu
0c713cb799 Fix inifite captcha notifications 2025-08-04 12:50:09 +03:00
Koitharu
6d3f8cbb3b Filter for Local storage tab on main screen 2025-08-04 09:57:07 +03:00
Koitharu
05739bb5b3 Proper handling network unavailable error for images 2025-07-30 16:22:21 +03:00
Koitharu
47f0bbee17 Disk cache for favicons 2025-07-30 15:52:09 +03:00
Koitharu
dd77926dcb Improve sources settings 2025-07-30 14:50:45 +03:00
Koitharu
1b76f21507 Show reason why manga has no chapters 2025-07-30 14:06:45 +03:00
Koitharu
fe21af5443 Update parsers 2025-07-29 16:22:04 +03:00
Koitharu
0b0373021e Fix loading local manga 2025-07-26 19:28:32 +03:00
Koitharu
d641e7933d Option to show only downloaded chapters 2025-07-25 13:43:01 +03:00
Koitharu
d8efe374a8 Experimental: improve manga loading in reader 2025-07-24 15:20:42 +03:00
Koitharu
506a8b6e90 UI fixes 2025-07-23 12:08:27 +03:00
Koitharu
d81173bf76 Merge branch 'feature/discord_rpc' into devel 2025-07-22 16:42:35 +03:00
Koitharu
896452a096 Discord RPC improvements 2025-07-22 13:05:27 +03:00
Daniil Zhuravlev
35aa4d5e8f ci: add a site update trigger when the application is released 2025-07-21 08:59:34 +03:00
Koitharu
4d4c9c7a48 Discord RPC 2025-07-20 15:18:11 +03:00
Koitharu
b667e32598 Update parsers 2025-07-20 08:13:28 +03:00
Koitharu
c987fc234b Add LeakCanary to nighly builds 2025-07-17 20:42:12 +03:00
Koitharu
8142a6811b Add option to hide fab (close #1466) 2025-07-16 20:05:00 +03:00
Koitharu
3e36e1e11c Debug menu for debug builds 2025-07-16 20:05:00 +03:00
Koitharu
30aaca6341 Merge pull request #1497 from dragonx943/patch-1 2025-07-13 18:48:46 +03:00
Draken
43b34a7bca Fix gradle checksum 2025-07-13 21:04:31 +07:00
Koitharu
b23008d0ae Update Miku theme #1490 2025-07-13 11:23:15 +03:00
Koitharu
5a368b27bb Fix override applying 2025-07-13 11:07:00 +03:00
Koitharu
fe3f95d160 Cache custom covers (close #1492) 2025-07-13 10:04:18 +03:00
Koitharu
de1a297338 Fix downloading edited manga (close #1493) 2025-07-13 09:41:04 +03:00
Koitharu
d6350afe3a Upgrade target sdk 2025-07-13 09:35:24 +03:00
Koitharu
ec048c70f1 Update parsers 2025-07-10 21:39:46 +03:00
Dragibus Noir
282c1b51f7 Translated using Weblate (French)
Currently translated at 100.0% (843 of 843 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-07-10 21:33:32 +03:00
Infy's Tagalog Translations
d6b6ce1bcd Translated using Weblate (Filipino)
Currently translated at 99.4% (838 of 843 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-07-10 21:33:32 +03:00
Giovanni S.C
f48444dcf6 Translated using Weblate (Spanish)
Currently translated at 92.6% (781 of 843 strings)

Translated using Weblate (Spanish)

Currently translated at 92.6% (781 of 843 strings)

Co-authored-by: Giovanni S.C <giovanniandre2003@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-07-10 21:33:32 +03:00
Krays The Poet
15ba766643 Translated using Weblate (German)
Currently translated at 76.5% (645 of 843 strings)

Translated using Weblate (German)

Currently translated at 76.5% (645 of 843 strings)

Co-authored-by: Krays The Poet <kraysthepoet@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2025-07-10 21:33:32 +03:00
Draken
a0dbbcb350 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (843 of 843 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (843 of 843 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-07-10 21:33:32 +03:00
Frosted
f72bba9557 Translated using Weblate (Turkish)
Currently translated at 100.0% (843 of 843 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-07-10 21:33:32 +03:00
Макар Разин
207791aa3e Translated using Weblate (Ukrainian)
Currently translated at 99.6% (840 of 843 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.6% (840 of 843 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (843 of 843 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (843 of 843 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
2025-07-10 21:33:32 +03:00
Koitharu
6319997716 Periodical backup improvements 2025-07-10 09:54:45 +03:00
Koitharu
b70c1da54b Implement missing getForegroundInfo for LocalStorageCleanupWorker 2025-07-10 08:57:11 +03:00
Koitharu
621cb19c5b Fix saving override for non-library manga 2025-07-10 08:48:10 +03:00
Koitharu
b528b7b3c1 Fix passing headers to favicon requests 2025-07-09 22:07:37 +03:00
Koitharu
9a1bb6f6fc Fix long tap on old Android versions (close #1478) 2025-07-09 21:57:37 +03:00
Koitharu
37f9c4b9f6 Fix window insets and search closing 2025-07-09 21:42:28 +03:00
Koitharu
d0084e50e7 Fix loading local manga (closes #1481, #1474, #1479, #1484, #1439) 2025-07-09 21:21:02 +03:00
Koitharu
088576cc9d Ignore network error for background progress update 2025-07-07 13:46:37 +03:00
Koitharu
f0ba42b518 Fix captcha notification dismissing 2025-07-06 12:09:30 +03:00
Koitharu
33366e63db Translated using Weblate (Russian)
Currently translated at 99.7% (841 of 843 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Fênixsmasters
0f39b313c0 Translated using Weblate (Portuguese)
Currently translated at 100.0% (842 of 842 strings)

Co-authored-by: Fênixsmasters <amadeudavi373@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Muktii
abcbb940d3 Translated using Weblate (Indonesian)
Currently translated at 99.2% (836 of 842 strings)

Co-authored-by: Muktii <mhdmuktikece@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
mak7im01
f4fec709fc Translated using Weblate (Russian)
Currently translated at 99.1% (835 of 842 strings)

Co-authored-by: mak7im01 <mak7im02@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Nagito Luck
a8c6f6a1ce Translated using Weblate (Spanish)
Currently translated at 92.0% (775 of 842 strings)

Co-authored-by: Nagito Luck <dealbamax@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Giovanni S.C
3a6794a50e Translated using Weblate (Spanish)
Currently translated at 92.0% (775 of 842 strings)

Co-authored-by: Giovanni S.C <giovanniandre2003@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Justine Kyle Cobar
aea3328f2d Translated using Weblate (Filipino)
Currently translated at 99.0% (834 of 842 strings)

Co-authored-by: Justine Kyle Cobar <cobarjustinekyle583@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Nagito Luck
c225e90626 Translated using Weblate (Spanish)
Currently translated at 90.7% (764 of 842 strings)

Co-authored-by: Nagito Luck <dealbamax@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Giovanni S.C
de8500d705 Translated using Weblate (Spanish)
Currently translated at 90.7% (764 of 842 strings)

Co-authored-by: Giovanni S.C <giovanniandre2003@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Draken
2d41cf14e2 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (842 of 842 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (840 of 840 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Frosted
1777528fcb Translated using Weblate (Turkish)
Currently translated at 100.0% (842 of 842 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (840 of 840 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-07-05 14:59:45 +03:00
Geovani Amaral
43618ed224 Translated using Weblate (Portuguese)
Currently translated at 100.0% (840 of 840 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.5% (836 of 840 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.4% (835 of 840 strings)

Co-authored-by: Geovani Amaral <geovani.af4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-07-05 14:59:45 +03:00
Koitharu
2229439547 Hide chapter-pages tabs when action mode started (closes #1454) 2025-07-05 10:24:34 +03:00
Koitharu
98abffa67b Fix login autofill (closes #1469) 2025-07-05 10:24:34 +03:00
Koitharu
aafefffc27 Yellow color filter in reader 2025-07-05 10:24:34 +03:00
Koitharu
add5c7dc17 Update parsers 2025-07-05 10:24:33 +03:00
Koitharu
40584cb6f5 Fix back press exit confirmation 2025-07-05 10:24:33 +03:00
Koitharu
e549d141a4 Merge pull request #1472 from dragonx943/patch-1 2025-07-04 09:21:19 +03:00
Draken
4199f54241 Update tags_warnlist 2025-07-04 12:07:33 +07:00
Stanislav Khromov
e511c3cc97 Update strings.xml 2025-06-30 08:49:12 +03:00
Stanislav Khromov
33dbca1bc9 Remove navigation inversion for tap actions
Tap actions for page and chapter navigation no longer respect the 'Invert navigation controls' setting. The setting summary was updated to clarify it only affects volume button and hardware key navigation.
2025-06-30 08:49:12 +03:00
Stanislav Khromov
40778a88dd Add option to invert reader navigation controls
Introduces a new setting allowing users to invert navigation controls for page and chapter switching in the reader. Updates preferences, strings, and control logic to support swapping the direction of volume button and tap navigation.
2025-06-30 08:49:12 +03:00
Koitharu
3e8e423962 Merge remote-tracking branch 'weblate/devel' into devel 2025-06-29 16:59:22 +03:00
Koitharu
881473f495 Update parsers 2025-06-29 16:39:45 +03:00
M
dc8ecf2b12 Translated using Weblate (Literary Chinese)
Currently translated at 5.0% (42 of 838 strings)

Translated using Weblate (Literary Chinese)

Currently translated at 88.8% (8 of 9 strings)

Co-authored-by: M <recall-ditto-giddy@duck.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/lzh/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lzh/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-06-29 15:11:24 +02:00
6XChen
19d7a98968 Translated using Weblate (Indonesian)
Currently translated at 99.7% (836 of 838 strings)

Co-authored-by: 6XChen <blackdarksoulweapon@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-06-29 15:11:23 +02:00
Sapate Vaibhav
b2538065a9 Translated using Weblate (Hindi)
Currently translated at 79.3% (665 of 838 strings)

Co-authored-by: Sapate Vaibhav <sapatevaibhav@duck.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
2025-06-29 15:11:22 +02:00
maryush
ebf37e3d08 Translated using Weblate (Polish)
Currently translated at 100.0% (840 of 840 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (838 of 838 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2025-06-29 15:11:21 +02:00
Alvoracz
8c37135a40 Translated using Weblate (Czech)
Currently translated at 98.0% (820 of 836 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-06-29 15:11:20 +02:00
finnchiki
ca716f9a34 Translated using Weblate (Indonesian)
Currently translated at 98.2% (821 of 836 strings)

Co-authored-by: finnchiki <azrellgans@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-06-29 15:11:19 +02:00
TheOneWhoCares
112ac70648 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (836 of 836 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-06-29 15:11:18 +02:00
Dragibus Noir
694e4d4baf Translated using Weblate (French)
Currently translated at 100.0% (840 of 840 strings)

Translated using Weblate (French)

Currently translated at 100.0% (836 of 836 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-06-29 15:11:17 +02:00
Rafa Herzog
e74afa06a1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (834 of 834 strings)

Co-authored-by: Rafa Herzog <49111482+necronyxon@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-06-29 15:11:16 +02:00
Infy's Tagalog Translations
4494cc1888 Translated using Weblate (Filipino)
Currently translated at 99.5% (832 of 836 strings)

Translated using Weblate (Filipino)

Currently translated at 99.5% (830 of 834 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-06-29 15:11:15 +02:00
Draken
b13d9078d4 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (834 of 834 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-06-29 15:11:14 +02:00
gekka
5999301de8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (837 of 840 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (836 of 838 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (834 of 836 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (832 of 834 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (832 of 834 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-06-29 15:11:13 +02:00
Frosted
601b4016e6 Translated using Weblate (Turkish)
Currently translated at 100.0% (838 of 838 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (834 of 834 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-06-29 15:11:12 +02:00
Nicola Bortoletto
4d13b8f7b0 Translated using Weblate (Italian)
Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (834 of 834 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-06-29 15:11:11 +02:00
Макар Разин
6b95ec829e Translated using Weblate (Russian)
Currently translated at 100.0% (834 of 834 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (834 of 834 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-06-29 15:11:10 +02:00
M
1c658fa3c3 Translated using Weblate (Literary Chinese)
Currently translated at 5.0% (42 of 838 strings)

Translated using Weblate (Literary Chinese)

Currently translated at 88.8% (8 of 9 strings)

Co-authored-by: M <recall-ditto-giddy@duck.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/lzh/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lzh/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-06-29 16:11:09 +03:00
6XChen
32a8a8fed2 Translated using Weblate (Indonesian)
Currently translated at 99.7% (836 of 838 strings)

Co-authored-by: 6XChen <blackdarksoulweapon@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Sapate Vaibhav
c63e0542a0 Translated using Weblate (Hindi)
Currently translated at 79.3% (665 of 838 strings)

Co-authored-by: Sapate Vaibhav <sapatevaibhav@duck.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
maryush
dea779fc4b Translated using Weblate (Polish)
Currently translated at 100.0% (840 of 840 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (838 of 838 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Alvoracz
339b8f7311 Translated using Weblate (Czech)
Currently translated at 98.0% (820 of 836 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
finnchiki
3c087dde11 Translated using Weblate (Indonesian)
Currently translated at 98.2% (821 of 836 strings)

Co-authored-by: finnchiki <azrellgans@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
TheOneWhoCares
dd1223229d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (836 of 836 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Dragibus Noir
0b393b0e81 Translated using Weblate (French)
Currently translated at 100.0% (836 of 836 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Rafa Herzog
b24cac9305 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (834 of 834 strings)

Co-authored-by: Rafa Herzog <49111482+necronyxon@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Infy's Tagalog Translations
dc92723526 Translated using Weblate (Filipino)
Currently translated at 99.5% (832 of 836 strings)

Translated using Weblate (Filipino)

Currently translated at 99.5% (830 of 834 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-06-29 16:11:09 +03:00
Draken
203020e100 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (834 of 834 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
gekka
05eac1eabd Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (837 of 840 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (836 of 838 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (834 of 836 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (832 of 834 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (832 of 834 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Frosted
c7b720ec91 Translated using Weblate (Turkish)
Currently translated at 100.0% (838 of 838 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (834 of 834 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-06-29 16:11:09 +03:00
Nicola Bortoletto
58026b6fc0 Translated using Weblate (Italian)
Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (834 of 834 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-06-29 16:11:09 +03:00
Макар Разин
ba2f9dc16c Translated using Weblate (Russian)
Currently translated at 100.0% (834 of 834 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (834 of 834 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-06-29 16:11:09 +03:00
Stanislav Khromov
97afa29785 Improve edge detection performance (#1457)
* Optimize edge detection by decoding full image once

Refactors edge detection to decode the entire image at a downscaled resolution once, instead of decoding multiple regions. This improves performance by reducing repeated decoding operations and leverages direct pixel access for edge analysis. Adds a scale factor calculation to balance accuracy and speed for large images.

* Update EdgeDetector.kt

* Update EdgeDetector.kt

* Update EdgeDetector.kt
2025-06-29 16:10:48 +03:00
Koitharu
97f2ff3bbd Adjust AlertDialog style 2025-06-29 16:08:55 +03:00
Koitharu
14f185393b New mirror switching approach 2025-06-28 09:12:49 +03:00
Koitharu
fc7f5f2cf9 Fix captcha notification dismissing 2025-06-28 07:02:51 +03:00
Koitharu
4088f50120 Remove unrelevant strictmode detections 2025-06-28 06:55:58 +03:00
Koitharu
afa18e086c Update tg bot token 2025-06-28 06:53:34 +03:00
Koitharu
a8cfb3521c Fix window size handling in reader 2025-06-28 06:52:41 +03:00
Koitharu
7047ee6155 Fix crashes 2025-06-27 19:25:55 +03:00
Koitharu
957b12f338 Improve error reporting from notifications 2025-06-27 18:39:10 +03:00
Koitharu
679b1fd2f2 Fix crashes 2025-06-26 18:56:20 +03:00
Koitharu
367a917c56 Update color schemes 2025-06-26 15:42:32 +03:00
Koitharu
ed7fdb32a1 Handle more cover loading errors 2025-06-24 18:45:27 +03:00
Koitharu
e8f0aa8388 Fix biometric authentication launch 2025-06-24 18:24:40 +03:00
Koitharu
7404612a84 Fix crash in downloads 2025-06-24 18:12:58 +03:00
Koitharu
512069ca3e Fix local manga list order by date 2025-06-23 21:15:24 +03:00
Koitharu
b53b8eefa3 AppBar behavior fixes 2025-06-23 20:59:14 +03:00
Koitharu
f7509b09c1 Fix amoled theme 2025-06-23 20:30:32 +03:00
Koitharu
fbff0ab027 Fix Arabic flag mapping (closes #1440) 2025-06-23 20:09:15 +03:00
Koitharu
17656233ef Fix bookmarks images loading 2025-06-22 20:32:37 +03:00
Koitharu
18466a2c1a Show error codes on covers 2025-06-22 19:54:39 +03:00
Koitharu
2797ea6a99 Fix backup backward compatibility 2025-06-22 18:47:20 +03:00
Koitharu
b4a298ea55 Fix empty shortcut title crash 2025-06-22 18:22:08 +03:00
Koitharu
f811eeebc9 Scroll timer improvements 2025-06-22 15:59:57 +03:00
Koitharu
aeee782512 Retry thumbnails loading after network state changed 2025-06-22 13:28:44 +03:00
Koitharu
fe59a13218 Update filter header UI 2025-06-22 12:39:25 +03:00
Koitharu
c2688517ba Improve assistant integration 2025-06-22 11:44:22 +03:00
Koitharu
95019f9eb6 Option to open reader in a separated task 2025-06-22 11:30:28 +03:00
Koitharu
f43769bde7 Fix double reader mode switch 2025-06-22 10:59:42 +03:00
Koitharu
c576b62d51 Scrobbling improvements and fixes (closes #1448) 2025-06-22 10:25:48 +03:00
Koitharu
722c4466bf Update parsers 2025-06-21 19:55:34 +03:00
Koitharu
61b863ae96 Fix DateTimeAgo in future 2025-06-21 18:15:40 +03:00
Koitharu
55ea0d7b2b Fix InflateException crash on A15 (maybe) 2025-06-21 18:06:40 +03:00
Koitharu
04f56c6d84 Update dependencies 2025-06-21 17:54:23 +03:00
Koitharu
e7aae4e72a Remove grid size slider step 2025-06-21 17:41:48 +03:00
Koitharu
3547e7afb8 Fix search suggestions on landscape 2025-06-21 16:26:28 +03:00
Koitharu
a07e5ab278 Fix filter summary 2025-06-21 16:26:04 +03:00
Koitharu
1ddc32cbd4 Fix crash in BackupRepository 2025-06-21 16:11:13 +03:00
Koitharu
80a30d059f Fix json configuration for backups 2025-06-20 09:22:15 +03:00
Koitharu
437e6809bf Backup restorng fixes 2025-06-14 15:04:10 +03:00
Koitharu
b9d4c070eb Use streams for backups 2025-06-14 12:07:52 +03:00
Koitharu
4ef6908e82 Use kotlin serialization for sync 2025-06-09 12:43:33 +03:00
Koitharu
b854ca8807 Update dependencies 2025-06-07 11:46:37 +03:00
Koitharu
db89bdfdff Respect network data saver #1390 2025-06-07 10:14:51 +03:00
Koitharu
dc1df527b2 Set read button min width 2025-06-07 09:11:50 +03:00
Koitharu
584e93fbbf Option to edit manga in details screen 2025-06-07 08:45:22 +03:00
Koitharu
c2d4258afc Improve external plugins support 2025-06-07 08:31:47 +03:00
Koitharu
60dca5f8c3 Page image picker; ability to use manga page as custom cover 2025-06-05 21:22:07 +03:00
Koitharu
5d1afab071 Fix workers scheduling 2025-06-05 09:31:29 +03:00
Koitharu
851e417370 Revert "Remove redundant workers constraints to fix crash"
This reverts commit 71a82ae187.
2025-06-05 09:28:37 +03:00
Koitharu
71a82ae187 Remove redundant workers constraints to fix crash 2025-06-03 19:13:11 +03:00
Koitharu
0e54e4778e Merge pull request #1430 from dragonx943/patch-1 2025-06-03 11:21:39 +03:00
Draken
053ce880e4 Fix build 2025-06-03 15:14:37 +07:00
VardanRattan
67b1e4e862 Fix ForegroundServiceStartNotAllowedException on Android 12+
- Add setExpedited() to WorkManager requests missing it
- Fixes crash when starting foreground services from background
- Updated DownloadFactory, LocalStorageCleanupWorker, SuggestionsWorker, and TrackWorker schedulers
- Ensures compliance with Android 12+ foreground service restrictions
2025-05-31 17:41:46 +03:00
Koitharu
e04a877310 Fix favorite categories observing 2025-05-31 15:57:49 +03:00
Koitharu
48a605eeb0 Update dependencies 2025-05-31 08:45:57 +03:00
Koitharu
aed08f18bb Merge branch 'master' into devel 2025-05-31 08:42:06 +03:00
dragonx943
0c626cd2a3 Remove theme_name_dynamic_v2 string 2025-05-31 08:25:43 +03:00
Draken
82e6aa335b Translated using Weblate (Vietnamese)
Currently translated at 100.0% (833 of 833 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-05-31 08:25:22 +03:00
Koitharu
f79575e8d5 Option to not collapse descriptions 2025-05-31 08:24:02 +03:00
Koitharu
ac0dc0a94a Added data to details intent 2025-05-31 07:51:12 +03:00
Koitharu
7de4ac2b89 Fix tablet navigation insets 2025-05-29 20:41:31 +03:00
Koitharu
e01ddc0db7 Fix Read split button #1417 2025-05-28 18:50:58 +03:00
Koitharu
5745eca683 Merge branch 'devel' of https://hosted.weblate.org/git/kotatsu/strings into devel 2025-05-28 18:38:53 +03:00
Dragibus Noir
8a8ee46234 Translated using Weblate (French)
Currently translated at 100.0% (829 of 829 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Rodrigo Cunha
26489627f2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (829 of 829 strings)

Co-authored-by: Rodrigo Cunha <rodrigocunha1110@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Naga
341ced2d83 Translated using Weblate (French)
Currently translated at 99.6% (826 of 829 strings)

Co-authored-by: Naga <yz2000.pro@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
maryush
c5dd0eb375 Translated using Weblate (Polish)
Currently translated at 100.0% (826 of 826 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
TheOneWhoCares
33045ae36f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (825 of 825 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Макар Разин
442ebe5919 Translated using Weblate (Russian)
Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.8% (824 of 825 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Belarusian)

Currently translated at 97.0% (800 of 824 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-05-27 17:25:02 +03:00
Yauhen
363bcbad18 Translated using Weblate (Belarusian)
Currently translated at 89.8% (740 of 824 strings)

Co-authored-by: Yauhen <bugomol@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
sithuhein
5721cf71d3 Translated using Weblate (Burmese)
Currently translated at 5.8% (48 of 824 strings)

Added translation using Weblate (Burmese)

Translated using Weblate (Burmese)

Currently translated at 77.7% (7 of 9 strings)

Co-authored-by: sithuhein <sithuh3in2007@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/my/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/my/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-05-27 17:25:02 +03:00
Roger VC
ed8cc8d01f Translated using Weblate (Catalan)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Roger VC <rogervilarasau@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ca/
Translation: Kotatsu/plurals
2025-05-27 17:25:02 +03:00
Evgeniy Khramov
6c38f59e0f Translated using Weblate (Russian)
Currently translated at 99.6% (817 of 820 strings)

Co-authored-by: Evgeniy Khramov <65224669+thejenja@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Infy's Tagalog Translations
4d07311afc Translated using Weblate (Filipino)
Currently translated at 99.7% (825 of 827 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (823 of 824 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (823 of 824 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (819 of 820 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-05-27 17:25:02 +03:00
Lumiini
8ec81bb33f Translated using Weblate (Finnish)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Finnish)

Currently translated at 30.8% (253 of 820 strings)

Co-authored-by: Lumiini <bennokaynak@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-05-27 17:25:02 +03:00
Draken
eb322d0dcd Translated using Weblate (Vietnamese)
Currently translated at 100.0% (829 of 829 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (820 of 820 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (820 of 820 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Ruslik31
42a929d3f1 Translated using Weblate (Russian)
Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Ruslik31 <dolgovruslana0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Vermeil
978167ad3f Translated using Weblate (Indonesian)
Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Vermeil <krisgamer6677@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Zan 1456
9767e1a87d Translated using Weblate (Hungarian)
Currently translated at 77.5% (632 of 815 strings)

Co-authored-by: Zan 1456 <mestermc594@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Brayan González
4b822d6684 Translated using Weblate (Spanish)
Currently translated at 93.6% (763 of 815 strings)

Co-authored-by: Brayan González <bg2896054brayan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Baldomero, Kier Justine D
b59e41ef62 Translated using Weblate (Filipino)
Currently translated at 99.8% (814 of 815 strings)

Co-authored-by: Baldomero, Kier Justine D <yeartwothousandfive@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Anon
89ddfd3037 Translated using Weblate (Serbian)
Currently translated at 99.5% (822 of 826 strings)

Translated using Weblate (Serbian)

Currently translated at 98.4% (802 of 815 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
gekka
d97a2bba52 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (824 of 827 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (824 of 825 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (823 of 824 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (820 of 821 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (819 of 820 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (814 of 815 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.6% (812 of 815 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.6% (812 of 815 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Infy's Tagalog Translations
e72a8b2b8e Translated using Weblate (Filipino)
Currently translated at 99.7% (813 of 815 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-05-27 17:25:02 +03:00
Boqirz
c3c1d94f92 Translated using Weblate (Indonesian)
Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Boqirz <alveromodar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Frosted
1f8c5a894a Translated using Weblate (Turkish)
Currently translated at 100.0% (829 of 829 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (827 of 827 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (826 of 826 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (820 of 820 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (815 of 815 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (815 of 815 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-05-27 17:25:02 +03:00
Nicola Bortoletto
9dd05fcc70 Translated using Weblate (Italian)
Currently translated at 100.0% (829 of 829 strings)

Translated using Weblate (Italian)

Currently translated at 99.8% (828 of 829 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (827 of 827 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (826 of 826 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (820 of 820 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-05-27 17:25:02 +03:00
Dragibus Noir
e6487fb199 Translated using Weblate (French)
Currently translated at 100.0% (829 of 829 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Rodrigo Cunha
9d9f611091 Translated using Weblate (Portuguese)
Currently translated at 100.0% (829 of 829 strings)

Co-authored-by: Rodrigo Cunha <rodrigocunha1110@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Naga
a7c21515cd Translated using Weblate (French)
Currently translated at 99.6% (826 of 829 strings)

Co-authored-by: Naga <yz2000.pro@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
maryush
01f1a37bc1 Translated using Weblate (Polish)
Currently translated at 100.0% (826 of 826 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
TheOneWhoCares
5a24f43db3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (825 of 825 strings)

Co-authored-by: TheOneWhoCares <266nre4gw@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Макар Разин
61b2e96bf1 Translated using Weblate (Russian)
Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.8% (824 of 825 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Belarusian)

Currently translated at 97.0% (800 of 824 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-05-27 16:24:54 +02:00
Yauhen
affbd0cdb6 Translated using Weblate (Belarusian)
Currently translated at 89.8% (740 of 824 strings)

Co-authored-by: Yauhen <bugomol@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
sithuhein
a2c40a302b Translated using Weblate (Burmese)
Currently translated at 5.8% (48 of 824 strings)

Added translation using Weblate (Burmese)

Translated using Weblate (Burmese)

Currently translated at 77.7% (7 of 9 strings)

Co-authored-by: sithuhein <sithuh3in2007@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/my/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/my/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-05-27 16:24:54 +02:00
Roger VC
6ac6353e6a Translated using Weblate (Catalan)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Roger VC <rogervilarasau@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ca/
Translation: Kotatsu/plurals
2025-05-27 16:24:54 +02:00
Evgeniy Khramov
203dc1801a Translated using Weblate (Russian)
Currently translated at 99.6% (817 of 820 strings)

Co-authored-by: Evgeniy Khramov <65224669+thejenja@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Infy's Tagalog Translations
dc4fbf61a9 Translated using Weblate (Filipino)
Currently translated at 99.6% (830 of 833 strings)

Translated using Weblate (Filipino)

Currently translated at 99.7% (825 of 827 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (823 of 824 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (823 of 824 strings)

Translated using Weblate (Filipino)

Currently translated at 99.8% (819 of 820 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-05-27 16:24:54 +02:00
Lumiini
1fac005db7 Translated using Weblate (Finnish)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Finnish)

Currently translated at 30.8% (253 of 820 strings)

Co-authored-by: Lumiini <bennokaynak@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-05-27 16:24:54 +02:00
Draken
e9ee658385 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (831 of 831 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (829 of 829 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (820 of 820 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (820 of 820 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Ruslik31
0785ba70ce Translated using Weblate (Russian)
Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Ruslik31 <dolgovruslana0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Vermeil
c4c6867fef Translated using Weblate (Indonesian)
Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Vermeil <krisgamer6677@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Zan 1456
447a44208f Translated using Weblate (Hungarian)
Currently translated at 77.5% (632 of 815 strings)

Co-authored-by: Zan 1456 <mestermc594@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Brayan González
eb9bd2ad5f Translated using Weblate (Spanish)
Currently translated at 93.6% (763 of 815 strings)

Co-authored-by: Brayan González <bg2896054brayan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Baldomero, Kier Justine D
5030d2c4c0 Translated using Weblate (Filipino)
Currently translated at 99.8% (814 of 815 strings)

Co-authored-by: Baldomero, Kier Justine D <yeartwothousandfive@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Anon
6b9fc7dd50 Translated using Weblate (Serbian)
Currently translated at 99.5% (822 of 826 strings)

Translated using Weblate (Serbian)

Currently translated at 98.4% (802 of 815 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
gekka
c7ca0d9707 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (824 of 827 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (824 of 825 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (823 of 824 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (820 of 821 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (819 of 820 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (814 of 815 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.6% (812 of 815 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.6% (812 of 815 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Infy's Tagalog Translations
e9a38d0d03 Translated using Weblate (Filipino)
Currently translated at 99.7% (813 of 815 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-05-27 16:24:54 +02:00
Boqirz
bc6ce75268 Translated using Weblate (Indonesian)
Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Boqirz <alveromodar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
Frosted
e545f19339 Translated using Weblate (Turkish)
Currently translated at 100.0% (833 of 833 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (829 of 829 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (827 of 827 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (826 of 826 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (820 of 820 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (815 of 815 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (815 of 815 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-05-27 16:24:54 +02:00
Nicola Bortoletto
ac4682d62c Translated using Weblate (Italian)
Currently translated at 100.0% (833 of 833 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (829 of 829 strings)

Translated using Weblate (Italian)

Currently translated at 99.8% (828 of 829 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (827 of 827 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (826 of 826 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (825 of 825 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (824 of 824 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (820 of 820 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (815 of 815 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-05-27 16:24:54 +02:00
arasseo.
3afb446564 only update domain 2025-05-27 17:24:49 +03:00
Koitharu
0ed2232ac2 Update parsers 2025-05-27 17:03:14 +03:00
Koitharu
8d9129daaf Fix serivce starting crash on startup
(cherry picked from commit 157d5e6c05)
2025-05-27 16:57:01 +03:00
Koitharu
f799606688 Fix snackbar positioning
(cherry picked from commit b1497f2ace)
2025-05-27 16:56:24 +03:00
Koitharu
64adc4f58d Fix race condition while js evaluation
(cherry picked from commit 41d7fd1b86)
2025-05-27 16:56:18 +03:00
Koitharu
f6aad3355a Update parsers
(cherry picked from commit 19d0fe97a0)
2025-05-27 16:56:14 +03:00
Koitharu
0badf10a8b Fix WebView crash
(cherry picked from commit fa37c72923)
2025-05-27 16:54:56 +03:00
Koitharu
e5118f5266 Amoled background for search view 2025-05-27 16:47:10 +03:00
Koitharu
157d5e6c05 Fix serivce starting crash on startup 2025-05-26 18:25:17 +03:00
Koitharu
a02a8ff9db Fix WebView crash 2025-05-26 18:21:57 +03:00
Koitharu
b1497f2ace Fix snackbar positioning 2025-05-25 19:54:56 +03:00
Koitharu
099590c419 AdBlock for WebView 2025-05-25 19:30:55 +03:00
Koitharu
41d7fd1b86 Fix race condition while js evaluation 2025-05-25 10:05:10 +03:00
Koitharu
d3d7912bb8 Improve tablet navigation 2025-05-25 09:56:31 +03:00
Koitharu
12f1ffd019 Handle search action 2025-05-24 21:26:40 +03:00
Koitharu
19d0fe97a0 Update parsers 2025-05-24 21:13:33 +03:00
Koitharu
771954ffb8 Fix main activity navigation icon click 2025-05-24 10:29:34 +03:00
Koitharu
f4997f5a7f Improve captcha notifications 2025-05-24 10:18:18 +03:00
Koitharu
ff5a873d3b Fix main menu 2025-05-23 07:25:33 +03:00
Koitharu
1b5720f2a5 Migrate to mdc search view 2025-05-22 18:48:56 +03:00
Koitharu
a52730fff0 Improve mouse accessibility 2025-05-21 14:01:42 +03:00
Koitharu
2dfc9b75a2 Merge branch 'master' into devel 2025-05-19 19:55:39 +03:00
Koitharu
cc6f004e0e Fix themes 2025-05-19 19:49:34 +03:00
Koitharu
fa37c72923 Fix WebView crash 2025-05-19 19:11:46 +03:00
Koitharu
ab2235d0ca Update parsers 2025-05-18 14:40:45 +03:00
Koitharu
cbf707b403 Fix locales config
(cherry picked from commit 61c068d4ee)
2025-05-18 14:36:09 +03:00
Koitharu
8971c7a6a2 Fix color filte activity strings
(cherry picked from commit 8f8abcc3f6)
2025-05-18 14:35:19 +03:00
Koitharu
1576c9cdde Fix search menu item duplication
(cherry picked from commit a4b9acd622)
2025-05-18 14:34:44 +03:00
Koitharu
a0b8603510 Update dependencies 2025-05-18 14:32:01 +03:00
Koitharu
5b899b16d0 Update expressive theme 2025-05-15 19:45:51 +03:00
Koitharu
a4b9acd622 Fix search menu item duplication 2025-05-15 19:37:46 +03:00
Koitharu
c458f1eafb Show changelog in settings 2025-05-15 19:29:40 +03:00
Koitharu
8f8abcc3f6 Fix color filte activity strings 2025-05-15 17:45:21 +03:00
Koitharu
b4b9f90edc Improve mouse accessibility 2025-05-12 20:30:19 +03:00
Koitharu
7cc777f0a6 Show chapters count for branches 2025-05-12 19:34:29 +03:00
Koitharu
61c068d4ee Fix locales config 2025-05-12 18:53:36 +03:00
Koitharu
ff021b56f4 Styles fixes 2025-05-11 19:18:39 +03:00
Koitharu
94ef64c4b7 Improve mouse interaction 2025-05-11 18:46:05 +03:00
Koitharu
8ad28fd509 Fix keyboard navigation direction 2025-05-11 17:35:45 +03:00
Koitharu
7148ebcf34 Cleanup themes 2025-05-11 17:33:54 +03:00
Koitharu
1229e9626e Move bookmark button to bottom reader bar 2025-05-11 16:26:17 +03:00
Koitharu
4ec9a91644 Save bookmark images 2025-05-11 15:16:49 +03:00
Koitharu
1bbe1204e6 Update parsers 2025-05-11 14:25:22 +03:00
Koitharu
3aaddfd513 Theme fixes 2025-05-10 12:59:45 +03:00
Koitharu
f5514728fe Update themes inheritance structure 2025-05-08 21:56:23 +03:00
Koitharu
4fcb3a969b Fix per-app locale selection 2025-05-08 21:56:22 +03:00
Koitharu
23f3182769 Update parsers 2025-05-08 21:56:22 +03:00
Koitharu
beba4f029a Fix per-app locale selection 2025-05-08 20:46:58 +03:00
Koitharu
7cf7a62881 Update details info card color
(cherry picked from commit 8d325aea0a)
2025-05-08 19:36:16 +03:00
kadirkid
c1e84715fb Switch per language support to manual
The current automatic support setup has a bug where the app language will change for users with Android 15 when there is a configuration change like rotating a screen. It seems that that using generateLocaleConfig on AGP 8.8+ triggers a bug in Android 15 (android:defaultLocale) which causes this issue

(cherry picked from commit 104d8da655)
2025-05-08 19:36:08 +03:00
Koitharu
a3cc5726ee Update parsers 2025-05-08 19:35:45 +03:00
Draken
b84e10e69f [Readme] Update informations 2025-05-08 19:32:20 +03:00
Draken
ce3a1969c8 [Readme] Update infomations 2025-05-08 19:32:20 +03:00
Draken
8282ca7d60 [Readme] Add certificate fingerprints 2025-05-08 19:32:20 +03:00
kadirkid
104d8da655 Switch per language support to manual
The current automatic support setup has a bug where the app language will change for users with Android 15 when there is a configuration change like rotating a screen. It seems that that using generateLocaleConfig on AGP 8.8+ triggers a bug in Android 15 (android:defaultLocale) which causes this issue
2025-05-08 19:14:54 +03:00
Koitharu
52c39ad40c Ask for one-time incognito for nsfw manga 2025-05-03 10:55:17 +03:00
Koitharu
842ecaaff6 Merge branch 'master' into devel 2025-05-03 09:26:21 +03:00
Koitharu
8d325aea0a Update details info card color 2025-05-03 09:16:23 +03:00
Koitharu
3023c02f12 Update parsers 2025-05-03 08:37:46 +03:00
Koitharu
efff034dc6 Remove duplicated warnlist tags 2025-05-03 08:33:36 +03:00
Draken
2bb5673446 Update tags_warnlist
(cherry picked from commit 8d78b19128)
2025-05-03 08:31:39 +03:00
Koitharu
6cb090309a Merge pull request #1392 from dragonx943/patch-1
Update "dangerous" tags list
2025-05-03 08:30:55 +03:00
Koitharu
0983885fa2 Update private notifications visibility 2025-05-03 08:30:36 +03:00
Draken
8d78b19128 Update tags_warnlist 2025-05-02 21:17:22 +07:00
Koitharu
4449996a91 Fix search suggestions
(cherry picked from commit 1a8045b89f)
2025-05-02 14:44:39 +03:00
Koitharu
9cf496b7c4 AVIF images downsampling
(cherry picked from commit 5d890cb3d0)
2025-05-02 14:44:25 +03:00
Koitharu
4fb1db47ab Fix image loading
(cherry picked from commit 257f583f78)
2025-05-02 14:44:12 +03:00
Koitharu
5d890cb3d0 AVIF images downsampling 2025-05-02 14:38:12 +03:00
Koitharu
257f583f78 Fix image loading 2025-04-30 17:27:41 +03:00
Koitharu
d45bab3879 Fix reader activity ui 2025-04-30 16:14:32 +03:00
Koitharu
c871255eb7 Improve reader scroll timer 2025-04-28 19:08:11 +03:00
Koitharu
1a8045b89f Fix search suggestions 2025-04-27 15:28:48 +03:00
Koitharu
f91f55fa66 Fix warnings and code cleanup 2025-04-26 09:31:05 +03:00
Koitharu
10bd46f077 Refactor image loading 2025-04-25 19:50:48 +03:00
Koitharu
bd4fecc3b6 Option to override manga title and cover 2025-04-20 17:20:42 +03:00
Koitharu
14b89fbee2 Use pagination for bookmarks backup 2025-04-19 08:31:47 +03:00
Koitharu
8291c55fc9 Fix some database-related crashes 2025-04-19 08:16:08 +03:00
Koitharu
d542fa6bb6 Update database structure and migrate from kapt to ksp 2025-04-15 20:07:26 +03:00
Koitharu
46ddcb7518 Update page loading ui 2025-04-13 11:33:04 +03:00
Koitharu
cf2d1aa6fb Fix main navbar height 2025-04-13 11:21:29 +03:00
Koitharu
ab3dd8aacb Merge pull request #1374 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-04-13 10:56:48 +03:00
srntskl-111
ae868fa9d1 Translated using Weblate (Indonesian)
Currently translated at 98.8% (805 of 814 strings)

Co-authored-by: srntskl-111 <maskhraish@outlook.co.id>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-04-13 09:55:44 +02:00
Frosted
4ecbf5978e Translated using Weblate (Turkish)
Currently translated at 100.0% (814 of 814 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-04-13 09:55:43 +02:00
Koitharu
31586cf48f Fix avif image decoding 2025-04-13 10:47:37 +03:00
Koitharu
3725a6e58f Update page loading ui 2025-04-13 10:01:24 +03:00
Koitharu
313c2ab2bf Respect rounded corners for page numbers (#1360) 2025-04-08 09:09:36 +03:00
Koitharu
fe5d37f45e Fix hiding page loading indicator (close #1357) 2025-04-08 09:09:36 +03:00
Koitharu
92f6221ba0 Merge pull request #1367 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-04-08 09:03:34 +03:00
大王叫我来巡山
0590a0c56f Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (811 of 814 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-04-07 18:01:57 +02:00
Koitharu
13ffc3a515 Translated using Weblate (Russian)
Currently translated at 100.0% (814 of 814 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2025-04-07 18:01:55 +02:00
Nicola Bortoletto
74b36226f2 Translated using Weblate (Italian)
Currently translated at 100.0% (814 of 814 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-04-07 18:01:53 +02:00
Boqirz
d501d0304a Translated using Weblate (Indonesian)
Currently translated at 98.7% (804 of 814 strings)

Co-authored-by: Boqirz <alveromodar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-04-07 18:01:51 +02:00
Draken
1059933c87 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (814 of 814 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (814 of 814 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-04-07 18:01:49 +02:00
Koitharu
5fa58b931e Fix Cloudflare protection resolving 2025-04-06 18:10:34 +03:00
Koitharu
ddecc72de7 Update page state management in reader 2025-04-06 16:26:13 +03:00
Koitharu
d35a0c5e1e Allow to open reader when details is not loaded yet 2025-04-03 19:41:44 +03:00
Koitharu
340994ce77 Fix reader slider behavior 2025-04-03 13:59:26 +03:00
Koitharu
42b2f21c4d Fix bottom navigation insets #1341 2025-04-03 13:19:10 +03:00
Koitharu
e4b9da54dd Update parsers 2025-04-03 12:22:51 +03:00
Koitharu
ccc41314ae UI fixes 2025-04-03 12:21:06 +03:00
Koitharu
93eb6a19a5 Update page loading ui 2025-04-03 12:21:06 +03:00
Koitharu
e4f2e19d2c Merge pull request #1358 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-04-03 12:19:44 +03:00
Thinker
73a687c9a7 Translated using Weblate (Bengali)
Currently translated at 21.8% (178 of 814 strings)

Co-authored-by: Thinker <sayakkundu711@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
2025-04-03 03:49:15 +02:00
Draken
32ca3c11fa Translated using Weblate (Vietnamese)
Currently translated at 100.0% (814 of 814 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-04-03 03:49:14 +02:00
Deivinni Silva
0d648dd188 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (814 of 814 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-04-03 03:49:14 +02:00
Frosted
86b7989c89 Translated using Weblate (Turkish)
Currently translated at 100.0% (814 of 814 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-04-03 03:49:14 +02:00
Alvoracz
01be6ab596 Translated using Weblate (Czech)
Currently translated at 99.3% (809 of 814 strings)

Co-authored-by: Alvoracz <sedlor@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2025-04-03 03:49:14 +02:00
Infy's Tagalog Translations
a3d01e8d34 Translated using Weblate (Filipino)
Currently translated at 99.7% (812 of 814 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-04-03 03:49:14 +02:00
gekka
808bd47b64 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (811 of 814 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-04-03 03:49:13 +02:00
Nicola Bortoletto
f4b506b26b Translated using Weblate (Italian)
Currently translated at 99.8% (813 of 814 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2025-04-03 03:49:13 +02:00
Koitharu
1f0d2e2039 Fix crash when open non-http url in browser 2025-03-31 10:19:31 +03:00
Koitharu
e3e315e2a6 Skip local directories with .notamanga file 2025-03-30 10:43:05 +03:00
Koitharu
bfc733784f Option to disable dangerous genres highlighting #1348 2025-03-30 10:34:27 +03:00
Koitharu
3ff25de252 Fix crash 2025-03-30 10:21:42 +03:00
Koitharu
3c726c1c56 Update dependencies 2025-03-30 10:21:42 +03:00
Koitharu
9cb7ff691f Merge pull request #1349 from weblate/weblate-kotatsu-strings
Translations update from Hosted Weblate
2025-03-30 10:21:06 +03:00
lenn
645ae3124f Translated using Weblate (Polish)
Currently translated at 96.0% (780 of 812 strings)

Co-authored-by: lenn <l3ennec@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2025-03-29 14:02:13 +01:00
Ore Ki
a3d1922913 Translated using Weblate (Indonesian)
Currently translated at 100.0% (812 of 812 strings)

Co-authored-by: Ore Ki <ramadrizkyyy@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2025-03-29 14:02:11 +01:00
gekka
62d2ea8f15 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (809 of 812 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.5% (808 of 812 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-03-29 14:02:09 +01:00
Draken
823752076b Translated using Weblate (Vietnamese)
Currently translated at 100.0% (812 of 812 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (812 of 812 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
2025-03-29 14:02:06 +01:00
Lorenzo Stella
3cbd392c72 Translated using Weblate (English)
Currently translated at 100.0% (812 of 812 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 2.4% (20 of 812 strings)

Translated using Weblate (English)

Currently translated at 100.0% (812 of 812 strings)

Translated using Weblate (English)

Currently translated at 100.0% (812 of 812 strings)

Co-authored-by: Lorenzo Stella <lorenzo.stella.1408@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en_GB/
Translation: Kotatsu/Strings
2025-03-29 14:02:04 +01:00
Anonymous
57f62f5860 Translated using Weblate (Malayalam)
Currently translated at 2.8% (23 of 812 strings)

Translated using Weblate (Sinhala)

Currently translated at 2.7% (22 of 812 strings)

Translated using Weblate (Croatian)

Currently translated at 97.5% (792 of 812 strings)

Translated using Weblate (Hungarian)

Currently translated at 76.3% (620 of 812 strings)

Translated using Weblate (Malay)

Currently translated at 38.4% (312 of 812 strings)

Translated using Weblate (Estonian)

Currently translated at 67.6% (549 of 812 strings)

Translated using Weblate (Thai)

Currently translated at 55.7% (453 of 812 strings)

Translated using Weblate (Czech)

Currently translated at 98.0% (796 of 812 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.1% (805 of 812 strings)

Translated using Weblate (Kazakh)

Currently translated at 66.9% (544 of 812 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 46.9% (381 of 812 strings)

Translated using Weblate (Filipino)

Currently translated at 98.2% (798 of 812 strings)

Translated using Weblate (Hindi)

Currently translated at 82.2% (668 of 812 strings)

Translated using Weblate (Polish)

Currently translated at 93.1% (756 of 812 strings)

Translated using Weblate (Korean)

Currently translated at 41.9% (341 of 812 strings)

Translated using Weblate (Greek)

Currently translated at 66.2% (538 of 812 strings)

Translated using Weblate (Serbian)

Currently translated at 97.9% (795 of 812 strings)

Translated using Weblate (Arabic)

Currently translated at 98.7% (802 of 812 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 80.9% (657 of 812 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.0% (804 of 812 strings)

Translated using Weblate (Ukrainian)

Currently translated at 81.6% (663 of 812 strings)

Translated using Weblate (Russian)

Currently translated at 98.5% (800 of 812 strings)

Translated using Weblate (Japanese)

Currently translated at 57.1% (464 of 812 strings)

Translated using Weblate (Turkish)

Currently translated at 99.6% (809 of 812 strings)

Translated using Weblate (French)

Currently translated at 99.6% (809 of 812 strings)

Translated using Weblate (Portuguese)

Currently translated at 96.6% (785 of 812 strings)

Translated using Weblate (Italian)

Currently translated at 99.5% (808 of 812 strings)

Translated using Weblate (German)

Currently translated at 78.3% (636 of 812 strings)

Translated using Weblate (Spanish)

Currently translated at 94.9% (771 of 812 strings)

Translated using Weblate (Belarusian)

Currently translated at 92.1% (748 of 812 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/et/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ko/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ml/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nn/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/si/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2025-03-29 14:02:02 +01:00
Koitharu
648fab6be5 Fix Open online manga variant 2025-03-27 20:39:36 +02:00
Koitharu
817ae68e67 Option to toggle ssiv debug 2025-03-26 19:24:32 +02:00
Koitharu
7c4b91ddc4 UI Fixes 2025-03-26 19:23:06 +02:00
Koitharu
d54e015195 Fix Favorite/Favourite strings 2025-03-25 10:08:16 +02:00
Koitharu
e369d1ba9d Merge remote-tracking branch 'weblate/devel' into devel 2025-03-25 10:01:27 +02:00
Koitharu
1a4358998b Merge pull request #1334 from Arararararagi/patch-1
typos and coherence check in strings
2025-03-25 09:47:37 +02:00
MrCrystallized
c53a833d9d Translated using Weblate (Bengali)
Currently translated at 22.1% (180 of 812 strings)

Translated using Weblate (Bengali)

Currently translated at 88.8% (8 of 9 strings)

Co-authored-by: MrCrystallized <mrcrystallizedbruh@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-03-24 21:54:54 +01:00
Lorenzo Stella
afff700ad3 Translated using Weblate (Romanian)
Currently translated at 11.0% (90 of 812 strings)

Translated using Weblate (Khmer (Central))

Currently translated at 17.7% (144 of 812 strings)

Translated using Weblate (Latvian)

Currently translated at 21.6% (176 of 812 strings)

Translated using Weblate (Lithuanian)

Currently translated at 5.5% (45 of 812 strings)

Translated using Weblate (Catalan)

Currently translated at 10.9% (89 of 812 strings)

Translated using Weblate (Hungarian)

Currently translated at 76.6% (622 of 812 strings)

Translated using Weblate (Punjabi)

Currently translated at 4.1% (34 of 812 strings)

Translated using Weblate (Malay)

Currently translated at 38.6% (314 of 812 strings)

Translated using Weblate (Thai)

Currently translated at 56.0% (455 of 812 strings)

Translated using Weblate (Kazakh)

Currently translated at 67.2% (546 of 812 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 47.1% (383 of 812 strings)

Translated using Weblate (Nepali)

Currently translated at 30.5% (248 of 812 strings)

Translated using Weblate (Hindi)

Currently translated at 82.6% (671 of 812 strings)

Translated using Weblate (Polish)

Currently translated at 93.5% (760 of 812 strings)

Translated using Weblate (Korean)

Currently translated at 42.2% (343 of 812 strings)

Translated using Weblate (Greek)

Currently translated at 66.5% (540 of 812 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 81.2% (660 of 812 strings)

Translated using Weblate (Ukrainian)

Currently translated at 82.0% (666 of 812 strings)

Translated using Weblate (Japanese)

Currently translated at 57.3% (466 of 812 strings)

Translated using Weblate (Persian)

Currently translated at 35.5% (289 of 812 strings)

Translated using Weblate (Finnish)

Currently translated at 32.0% (260 of 812 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (812 of 812 strings)

Translated using Weblate (German)

Currently translated at 78.6% (639 of 812 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 35.4% (288 of 812 strings)

Translated using Weblate (Spanish)

Currently translated at 95.4% (775 of 812 strings)

Translated using Weblate (English)

Currently translated at 100.0% (812 of 812 strings)

Co-authored-by: Lorenzo Stella <lorenzo.stella.1408@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
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/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/km/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ko/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/lv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nn/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pa/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ro/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2025-03-24 21:54:54 +01:00
Anon
5bc00bc7f5 Translated using Weblate (Serbian)
Currently translated at 98.2% (798 of 812 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2025-03-24 21:54:54 +01:00
Dragibus Noir
e2ace90cdb Translated using Weblate (French)
Currently translated at 100.0% (812 of 812 strings)

Co-authored-by: Dragibus Noir <dragibusnoir@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2025-03-24 21:54:54 +01:00
Lumiini
1afbd2b6a8 Translated using Weblate (Finnish)
Currently translated at 66.6% (6 of 9 strings)

Co-authored-by: Lumiini <bennokaynak@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fi/
Translation: Kotatsu/plurals
2025-03-24 21:54:53 +01:00
Frosted
d36c5af0c4 Translated using Weblate (Turkish)
Currently translated at 100.0% (812 of 812 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-24 21:54:53 +01:00
Nicola Bortoletto
705bb2b084 Translated using Weblate (Italian)
Currently translated at 100.0% (812 of 812 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-24 21:54:53 +01:00
gekka
a208d13930 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (808 of 812 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2025-03-24 21:54:53 +01:00
Kusou
44d8861b7f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (808 of 808 strings)

Co-authored-by: Kusou <orion26br@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2025-03-24 21:54:53 +01:00
ruban s
9821f06ca1 Translated using Weblate (Tamil)
Currently translated at 100.0% (808 of 808 strings)

Co-authored-by: ruban s <rubans013@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ta/
Translation: Kotatsu/Strings
2025-03-24 21:54:52 +01:00
Amato Fugosi
92f9f56f59 Translated using Weblate (Bengali)
Currently translated at 19.9% (161 of 808 strings)

Translated using Weblate (Bengali)

Currently translated at 88.8% (8 of 9 strings)

Co-authored-by: Amato Fugosi <fugosiamato@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2025-03-24 21:54:52 +01:00
Koitharu
424c4d8827 Multiple authors support 2025-03-24 14:18:04 +02:00
Koitharu
24cf2a2725 Use page preview in reader while loading 2025-03-22 16:18:35 +02:00
Koitharu
1a5c3c1f6f Update dependencies 2025-03-21 09:03:31 +02:00
Koitharu
0b8fbf892a Improve details activity 2025-03-20 18:00:58 +02:00
Koitharu
a2f9356b8a Fix crashes 2025-03-20 08:20:43 +02:00
Koitharu
7003463bac Option to use disabled sources for suggestions 2025-03-20 07:52:08 +02:00
Koitharu
7a663fa9c1 Merge branch 'devel' 2025-03-18 19:36:42 +02:00
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
I bit my tongue
9cc1cdac62 typos and coherence check 2025-03-16 21:13:54 +00: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
8d7f44d2da Fix DateTimeAgo formatting
(cherry picked from commit 5f0514638a)
2025-02-24 18:09:03 +02:00
Koitharu
930d4dfd83 Fix checking for removed manga updates (close #1064)
(cherry picked from commit 1b8d35d424)
2025-02-24 18:07:49 +02:00
Koitharu
290cb652ee Update parsers 2025-02-24 18:05:51 +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
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
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
734846a018 Fix checking for new chapters in some cases (#1212, #1195, #1190) 2024-12-18 18:24:41 +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
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
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
66645d93f8 Update parsers 2024-12-05 15:52:16 +02:00
Koitharu
f2582bce1d Update dependencies 2024-12-03 14:36:47 +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
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
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
Mac135135
7c74c87524 Merge remote-tracking branch 'origin/devel' into devel 2024-09-07 15:26:13 +03:00
Mac135135
f86ee7d5c2 Merge remote-tracking branch 'origin/devel' into devel 2024-08-04 19:46:52 +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
1052 changed files with 36423 additions and 18387 deletions

View File

@@ -4,7 +4,7 @@ root = true
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1,16 @@
name: Trigger Site Update
on:
release:
types: [published]
jobs:
trigger-site:
runs-on: ubuntu-latest
steps:
- name: Send repository_dispatch to site-repo
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SITE_REPO_TOKEN }}
repository: KotatsuApp/website
event-type: app-release

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@
/.idea/dictionaries
/.idea/modules.xml
/.idea/misc.xml
/.idea/markdown.xml
/.idea/discord.xml
/.idea/compiler.xml
/.idea/workspace.xml

2
.idea/.gitignore generated vendored
View File

@@ -3,3 +3,5 @@
/workspace.xml
/migrations.xml
/runConfigurations.xml
/appInsightsSettings.xml
/kotlinCodeInsightSettings.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>

26
.idea/appInsightsSettings.xml generated Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="tabSettings">
<map>
<entry key="Firebase Crashlytics">
<value>
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@@ -1,9 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="USE_TAB_CHARACTER" value="true" />
</value>
<value />
</option>
<AndroidXmlCodeStyleSettings>
<option name="LAYOUT_SETTINGS">
@@ -22,40 +20,46 @@
</value>
</option>
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="true" />
<package name="androidx" withSubpackages="true" static="true" />
<package name="com" withSubpackages="true" static="true" />
<package name="junit" withSubpackages="true" static="true" />
<package name="net" withSubpackages="true" static="true" />
<package name="org" withSubpackages="true" static="true" />
<package name="java" withSubpackages="true" static="true" />
<package name="javax" withSubpackages="true" static="true" />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="androidx" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="ALLOW_TRAILING_COMMA_COLLECTION_LITERAL_EXPRESSION" value="true" />
<option name="ALLOW_TRAILING_COMMA_VALUE_ARGUMENT_LIST" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="CMake">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Groovy">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="HTML">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Shell Script">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
@@ -64,7 +68,6 @@
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
<arrangement>
<rules>
@@ -179,9 +182,6 @@
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

3
.idea/gradle.xml generated
View File

@@ -13,8 +13,7 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
</project>

2
.idea/vcs.xml generated
View File

@@ -10,6 +10,6 @@
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

112
README.md
View File

@@ -1,57 +1,115 @@
# Kotatsu
> [!IMPORTANT]
> In light of recent challenges — including threating actions from Kakao Entertainment Corp and upcoming Googles
> [new sideloading policy](https://f-droid.org/ru/2025/10/28/sideloading.html) — weve made the difficult decision to shut down Kotatsu and end its support. Were deeply grateful
> to everyone who contributed and to the amazing community that grew around this project.
Kotatsu is a free and open-source manga reader for Android with built-in online content sources.
---
[![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) ![F-Droid Version](https://img.shields.io/f-droid/v/org.koitharu.kotatsu) ![Android 5.0](https://img.shields.io/badge/android-5.0+-brightgreen) [![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) [![License](https://img.shields.io/github/license/KotatsuApp/Kotatsu)](https://github.com/KotatsuApp/Kotatsu/blob/devel/LICENSE)
<div align="center">
### Download
**[Kotatsu](https://github.com/KotatsuApp/Kotatsu) is a free and open-source manga reader for Android with built-in
online content sources.**
- **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).
![Android 6.0](https://img.shields.io/badge/android-6.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)
### Main Features
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers)
* Search manga by name, genres, and more filters
* Reading history and bookmarks
<div align="left">
* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers) (with 1200+ manga sources)
* Search manga by name, genres and more filters
* Favorites 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 customizable reader
* Notifications about new chapters with updates feed
* 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-protected access to the app
* Password / fingerprint-protected access to the app
* Automatically sync app data with other devices on the same account
* Support for older devices running Android 6.0+
### 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**
### Certificate fingerprints
```plaintext
2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE
```
```plaintext
67:E1:51:00:BB:80:93:01:78:3E:DC:B6:34:8F:A3:BB:F8:30:34:D9:1E:62:86:8A:91:05:3D:BD:70:DB:3F:18
```
### License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
<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 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 align="left">
The developers of this application do not have any affiliation with the content available in the app and does not store
or distribute any content. This application should be considered a web browser, all content that can be found using this
application is freely available on the Internet. All DMCA takedown requests should be sent to the owners of the website
where the content is hosted.
</div>

View File

@@ -3,32 +3,41 @@ import java.time.LocalDateTime
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'com.google.devtools.ksp'
id 'kotlin-parcelize'
id 'dagger.hilt.android.plugin'
id 'androidx.room'
id 'org.jetbrains.kotlin.plugin.serialization'
// enable if needed
// id 'dev.reformator.stacktracedecoroutinator'
}
android {
compileSdk = 35
compileSdk = 36
buildToolsVersion = '35.0.0'
namespace = 'org.koitharu.kotatsu'
defaultConfig {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 35
versionCode = 697
versionName = '7.7.5'
minSdk = 23
targetSdk = 36
versionCode = 1033
versionName = '9.4.1'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
arg('room.generateKotlin', 'true')
arg('room.schemaLocation', "$projectDir/schemas")
}
androidResources {
generateLocaleConfig true
// https://issuetracker.google.com/issues/408030127
generateLocaleConfig false
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localProperties.load(new FileInputStream(localPropertiesFile))
}
resValue 'string', 'tg_backup_bot_token', localProperties.getProperty('tg_backup_bot_token', '')
}
buildTypes {
debug {
@@ -71,11 +80,20 @@ android {
'-opt-in=kotlin.ExperimentalStdlibApi',
'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
'-opt-in=kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi',
'-opt-in=kotlinx.coroutines.InternalForInheritanceCoroutinesApi',
'-opt-in=kotlinx.coroutines.FlowPreview',
'-opt-in=kotlin.contracts.ExperimentalContracts',
'-opt-in=coil3.annotation.ExperimentalCoilApi',
'-opt-in=coil3.annotation.InternalCoilApi',
'-opt-in=kotlinx.serialization.ExperimentalSerializationApi',
'-Xjspecify-annotations=strict',
'-Xannotation-default-target=first-only',
'-Xtype-enhancement-improvements-strict-mode'
]
}
room {
schemaDirectory "$projectDir/schemas"
}
lint {
abortOnError true
disable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled', 'SimpleDateFormat'
@@ -97,13 +115,6 @@ android {
}
}
}
afterEvaluate {
compileDebugKotlin {
kotlinOptions {
freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']
}
}
}
dependencies {
def parsersVersion = libs.versions.parsers.get()
if (System.properties.containsKey('parsersVersionOverride')) {
@@ -131,6 +142,7 @@ dependencies {
implementation libs.lifecycle.service
implementation libs.lifecycle.process
implementation libs.androidx.constraintlayout
implementation libs.androidx.documentfile
implementation libs.androidx.swiperefreshlayout
implementation libs.androidx.recyclerview
implementation libs.androidx.viewpager2
@@ -143,6 +155,9 @@ dependencies {
implementation libs.androidx.work.runtime
implementation libs.guava
// Foldable/Window layout
implementation libs.androidx.window
implementation libs.androidx.room.runtime
implementation libs.androidx.room.ktx
ksp libs.androidx.room.compiler
@@ -151,14 +166,15 @@ dependencies {
implementation libs.okhttp.tls
implementation libs.okhttp.dnsoverhttps
implementation libs.okio
implementation libs.kotlinx.serialization.json
implementation libs.adapterdelegates
implementation libs.adapterdelegates.viewbinding
implementation libs.hilt.android
kapt libs.hilt.compiler
ksp libs.hilt.compiler
implementation libs.androidx.hilt.work
kapt libs.androidx.hilt.compiler
ksp libs.androidx.hilt.compiler
implementation libs.coil.core
implementation libs.coil.network
@@ -168,6 +184,7 @@ dependencies {
implementation libs.ssiv
implementation libs.disk.lru.cache
implementation libs.markwon
implementation libs.kizzyrpc
implementation libs.acra.http
implementation libs.acra.dialog
@@ -175,6 +192,7 @@ dependencies {
implementation libs.conscrypt.android
debugImplementation libs.leakcanary.android
nightlyImplementation libs.leakcanary.android
debugImplementation libs.workinspector
testImplementation libs.junit
@@ -192,5 +210,5 @@ dependencies {
androidTestImplementation libs.moshi.kotlin
androidTestImplementation libs.hilt.android.testing
kaptAndroidTest libs.hilt.android.compiler
kspAndroidTest libs.hilt.android.compiler
}

View File

@@ -8,8 +8,7 @@
public static void checkParameterIsNotNull(...);
public static void checkNotNullParameter(...);
}
-keep public class ** extends org.koitharu.kotatsu.core.ui.BaseFragment
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
@@ -17,10 +16,12 @@
-dontwarn com.google.j2objc.annotations.**
-dontwarn coil3.PlatformContext
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
-keep class org.koitharu.kotatsu.settings.about.changelog.ChangelogFragment
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
-keep class org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy { *; }
-keep class org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment { *; }
-keep class org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment { *; }
-keep class org.jsoup.parser.Tag
-keep class org.jsoup.internal.StringUtil

View File

@@ -1,6 +1,7 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"altTitles": [],
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
@@ -29,13 +30,15 @@
}
],
"state": "FINISHED",
"authors": [],
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 1552943969433540704,
"name": "1 - 1",
"title": "1 - 1",
"number": 1,
"volume": 0,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -43,8 +46,9 @@
},
{
"id": 1552943969433540705,
"name": "1 - 2",
"title": "1 - 2",
"number": 2,
"volume": 0,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -52,8 +56,9 @@
},
{
"id": 1552943969433540706,
"name": "1 - 3",
"title": "1 - 3",
"number": 3,
"volume": 0,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -61,8 +66,9 @@
},
{
"id": 1552943969433540707,
"name": "1 - 4",
"title": "1 - 4",
"number": 4,
"volume": 0,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -70,8 +76,9 @@
},
{
"id": 1552943969433540708,
"name": "1 - 5",
"title": "1 - 5",
"number": 5,
"volume": 0,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -79,8 +86,9 @@
},
{
"id": 1552943969433541665,
"name": "2 - 1",
"title": "2 - 1",
"number": 6,
"volume": 0,
"url": "/stranstviia_emanon/vol2/1",
"scanlator": "Sup!",
"uploadDate": 1415570400000,
@@ -88,8 +96,9 @@
},
{
"id": 1552943969433541666,
"name": "2 - 2",
"title": "2 - 2",
"number": 7,
"volume": 0,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
@@ -97,8 +106,9 @@
},
{
"id": 1552943969433541667,
"name": "2 - 3",
"title": "2 - 3",
"number": 8,
"volume": 0,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
@@ -106,8 +116,9 @@
},
{
"id": 1552943969433541668,
"name": "2 - 4",
"title": "2 - 4",
"number": 9,
"volume": 0,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
@@ -115,8 +126,9 @@
},
{
"id": 1552943969433541669,
"name": "2 - 5",
"title": "2 - 5",
"number": 10,
"volume": 0,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
@@ -124,8 +136,9 @@
},
{
"id": 1552943969433541670,
"name": "2 - 6",
"title": "2 - 6",
"number": 11,
"volume": 0,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
@@ -133,8 +146,9 @@
},
{
"id": 1552943969433542626,
"name": "3 - 1",
"title": "3 - 1",
"number": 12,
"volume": 0,
"url": "/stranstviia_emanon/vol3/1",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
@@ -142,8 +156,9 @@
},
{
"id": 1552943969433542627,
"name": "3 - 2",
"title": "3 - 2",
"number": 13,
"volume": 0,
"url": "/stranstviia_emanon/vol3/2",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
@@ -151,8 +166,9 @@
},
{
"id": 1552943969433542628,
"name": "3 - 3",
"title": "3 - 3",
"number": 14,
"volume": 0,
"url": "/stranstviia_emanon/vol3/3",
"scanlator": "",
"uploadDate": 1465851600000,
@@ -160,4 +176,4 @@
}
],
"source": "READMANGA_RU"
}
}

View File

@@ -1,6 +1,7 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"altTitles": [],
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
@@ -29,8 +30,9 @@
}
],
"state": "FINISHED",
"authors": [],
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [],
"source": "READMANGA_RU"
}
}

View File

@@ -1,6 +1,7 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"altTitles": [],
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
@@ -29,13 +30,15 @@
}
],
"state": "FINISHED",
"authors": [],
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 3552943969433540704,
"name": "1 - 1",
"title": "1 - 1",
"number": 1,
"volume": 0,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -43,8 +46,9 @@
},
{
"id": 3552943969433540705,
"name": "1 - 2",
"title": "1 - 2",
"number": 2,
"volume": 0,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -52,8 +56,9 @@
},
{
"id": 3552943969433540706,
"name": "1 - 3",
"title": "1 - 3",
"number": 3,
"volume": 0,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -61,8 +66,9 @@
},
{
"id": 3552943969433540707,
"name": "1 - 4",
"title": "1 - 4",
"number": 4,
"volume": 0,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -70,8 +76,9 @@
},
{
"id": 3552943969433540708,
"name": "1 - 5",
"title": "1 - 5",
"number": 5,
"volume": 0,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -79,8 +86,9 @@
},
{
"id": 3552943969433541665,
"name": "2 - 1",
"title": "2 - 1",
"number": 6,
"volume": 0,
"url": "/stranstviia_emanon/vol2/1",
"scanlator": "Sup!",
"uploadDate": 1415570400000,
@@ -88,8 +96,9 @@
},
{
"id": 3552943969433541666,
"name": "2 - 2",
"title": "2 - 2",
"number": 7,
"volume": 0,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
@@ -97,8 +106,9 @@
},
{
"id": 3552943969433541667,
"name": "2 - 3",
"title": "2 - 3",
"number": 8,
"volume": 0,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
@@ -106,8 +116,9 @@
},
{
"id": 3552943969433541668,
"name": "2 - 4",
"title": "2 - 4",
"number": 9,
"volume": 0,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
@@ -115,8 +126,9 @@
},
{
"id": 3552943969433541669,
"name": "2 - 5",
"title": "2 - 5",
"number": 10,
"volume": 0,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
@@ -124,8 +136,9 @@
},
{
"id": 3552943969433541670,
"name": "2 - 6",
"title": "2 - 6",
"number": 11,
"volume": 0,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
@@ -133,4 +146,4 @@
}
],
"source": "READMANGA_RU"
}
}

View File

@@ -1,6 +1,7 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"altTitles": [],
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
@@ -29,13 +30,15 @@
}
],
"state": "FINISHED",
"authors": [],
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 3552943969433540704,
"name": "1 - 1",
"title": "1 - 1",
"number": 1,
"volume": 0,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -43,8 +46,9 @@
},
{
"id": 3552943969433540705,
"name": "1 - 2",
"title": "1 - 2",
"number": 2,
"volume": 0,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -52,8 +56,9 @@
},
{
"id": 3552943969433540706,
"name": "1 - 3",
"title": "1 - 3",
"number": 3,
"volume": 0,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -61,8 +66,9 @@
},
{
"id": 3552943969433540707,
"name": "1 - 4",
"title": "1 - 4",
"number": 4,
"volume": 0,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -70,8 +76,9 @@
},
{
"id": 3552943969433540708,
"name": "1 - 5",
"title": "1 - 5",
"number": 5,
"volume": 0,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -79,8 +86,9 @@
},
{
"id": 3552943969433541665,
"name": "2 - 1",
"title": "2 - 1",
"number": 6,
"volume": 0,
"url": "/stranstviia_emanon/vol2/1",
"scanlator": "Sup!",
"uploadDate": 1415570400000,
@@ -88,8 +96,9 @@
},
{
"id": 3552943969433541666,
"name": "2 - 2",
"title": "2 - 2",
"number": 7,
"volume": 0,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
@@ -97,8 +106,9 @@
},
{
"id": 3552943969433541667,
"name": "2 - 3",
"title": "2 - 3",
"number": 8,
"volume": 0,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
@@ -106,8 +116,9 @@
},
{
"id": 3552943969433541668,
"name": "2 - 4",
"title": "2 - 4",
"number": 9,
"volume": 0,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
@@ -115,8 +126,9 @@
},
{
"id": 3552943969433541669,
"name": "2 - 5",
"title": "2 - 5",
"number": 10,
"volume": 0,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
@@ -124,8 +136,9 @@
},
{
"id": 3552943969433541670,
"name": "2 - 6",
"title": "2 - 6",
"number": 11,
"volume": 0,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
@@ -133,8 +146,9 @@
},
{
"id": 3552943969433542626,
"name": "3 - 1",
"title": "3 - 1",
"number": 12,
"volume": 0,
"url": "/stranstviia_emanon/vol3/1",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
@@ -142,8 +156,9 @@
},
{
"id": 3552943969433542627,
"name": "3 - 2",
"title": "3 - 2",
"number": 13,
"volume": 0,
"url": "/stranstviia_emanon/vol3/2",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
@@ -151,8 +166,9 @@
},
{
"id": 3552943969433542628,
"name": "3 - 3",
"title": "3 - 3",
"number": 14,
"volume": 0,
"url": "/stranstviia_emanon/vol3/3",
"scanlator": "",
"uploadDate": 1465851600000,
@@ -160,4 +176,4 @@
}
],
"source": "READMANGA_RU"
}
}

View File

@@ -1,6 +1,7 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"altTitles": [],
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
@@ -29,7 +30,8 @@
}
],
"state": "FINISHED",
"authors": [],
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": null,
"source": "READMANGA_RU"
}
}

View File

@@ -1,6 +1,7 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"altTitles": [],
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
@@ -29,13 +30,15 @@
}
],
"state": "FINISHED",
"authors": [],
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 3552943969433540704,
"name": "1 - 1",
"title": "1 - 1",
"number": 1,
"volume": 0,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -43,8 +46,9 @@
},
{
"id": 3552943969433540705,
"name": "1 - 2",
"title": "1 - 2",
"number": 2,
"volume": 0,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -52,8 +56,9 @@
},
{
"id": 3552943969433540706,
"name": "1 - 3",
"title": "1 - 3",
"number": 3,
"volume": 0,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -61,8 +66,9 @@
},
{
"id": 3552943969433540707,
"name": "1 - 4",
"title": "1 - 4",
"number": 4,
"volume": 0,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -70,8 +76,9 @@
},
{
"id": 3552943969433540708,
"name": "1 - 5",
"title": "1 - 5",
"number": 5,
"volume": 0,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
@@ -79,8 +86,9 @@
},
{
"id": 3552943969433541666,
"name": "2 - 2",
"title": "2 - 2",
"number": 7,
"volume": 0,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
@@ -88,8 +96,9 @@
},
{
"id": 3552943969433541667,
"name": "2 - 3",
"title": "2 - 3",
"number": 8,
"volume": 0,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
@@ -97,8 +106,9 @@
},
{
"id": 3552943969433541668,
"name": "2 - 4",
"title": "2 - 4",
"number": 9,
"volume": 0,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
@@ -106,8 +116,9 @@
},
{
"id": 3552943969433541669,
"name": "2 - 5",
"title": "2 - 5",
"number": 10,
"volume": 0,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
@@ -115,8 +126,9 @@
},
{
"id": 3552943969433541670,
"name": "2 - 6",
"title": "2 - 6",
"number": 11,
"volume": 0,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
@@ -124,8 +136,9 @@
},
{
"id": 3552943969433542626,
"name": "3 - 1",
"title": "3 - 1",
"number": 12,
"volume": 0,
"url": "/stranstviia_emanon/vol3/1",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
@@ -133,8 +146,9 @@
},
{
"id": 3552943969433542627,
"name": "3 - 2",
"title": "3 - 2",
"number": 13,
"volume": 0,
"url": "/stranstviia_emanon/vol3/2",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
@@ -142,8 +156,9 @@
},
{
"id": 3552943969433542628,
"name": "3 - 3",
"title": "3 - 3",
"number": 14,
"volume": 0,
"url": "/stranstviia_emanon/vol3/3",
"scanlator": "",
"uploadDate": 1465851600000,
@@ -151,4 +166,4 @@
}
],
"source": "READMANGA_RU"
}
}

View File

@@ -1,19 +1,29 @@
package org.koitharu.kotatsu
import androidx.test.platform.app.InstrumentationRegistry
import com.squareup.moshi.*
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okio.buffer
import okio.source
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.*
import org.koitharu.kotatsu.parsers.model.MangaSource
import java.time.Instant
import java.util.Date
import kotlin.reflect.KClass
object SampleData {
private val moshi = Moshi.Builder()
.add(DateAdapter())
.add(InstantAdapter())
.add(MangaSourceAdapter())
.add(KotlinJsonAdapterFactory())
.build()
@@ -51,4 +61,36 @@ object SampleData {
writer.value(value?.time ?: 0L)
}
}
}
private class MangaSourceAdapter : JsonAdapter<MangaSource>() {
@FromJson
override fun fromJson(reader: JsonReader): MangaSource? {
val name = reader.nextString() ?: return null
return MangaSource(name)
}
@ToJson
override fun toJson(writer: JsonWriter, value: MangaSource?) {
writer.value(value?.name)
}
}
private class InstantAdapter : JsonAdapter<Instant>() {
@FromJson
override fun fromJson(reader: JsonReader): Instant? {
val ms = reader.nextLong()
return if (ms == 0L) {
null
} else {
Instant.ofEpochMilli(ms)
}
}
@ToJson
override fun toJson(writer: JsonWriter, value: Instant?) {
writer.value(value?.toEpochMilli() ?: 0L)
}
}
}

View File

@@ -15,7 +15,8 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koitharu.kotatsu.SampleData
import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.backups.data.BackupRepository
import org.koitharu.kotatsu.backups.domain.AppBackupAgent
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository

View File

@@ -1,21 +1,33 @@
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() {
@@ -29,8 +41,8 @@ class KotatsuApp : BaseApp() {
detectNetwork()
detectDiskWrites()
detectCustomSlowCalls()
detectResourceMismatches()
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) {
@@ -44,18 +56,15 @@ class KotatsuApp : BaseApp() {
detectLeakedSqlLiteObjects()
detectLeakedClosableObjects()
detectLeakedRegistrationObjects()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectContentUriWithoutPermission()
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()
}.build(),
)
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder().apply {
detectWrongFragmentContainer()
@@ -70,4 +79,13 @@ class KotatsuApp : BaseApp() {
}
}.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

@@ -55,7 +55,7 @@ class StrictModeNotifier(
.setContentIntent(
PendingIntentCompat.getActivity(
context,
0,
violation.hashCode(),
ShareHelper(context).getShareTextIntent(violation.stackTraceToString()),
0,
false,

View File

@@ -0,0 +1,57 @@
package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.core.cache.MemoryContentCache
import org.koitharu.kotatsu.core.model.TestMangaSource
import org.koitharu.kotatsu.parsers.MangaLoaderContext
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.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.EnumSet
/*
This class is for parser development and testing purposes
You can open it in the app via Settings -> Debug
*/
class TestMangaRepository(
@Suppress("unused") private val loaderContext: MangaLoaderContext,
cache: MemoryContentCache
) : CachingMangaRepository(cache) {
override val source = TestMangaSource
override val sortOrders: Set<SortOrder> = EnumSet.allOf(SortOrder::class.java)
override var defaultSortOrder: SortOrder
get() = sortOrders.first()
set(value) = Unit
override val filterCapabilities = MangaListFilterCapabilities()
override suspend fun getFilterOptions() = MangaListFilterOptions()
override suspend fun getList(
offset: Int,
order: SortOrder?,
filter: MangaListFilter?
): List<Manga> = TODO("Get manga list by filter")
override suspend fun getDetailsImpl(
manga: Manga
): Manga = TODO("Fetch manga details")
override suspend fun getPagesImpl(
chapter: MangaChapter
): List<MangaPage> = TODO("Get pages for specific chapter")
override suspend fun getPageUrl(
page: MangaPage
): String = TODO("Return direct url of page image or page.url if it is already a direct url")
override suspend fun getRelatedMangaImpl(
seed: Manga
): List<Manga> = TODO("Get list of related manga. This method is optional and parser library has a default implementation")
}

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

@@ -0,0 +1,72 @@
package org.koitharu.kotatsu.settings
import android.os.Bundle
import androidx.preference.Preference
import leakcanary.LeakCanary
import org.koitharu.kotatsu.KotatsuApp
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.TestMangaSource
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.settings.utils.SplitSwitchPreference
import org.koitharu.workinspector.WorkInspector
class DebugSettingsFragment : BasePreferenceFragment(R.string.debug), Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
private val application
get() = requireContext().applicationContext as KotatsuApp
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_debug)
findPreference<SplitSwitchPreference>(KEY_LEAK_CANARY)?.let { pref ->
pref.isChecked = application.isLeakCanaryEnabled
pref.onPreferenceChangeListener = this
pref.onContainerClickListener = this
}
}
override fun onResume() {
super.onResume()
findPreference<SplitSwitchPreference>(KEY_LEAK_CANARY)?.isChecked = application.isLeakCanaryEnabled
}
override fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {
KEY_WORK_INSPECTOR -> {
startActivity(WorkInspector.getIntent(preference.context))
true
}
KEY_TEST_PARSER -> {
router.openList(TestMangaSource, null, null)
true
}
else -> super.onPreferenceTreeClick(preference)
}
override fun onPreferenceClick(preference: Preference): Boolean = when (preference.key) {
KEY_LEAK_CANARY -> {
startActivity(LeakCanary.newLeakDisplayActivityIntent())
true
}
else -> super.onPreferenceTreeClick(preference)
}
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean = when (preference.key) {
KEY_LEAK_CANARY -> {
application.isLeakCanaryEnabled = newValue as Boolean
true
}
else -> false
}
private companion object {
const val KEY_LEAK_CANARY = "leak_canary"
const val KEY_WORK_INSPECTOR = "work_inspector"
const val KEY_TEST_PARSER = "test_parser"
}
}

View File

@@ -1,33 +0,0 @@
package org.koitharu.kotatsu.settings
import android.content.Context
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import leakcanary.LeakCanary
import org.koitharu.kotatsu.R
import org.koitharu.workinspector.WorkInspector
class SettingsMenuProvider(
private val context: Context,
) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_settings, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_leaks -> {
context.startActivity(LeakCanary.newLeakDisplayActivityIntent())
true
}
R.id.action_works -> {
context.startActivity(WorkInspector.getIntent(context))
true
}
else -> false
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M20,8H17.19C16.74,7.2 16.12,6.5 15.37,6L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.05,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6C7.87,6.5 7.26,7.21 6.81,8H4V10H6.09C6.03,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.03,15.67 6.09,16H4V18H6.81C8.47,20.87 12.14,21.84 15,20.18C15.91,19.66 16.67,18.9 17.19,18H20V16H17.91C17.97,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.97,10.33 17.91,10H20V8M16,15A4,4 0 0,1 12,19A4,4 0 0,1 8,15V11A4,4 0 0,1 12,7A4,4 0 0,1 16,11V15M14,10V12H10V10H14M10,14H14V16H10V14Z" />
</vector>

View File

@@ -1,16 +0,0 @@
<?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">
<item
android:id="@id/action_leaks"
android:title="@string/leak_canary_display_activity_label"
app:showAsAction="never" />
<item
android:id="@id/action_works"
android:title="@string/wi_lib_name"
app:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.koitharu.kotatsu.settings.utils.SplitSwitchPreference
android:key="leak_canary"
android:persistent="false"
android:title="LeakCanary" />
<Preference
android:key="work_inspector"
android:persistent="false"
android:title="@string/wi_lib_name" />
<Preference
android:key="test_parser"
android:persistent="false"
android:title="@string/test_parser"
app:allowDividerAbove="true" />
</androidx.preference.PreferenceScreen>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.DebugSettingsFragment"
android:icon="@drawable/ic_debug"
android:key="debug"
android:title="@string/debug" />
</androidx.preference.PreferenceScreen>

View File

@@ -5,7 +5,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -19,17 +21,19 @@
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="RequestInstallPackagesPolicy" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
tools:ignore="AllFilesAccessPolicy,ScopedStorage" />
<queries>
<intent>
@@ -44,14 +48,18 @@
<application
android:name="org.koitharu.kotatsu.KotatsuApp"
android:allowBackup="true"
android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent"
android:backupAgent="org.koitharu.kotatsu.backups.domain.AppBackupAgent"
android:dataExtractionRules="@xml/backup_rules"
android:enableOnBackInvokedCallback="true"
android:enableOnBackInvokedCallback="@bool/is_predictive_back_enabled"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_content"
android:fullBackupOnly="true"
android:hasFragileUserData="true"
android:restoreAnyVersion="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
@@ -207,8 +215,12 @@
android:launchMode="singleTop" />
<activity android:name="org.koitharu.kotatsu.image.ui.ImageActivity" />
<activity android:name="org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity" />
<activity
android:name="org.koitharu.kotatsu.settings.override.OverrideConfigActivity"
android:label="@string/edit" />
<activity
android:name="org.koitharu.kotatsu.sync.ui.SyncAuthActivity"
android:exported="true"
android:label="@string/sync" />
<activity
android:name="org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity"
@@ -259,6 +271,27 @@
<activity
android:name="org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity"
android:label="@string/tracker_debug_info" />
<activity
android:name="org.koitharu.kotatsu.picker.ui.PageImagePickActivity"
android:exported="true"
android:label="@string/pick_manga_page">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.OPENABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="org.koitharu.kotatsu.scrobbling.discord.ui.DiscordAuthActivity"
android:label="@string/discord" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
@@ -269,7 +302,7 @@
android:foregroundServiceType="dataSync"
android:label="@string/local_manga_processing" />
<service
android:name="org.koitharu.kotatsu.settings.backup.PeriodicalBackupService"
android:name="org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupService"
android:foregroundServiceType="dataSync"
android:label="@string/periodic_backups" />
<service
@@ -279,6 +312,18 @@
<service
android:name="org.koitharu.kotatsu.local.ui.LocalIndexUpdateService"
android:label="@string/local_manga_processing" />
<service
android:name="org.koitharu.kotatsu.backups.ui.backup.BackupService"
android:foregroundServiceType="dataSync"
android:label="@string/creating_backup" />
<service
android:name="org.koitharu.kotatsu.backups.ui.restore.RestoreService"
android:foregroundServiceType="dataSync"
android:label="@string/restoring_backup" />
<service
android:name="org.koitharu.kotatsu.local.ui.ImportService"
android:foregroundServiceType="dataSync"
android:label="@string/importing_manga" />
<service
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetService"
android:label="@string/manga_shelf"
@@ -324,6 +369,9 @@
android:name="org.koitharu.kotatsu.details.service.MangaPrefetchService"
android:exported="false"
android:label="@string/prefetch_content" />
<service
android:name="org.koitharu.kotatsu.browser.AdListUpdateService"
android:exported="false" />
<provider
android:name="org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider"
@@ -361,6 +409,13 @@
tools:node="remove" />
</provider>
<receiver
android:name="org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler$DiscardReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.koitharu.kotatsu.CAPTCHA_DISCARD" />
</intent-filter>
</receiver>
<receiver
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider"
android:exported="true"

View File

@@ -3,88 +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.model.SortOrder
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_DEFAULT = 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> = invoke(manga, MATCH_THRESHOLD_DEFAULT)
suspend operator fun invoke(manga: Manga, matchThreshold: Float): 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.filterCapabilities.isSearchSupported) {
continue
}
launch {
val searchHelper = searchHelperFactory.create(source)
val list = runCatchingCancellable {
semaphore.withPermit {
repository.getList(offset = 0, SortOrder.RELEVANCE, MangaListFilter(query = manga.title))
searchHelper(manga.title, SearchKind.TITLE)?.manga
}
}.getOrDefault(emptyList())
for (item in list) {
if (item.matches(manga, matchThreshold)) {
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, threshold: Float): Boolean {
return matchesTitles(title, ref.title, threshold) ||
matchesTitles(title, ref.altTitle, threshold) ||
matchesTitles(altTitle, ref.title, threshold) ||
matchesTitles(altTitle, ref.altTitle, threshold)
}
private fun matchesTitles(a: String?, b: String?, threshold: Float): Boolean {
return !a.isNullOrEmpty() && !b.isNullOrEmpty() && a.almostEquals(b, 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

@@ -15,6 +15,7 @@ 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
@@ -29,12 +30,14 @@ class AutoFixUseCase @Inject constructor(
) {
suspend operator fun invoke(mangaId: Long): Pair<Manga, Manga?> {
val seed = checkNotNull(mangaDataRepository.findMangaById(mangaId)) { "Manga $mangaId not found" }
.getDetailsSafe()
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, matchThreshold = 0.02f)
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) {

View File

@@ -30,21 +30,19 @@ constructor(
oldManga: Manga,
newManga: Manga,
) {
val oldDetails =
if (oldManga.chapters.isNullOrEmpty()) {
runCatchingCancellable {
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
}.getOrDefault(oldManga)
} else {
oldManga
}
val newDetails =
if (newManga.chapters.isNullOrEmpty()) {
mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
} else {
newManga
}
mangaDataRepository.storeManga(newDetails)
val oldDetails = if (oldManga.chapters.isNullOrEmpty()) {
runCatchingCancellable {
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
}.getOrDefault(oldManga)
} else {
oldManga
}
val newDetails = if (newManga.chapters.isNullOrEmpty()) {
mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
} else {
newManga
}
mangaDataRepository.storeManga(newDetails, replaceExisting = true)
database.withTransaction {
// replace favorites
val favoritesDao = database.getFavouritesDao()
@@ -101,11 +99,11 @@ constructor(
mangaId = newDetails.id,
rating = prevInfo.rating,
status =
prevInfo.status ?: when {
newHistory == null -> ScrobblingStatus.PLANNED
newHistory.percent == 1f -> ScrobblingStatus.COMPLETED
else -> ScrobblingStatus.READING
},
prevInfo.status ?: when {
newHistory == null -> ScrobblingStatus.PLANNED
newHistory.percent == 1f -> ScrobblingStatus.COMPLETED
else -> ScrobblingStatus.READING
},
comment = prevInfo.comment,
)
if (newHistory != null) {

View File

@@ -4,6 +4,7 @@ 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 coil3.ImageLoader
import coil3.request.ImageRequest
@@ -20,15 +21,11 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
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.mangaExtra
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.databinding.ItemMangaAlternativeBinding
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -51,10 +48,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))
}
@@ -70,7 +79,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)
@@ -87,13 +99,6 @@ fun alternativeAD(
.allowRgb565(true)
.enqueueWith(coil)
}
binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run {
size(CoverSizeResolver(binding.imageViewCover))
defaultPlaceholders(context)
transformations(TrimTransformation())
allowRgb565(true)
mangaExtra(item.manga)
enqueueWith(coil)
}
binding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga)
}
}

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 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.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.parsers.model.MangaListFilter
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject
@AndroidEntryPoint
class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
ListStateHolderListener,
OnListItemClickListener<MangaAlternativeModel> {
@Inject
@@ -52,9 +51,10 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
}
val listAdapter = BaseListAdapter<ListModel>()
.addDelegate(ListItemType.MANGA_LIST_DETAILED, alternativeAD(coil, this, this))
.addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, this, null))
.addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(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,39 +62,46 @@ 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(
MangaListActivity.newIntent(
this,
item.manga.source,
MangaListFilter(query = 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) {
buildAlertDialog(this, isCentered = true) {
setIcon(R.drawable.ic_replace)
@@ -114,10 +121,4 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
}
}.show()
}
companion object {
fun newIntent(context: Context, manga: Manga) = Intent(context, AlternativesActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
}

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

@@ -10,7 +10,6 @@ import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import coil3.ImageLoader
import coil3.request.ImageRequest
@@ -18,19 +17,23 @@ 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.alternatives.domain.AutoFixUseCase.NoAlternativesException
import org.koitharu.kotatsu.core.ErrorReporterReceiver
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.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.details.ui.DetailsActivity
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
import androidx.appcompat.R as appcompatR
@AndroidEntryPoint
class AutoFixService : CoroutineIntentService() {
@@ -45,33 +48,35 @@ class AutoFixService : CoroutineIntentService() {
override fun onCreate() {
super.onCreate()
notificationManager = NotificationManagerCompat.from(applicationContext)
notificationManager = NotificationManagerCompat.from(this)
}
override suspend fun IntentJobContext.processIntent(intent: Intent) {
val ids = requireNotNull(intent.getLongArrayExtra(DATA_IDS))
startForeground(this)
for (mangaId in ids) {
val result = runCatchingCancellable {
autoFixUseCase.invoke(mangaId)
}
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
val notification = buildNotification(result)
notificationManager.notify(TAG, startId, notification)
powerManager.withPartialWakeLock(TAG) {
val result = runCatchingCancellable {
autoFixUseCase.invoke(mangaId)
}
if (checkNotificationPermission(CHANNEL_ID)) {
val notification = buildNotification(startId, result)
notificationManager.notify(TAG, startId, notification)
}
}
}
}
override fun IntentJobContext.onError(error: Throwable) {
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
val notification = runBlocking { buildNotification(Result.failure(error)) }
if (checkNotificationPermission(CHANNEL_ID)) {
val notification = runBlocking { buildNotification(startId, Result.failure(error)) }
notificationManager.notify(TAG, startId, notification)
}
}
@SuppressLint("InlinedApi")
private fun startForeground(jobContext: IntentJobContext) {
val title = applicationContext.getString(R.string.fixing_manga)
val title = getString(R.string.fixing_manga)
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_MIN)
.setName(title)
.setShowBadge(false)
@@ -81,7 +86,7 @@ class AutoFixService : CoroutineIntentService() {
.build()
notificationManager.createNotificationChannel(channel)
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(title)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setDefaults(0)
@@ -92,8 +97,8 @@ class AutoFixService : CoroutineIntentService() {
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(
materialR.drawable.material_ic_clear_black_24dp,
applicationContext.getString(android.R.string.cancel),
appcompatR.drawable.abc_ic_clear_material,
getString(android.R.string.cancel),
jobContext.getCancelIntent(),
)
.build()
@@ -105,8 +110,8 @@ class AutoFixService : CoroutineIntentService() {
)
}
private suspend fun buildNotification(result: Result<Pair<Manga, Manga?>>): Notification {
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
private suspend fun buildNotification(startId: Int, result: Result<Pair<Manga, Manga?>>): Notification {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(0)
.setSilent(true)
@@ -115,59 +120,64 @@ class AutoFixService : CoroutineIntentService() {
if (replacement != null) {
notification.setLargeIcon(
coil.execute(
ImageRequest.Builder(applicationContext)
ImageRequest.Builder(this)
.data(replacement.coverUrl)
.mangaSourceExtra(replacement.source)
.build(),
).toBitmapOrNull(),
)
notification.setSubText(replacement.title)
val intent = DetailsActivity.newIntent(applicationContext, replacement)
val intent = AppRouter.detailsIntent(this, replacement)
notification.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
this,
replacement.id.toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT,
false,
),
).setVisibility(
if (replacement.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
if (replacement.isNsfw()) {
NotificationCompat.VISIBILITY_SECRET
} else {
NotificationCompat.VISIBILITY_PUBLIC
},
)
notification
.setContentTitle(applicationContext.getString(R.string.fixed))
.setContentTitle(getString(R.string.fixed))
.setContentText(
applicationContext.getString(
getString(
R.string.manga_replaced,
seed.title,
seed.source.getTitle(applicationContext),
seed.source.getTitle(this),
replacement.title,
replacement.source.getTitle(applicationContext),
replacement.source.getTitle(this),
),
)
.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))
.setContentTitle(getString(R.string.fixing_manga))
.setContentText(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))
.setContentTitle(getString(R.string.error_occurred))
.setContentText(
if (error is AutoFixUseCase.NoAlternativesException) {
applicationContext.getString(R.string.no_alternatives_found, error.seed.manga.title)
if (error is NoAlternativesException) {
getString(R.string.no_alternatives_found, error.seed.manga.title)
} else {
error.getDisplayMessage(applicationContext.resources)
error.getDisplayMessage(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,
)
ErrorReporterReceiver.getNotificationAction(
context = this,
e = error,
notificationId = startId,
notificationTag = TAG,
)?.let { action ->
notification.addAction(action)
}
}
return notification.build()

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

@@ -0,0 +1,314 @@
package org.koitharu.kotatsu.backups.data
import androidx.collection.ArrayMap
import androidx.room.withTransaction
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.DecodeSequenceMode
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeToSequence
import kotlinx.serialization.json.encodeToStream
import kotlinx.serialization.serializer
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.backups.data.model.BackupIndex
import org.koitharu.kotatsu.backups.data.model.BookmarkBackup
import org.koitharu.kotatsu.backups.data.model.CategoryBackup
import org.koitharu.kotatsu.backups.data.model.FavouriteBackup
import org.koitharu.kotatsu.backups.data.model.HistoryBackup
import org.koitharu.kotatsu.backups.data.model.MangaBackup
import org.koitharu.kotatsu.backups.data.model.ScrobblingBackup
import org.koitharu.kotatsu.backups.data.model.SourceBackup
import org.koitharu.kotatsu.backups.data.model.StatisticBackup
import org.koitharu.kotatsu.backups.domain.BackupSection
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.CompositeResult
import org.koitharu.kotatsu.core.util.progress.Progress
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.filter.data.PersistableFilter
import org.koitharu.kotatsu.filter.data.SavedFiltersRepository
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.data.TapGridSettings
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import javax.inject.Inject
@Reusable
class BackupRepository @Inject constructor(
private val database: MangaDatabase,
private val settings: AppSettings,
private val tapGridSettings: TapGridSettings,
private val mangaSourcesRepository: MangaSourcesRepository,
private val savedFiltersRepository: SavedFiltersRepository,
) {
private val json = Json {
allowSpecialFloatingPointValues = true
coerceInputValues = true
encodeDefaults = true
ignoreUnknownKeys = true
useAlternativeNames = false
}
suspend fun createBackup(
output: ZipOutputStream,
progress: FlowCollector<Progress>?,
) {
progress?.emit(Progress.INDETERMINATE)
var commonProgress = Progress(0, BackupSection.entries.size)
for (section in BackupSection.entries) {
when (section) {
BackupSection.INDEX -> output.writeJsonArray(
section = BackupSection.INDEX,
data = flowOf(BackupIndex()),
serializer = serializer(),
)
BackupSection.HISTORY -> output.writeJsonArray(
section = BackupSection.HISTORY,
data = database.getHistoryDao().dump().map { HistoryBackup(it) },
serializer = serializer(),
)
BackupSection.CATEGORIES -> output.writeJsonArray(
section = BackupSection.CATEGORIES,
data = database.getFavouriteCategoriesDao().findAll().asFlow().map { CategoryBackup(it) },
serializer = serializer(),
)
BackupSection.FAVOURITES -> output.writeJsonArray(
section = BackupSection.FAVOURITES,
data = database.getFavouritesDao().dump().map { FavouriteBackup(it) },
serializer = serializer(),
)
BackupSection.SETTINGS -> output.writeString(
section = BackupSection.SETTINGS,
data = dumpSettings(),
)
BackupSection.SETTINGS_READER_GRID -> output.writeString(
section = BackupSection.SETTINGS_READER_GRID,
data = dumpReaderGridSettings(),
)
BackupSection.BOOKMARKS -> output.writeJsonArray(
section = BackupSection.BOOKMARKS,
data = database.getBookmarksDao().dump().map { BookmarkBackup(it.first, it.second) },
serializer = serializer(),
)
BackupSection.SOURCES -> output.writeJsonArray(
section = BackupSection.SOURCES,
data = database.getSourcesDao().dumpEnabled().map { SourceBackup(it) },
serializer = serializer(),
)
BackupSection.SCROBBLING -> output.writeJsonArray(
section = BackupSection.SCROBBLING,
data = database.getScrobblingDao().dumpEnabled().map { ScrobblingBackup(it) },
serializer = serializer(),
)
BackupSection.STATS -> output.writeJsonArray(
section = BackupSection.STATS,
data = database.getStatsDao().dumpEnabled().map { StatisticBackup(it) },
serializer = serializer(),
)
BackupSection.SAVED_FILTERS -> {
val sources = mangaSourcesRepository.getEnabledSources()
val filters = sources.flatMap { source ->
savedFiltersRepository.getAll(source)
}
output.writeJsonArray(
section = BackupSection.SAVED_FILTERS,
data = filters.asFlow(),
serializer = serializer(),
)
}
}
progress?.emit(commonProgress)
commonProgress++
}
progress?.emit(commonProgress)
}
suspend fun restoreBackup(
input: ZipInputStream,
sections: Set<BackupSection>,
progress: FlowCollector<Progress>?,
): CompositeResult {
progress?.emit(Progress.INDETERMINATE)
var commonProgress = Progress(0, sections.size)
var entry = input.nextEntry
var result = CompositeResult.EMPTY
while (entry != null) {
val section = BackupSection.of(entry)
if (section in sections) {
result += when (section) {
BackupSection.INDEX -> CompositeResult.EMPTY // useless in our case
BackupSection.HISTORY -> input.readJsonArray<HistoryBackup>(serializer()).restoreToDb {
upsertManga(it.manga)
getHistoryDao().upsert(it.toEntity())
}
BackupSection.CATEGORIES -> input.readJsonArray<CategoryBackup>(serializer()).restoreToDb {
getFavouriteCategoriesDao().upsert(it.toEntity())
}
BackupSection.FAVOURITES -> input.readJsonArray<FavouriteBackup>(serializer()).restoreToDb {
upsertManga(it.manga)
getFavouritesDao().upsert(it.toEntity())
}
BackupSection.SETTINGS -> input.readMap().let {
settings.upsertAll(it)
CompositeResult.success()
}
BackupSection.SETTINGS_READER_GRID -> input.readMap().let {
tapGridSettings.upsertAll(it)
CompositeResult.success()
}
BackupSection.BOOKMARKS -> input.readJsonArray<BookmarkBackup>(serializer()).restoreToDb {
upsertManga(it.manga)
getBookmarksDao().upsert(it.bookmarks.map { b -> b.toEntity() })
}
BackupSection.SOURCES -> input.readJsonArray<SourceBackup>(serializer()).restoreToDb {
getSourcesDao().upsert(it.toEntity())
}
BackupSection.SCROBBLING -> input.readJsonArray<ScrobblingBackup>(serializer()).restoreToDb {
getScrobblingDao().upsert(it.toEntity())
}
BackupSection.STATS -> input.readJsonArray<StatisticBackup>(serializer()).restoreToDb {
getStatsDao().upsert(it.toEntity())
}
BackupSection.SAVED_FILTERS -> input.readJsonArray<PersistableFilter>(serializer())
.restoreWithoutTransaction {
savedFiltersRepository.save(it)
}
null -> CompositeResult.EMPTY // skip unknown entries
}
progress?.emit(commonProgress)
commonProgress++
}
input.closeEntry()
entry = input.nextEntry
}
progress?.emit(commonProgress)
return result
}
private suspend fun <T> ZipOutputStream.writeJsonArray(
section: BackupSection,
data: Flow<T>,
serializer: SerializationStrategy<T>,
) {
data.onStart {
putNextEntry(ZipEntry(section.entryName))
write("[")
}.onCompletion { error ->
if (error == null) {
write("]")
}
closeEntry()
flush()
}.collectIndexed { index, value ->
if (index > 0) {
write(",")
}
json.encodeToStream(serializer, value, this)
}
}
private fun <T> InputStream.readJsonArray(
serializer: DeserializationStrategy<T>,
): Sequence<T> = json.decodeToSequence(this, serializer, DecodeSequenceMode.ARRAY_WRAPPED)
private fun InputStream.readMap(): Map<String, Any?> {
val jo = JSONArray(readString()).getJSONObject(0)
val map = ArrayMap<String, Any?>(jo.length())
val keys = jo.keys()
while (keys.hasNext()) {
val key = keys.next()
map[key] = jo.get(key)
}
return map
}
private fun ZipOutputStream.writeString(
section: BackupSection,
data: String,
) {
putNextEntry(ZipEntry(section.entryName))
try {
write("[")
write(data)
write("]")
} finally {
closeEntry()
flush()
}
}
private fun OutputStream.write(str: String) = write(str.toByteArray())
private fun InputStream.readString(): String = readBytes().decodeToString()
private fun dumpSettings(): String {
val map = settings.getAllValues().toMutableMap()
map.remove(AppSettings.KEY_APP_PASSWORD)
map.remove(AppSettings.KEY_PROXY_PASSWORD)
map.remove(AppSettings.KEY_PROXY_LOGIN)
map.remove(AppSettings.KEY_INCOGNITO_MODE)
return JSONObject(map).toString()
}
private fun dumpReaderGridSettings(): String {
return JSONObject(tapGridSettings.getAllValues()).toString()
}
private suspend fun MangaDatabase.upsertManga(manga: MangaBackup) {
val tags = manga.tags.map { it.toEntity() }
getTagsDao().upsert(tags)
getMangaDao().upsert(manga.toEntity(), tags)
}
private suspend inline fun <T> Sequence<T>.restoreToDb(crossinline block: suspend MangaDatabase.(T) -> Unit): CompositeResult {
return fold(CompositeResult.EMPTY) { result, item ->
result + runCatchingCancellable {
database.withTransaction {
database.block(item)
}
}
}
}
private suspend inline fun <T> Sequence<T>.restoreWithoutTransaction(crossinline block: suspend (T) -> Unit): CompositeResult {
return fold(CompositeResult.EMPTY) { result, item ->
result + runCatchingCancellable {
block(item)
}
}
}
}

View File

@@ -0,0 +1,19 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.BuildConfig
@Serializable
class BackupIndex(
@SerialName("app_id") val appId: String,
@SerialName("app_version") val appVersion: Int,
@SerialName("created_at") val createdAt: Long,
) {
constructor() : this(
appId = BuildConfig.APPLICATION_ID,
appVersion = BuildConfig.VERSION_CODE,
createdAt = System.currentTimeMillis(),
)
}

View File

@@ -0,0 +1,56 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.parsers.util.mapToSet
@Serializable
class BookmarkBackup(
@SerialName("manga") val manga: MangaBackup,
@SerialName("tags") val tags: Set<TagBackup>,
@SerialName("bookmarks") val bookmarks: List<Bookmark>,
) {
@Serializable
class Bookmark(
@SerialName("manga_id") val mangaId: Long,
@SerialName("page_id") val pageId: Long,
@SerialName("chapter_id") val chapterId: Long,
@SerialName("page") val page: Int,
@SerialName("scroll") val scroll: Int,
@SerialName("image_url") val imageUrl: String,
@SerialName("created_at") val createdAt: Long,
@SerialName("percent") val percent: Float,
) {
fun toEntity() = BookmarkEntity(
mangaId = mangaId,
pageId = pageId,
chapterId = chapterId,
page = page,
scroll = scroll,
imageUrl = imageUrl,
createdAt = createdAt,
percent = percent,
)
}
constructor(manga: MangaWithTags, entities: List<BookmarkEntity>) : this(
manga = MangaBackup(manga.copy(tags = emptyList())),
tags = manga.tags.mapToSet { TagBackup(it) },
bookmarks = entities.map {
Bookmark(
mangaId = it.mangaId,
pageId = it.pageId,
chapterId = it.chapterId,
page = it.page,
scroll = it.scroll,
imageUrl = it.imageUrl,
createdAt = it.createdAt,
percent = it.percent,
)
},
)
}

View File

@@ -0,0 +1,39 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.list.domain.ListSortOrder
@Serializable
class CategoryBackup(
@SerialName("category_id") val categoryId: Int,
@SerialName("created_at") val createdAt: Long,
@SerialName("sort_key") val sortKey: Int,
@SerialName("title") val title: String,
@SerialName("order") val order: String = ListSortOrder.NEWEST.name,
@SerialName("track") val track: Boolean = true,
@SerialName("show_in_lib") val isVisibleInLibrary: Boolean = true,
) {
constructor(entity: FavouriteCategoryEntity) : this(
categoryId = entity.categoryId,
createdAt = entity.createdAt,
sortKey = entity.sortKey,
title = entity.title,
order = entity.order,
track = entity.track,
isVisibleInLibrary = entity.isVisibleInLibrary,
)
fun toEntity() = FavouriteCategoryEntity(
categoryId = categoryId,
createdAt = createdAt,
sortKey = sortKey,
title = title,
order = order,
track = track,
isVisibleInLibrary = isVisibleInLibrary,
deletedAt = 0L,
)
}

View File

@@ -0,0 +1,36 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouriteManga
@Serializable
class FavouriteBackup(
@SerialName("manga_id") val mangaId: Long,
@SerialName("category_id") val categoryId: Long,
@SerialName("sort_key") val sortKey: Int = 0,
@SerialName("pinned") val isPinned: Boolean = false,
@SerialName("created_at") val createdAt: Long,
@SerialName("manga") val manga: MangaBackup,
) {
constructor(entity: FavouriteManga) : this(
mangaId = entity.manga.id,
categoryId = entity.favourite.categoryId,
sortKey = entity.favourite.sortKey,
isPinned = entity.favourite.isPinned,
createdAt = entity.favourite.createdAt,
manga = MangaBackup(MangaWithTags(entity.manga, entity.tags)),
)
fun toEntity() = FavouriteEntity(
mangaId = mangaId,
categoryId = categoryId,
sortKey = sortKey,
isPinned = isPinned,
createdAt = createdAt,
deletedAt = 0L,
)
}

View File

@@ -0,0 +1,46 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.history.data.HistoryWithManga
import org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE
@Serializable
class HistoryBackup(
@SerialName("manga_id") val mangaId: Long,
@SerialName("created_at") val createdAt: Long,
@SerialName("updated_at") val updatedAt: Long,
@SerialName("chapter_id") val chapterId: Long,
@SerialName("page") val page: Int,
@SerialName("scroll") val scroll: Float,
@SerialName("percent") val percent: Float = PROGRESS_NONE,
@SerialName("chapters") val chaptersCount: Int = 0,
@SerialName("manga") val manga: MangaBackup,
) {
constructor(entity: HistoryWithManga) : this(
mangaId = entity.manga.id,
createdAt = entity.history.createdAt,
updatedAt = entity.history.updatedAt,
chapterId = entity.history.chapterId,
page = entity.history.page,
scroll = entity.history.scroll,
percent = entity.history.percent,
chaptersCount = entity.history.chaptersCount,
manga = MangaBackup(MangaWithTags(entity.manga, entity.tags)),
)
fun toEntity() = HistoryEntity(
mangaId = mangaId,
createdAt = createdAt,
updatedAt = updatedAt,
chapterId = chapterId,
page = page,
scroll = scroll,
percent = percent,
deletedAt = 0L,
chaptersCount = chaptersCount,
)
}

View File

@@ -0,0 +1,60 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.util.mapToSet
@Serializable
class MangaBackup(
@SerialName("id") val id: Long,
@SerialName("title") val title: String,
@SerialName("alt_title") val altTitles: String? = null,
@SerialName("url") val url: String,
@SerialName("public_url") val publicUrl: String,
@SerialName("rating") val rating: Float = RATING_UNKNOWN,
@SerialName("nsfw") val isNsfw: Boolean = false,
@SerialName("content_rating") val contentRating: String? = null,
@SerialName("cover_url") val coverUrl: String,
@SerialName("large_cover_url") val largeCoverUrl: String? = null,
@SerialName("state") val state: String? = null,
@SerialName("author") val authors: String? = null,
@SerialName("source") val source: String,
@SerialName("tags") val tags: Set<TagBackup> = emptySet(),
) {
constructor(entity: MangaWithTags) : this(
id = entity.manga.id,
title = entity.manga.title,
altTitles = entity.manga.altTitles,
url = entity.manga.url,
publicUrl = entity.manga.publicUrl,
rating = entity.manga.rating,
isNsfw = entity.manga.isNsfw,
contentRating = entity.manga.contentRating,
coverUrl = entity.manga.coverUrl,
largeCoverUrl = entity.manga.largeCoverUrl,
state = entity.manga.state,
authors = entity.manga.authors,
source = entity.manga.source,
tags = entity.tags.mapToSet { TagBackup(it) },
)
fun toEntity() = MangaEntity(
id = id,
title = title,
altTitles = altTitles,
url = url,
publicUrl = publicUrl,
rating = rating,
isNsfw = isNsfw,
contentRating = contentRating,
coverUrl = coverUrl,
largeCoverUrl = largeCoverUrl,
state = state,
authors = authors,
source = source,
)
}

View File

@@ -0,0 +1,40 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
@Serializable
class ScrobblingBackup(
@SerialName("scrobbler") val scrobbler: Int,
@SerialName("id") val id: Int,
@SerialName("manga_id") val mangaId: Long,
@SerialName("target_id") val targetId: Long,
@SerialName("status") val status: String?,
@SerialName("chapter") val chapter: Int,
@SerialName("comment") val comment: String?,
@SerialName("rating") val rating: Float,
) {
constructor(entity: ScrobblingEntity) : this(
scrobbler = entity.scrobbler,
id = entity.id,
mangaId = entity.mangaId,
targetId = entity.targetId,
status = entity.status,
chapter = entity.chapter,
comment = entity.comment,
rating = entity.rating,
)
fun toEntity() = ScrobblingEntity(
scrobbler = scrobbler,
id = id,
mangaId = mangaId,
targetId = targetId,
status = status,
chapter = chapter,
comment = comment,
rating = rating,
)
}

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
@Serializable
class SourceBackup(
@SerialName("source") val source: String,
@SerialName("sort_key") val sortKey: Int,
@SerialName("used_at") val lastUsedAt: Long,
@SerialName("added_in") val addedIn: Int,
@SerialName("pinned") val isPinned: Boolean = false,
@SerialName("enabled") val isEnabled: Boolean = true, // for compatibility purposes, should be only true
) {
constructor(entity: MangaSourceEntity) : this(
source = entity.source,
sortKey = entity.sortKey,
lastUsedAt = entity.lastUsedAt,
addedIn = entity.addedIn,
isPinned = entity.isPinned,
isEnabled = entity.isEnabled,
)
fun toEntity() = MangaSourceEntity(
source = source,
isEnabled = isEnabled,
sortKey = sortKey,
addedIn = addedIn,
lastUsedAt = lastUsedAt,
isPinned = isPinned,
cfState = 0,
)
}

View File

@@ -0,0 +1,28 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.stats.data.StatsEntity
@Serializable
class StatisticBackup(
@SerialName("manga_id") val mangaId: Long,
@SerialName("started_at") val startedAt: Long,
@SerialName("duration") val duration: Long,
@SerialName("pages") val pages: Int,
) {
constructor(entity: StatsEntity) : this(
mangaId = entity.mangaId,
startedAt = entity.startedAt,
duration = entity.duration,
pages = entity.pages,
)
fun toEntity() = StatsEntity(
mangaId = mangaId,
startedAt = startedAt,
duration = duration,
pages = pages,
)
}

View File

@@ -0,0 +1,31 @@
package org.koitharu.kotatsu.backups.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.koitharu.kotatsu.core.db.entity.TagEntity
@Serializable
class TagBackup(
@SerialName("id") val id: Long,
@SerialName("title") val title: String,
@SerialName("key") val key: String,
@SerialName("source") val source: String,
@SerialName("pinned") val isPinned: Boolean = false,
) {
constructor(entity: TagEntity) : this(
id = entity.id,
title = entity.title,
key = entity.key,
source = entity.source,
isPinned = entity.isPinned,
)
fun toEntity() = TagEntity(
id = id,
title = title,
key = key,
source = source,
isPinned = isPinned,
)
}

View File

@@ -0,0 +1,119 @@
package org.koitharu.kotatsu.backups.domain
import android.app.backup.BackupAgent
import android.app.backup.BackupDataInput
import android.app.backup.BackupDataOutput
import android.app.backup.FullBackupDataOutput
import android.content.Context
import android.os.ParcelFileDescriptor
import androidx.annotation.VisibleForTesting
import com.google.common.io.ByteStreams
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.backups.data.BackupRepository
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.filter.data.SavedFiltersRepository
import org.koitharu.kotatsu.reader.data.TapGridSettings
import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
import java.util.EnumSet
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class AppBackupAgent : BackupAgent() {
override fun onBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput?,
newState: ParcelFileDescriptor?
) = Unit
override fun onRestore(
data: BackupDataInput?,
appVersionCode: Int,
newState: ParcelFileDescriptor?
) = Unit
override fun onFullBackup(data: FullBackupDataOutput) {
super.onFullBackup(data)
val file = createBackupFile(
this,
BackupRepository(
database = MangaDatabase(context = applicationContext),
settings = AppSettings(applicationContext),
tapGridSettings = TapGridSettings(applicationContext),
mangaSourcesRepository = MangaSourcesRepository(
context = applicationContext,
db = MangaDatabase(context = applicationContext),
settings = AppSettings(applicationContext),
),
savedFiltersRepository = SavedFiltersRepository(
context = applicationContext,
),
),
)
try {
fullBackupFile(file, data)
} finally {
file.delete()
}
}
override fun onRestoreFile(
data: ParcelFileDescriptor,
size: Long,
destination: File?,
type: Int,
mode: Long,
mtime: Long
) {
if (destination?.name?.endsWith(".bk.zip") == true) {
restoreBackupFile(
data.fileDescriptor,
size,
BackupRepository(
database = MangaDatabase(applicationContext),
settings = AppSettings(applicationContext),
tapGridSettings = TapGridSettings(applicationContext),
mangaSourcesRepository = MangaSourcesRepository(
context = applicationContext,
db = MangaDatabase(context = applicationContext),
settings = AppSettings(applicationContext),
),
savedFiltersRepository = SavedFiltersRepository(
context = applicationContext,
),
),
)
destination.delete()
} else {
super.onRestoreFile(data, size, destination, type, mode, mtime)
}
}
@VisibleForTesting
fun createBackupFile(context: Context, repository: BackupRepository): File {
val file = BackupUtils.createTempFile(context)
ZipOutputStream(file.outputStream()).use { output ->
runBlocking {
repository.createBackup(output, null)
}
}
return file
}
@VisibleForTesting
fun restoreBackupFile(fd: FileDescriptor, size: Long, repository: BackupRepository) {
ZipInputStream(ByteStreams.limit(FileInputStream(fd), size)).use { input ->
val sections = EnumSet.allOf(BackupSection::class.java)
// managed externally
sections.remove(BackupSection.SETTINGS)
sections.remove(BackupSection.SETTINGS_READER_GRID)
runBlocking {
repository.restoreBackup(input, sections, null)
}
}
}
}

View File

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

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.settings.backup
package org.koitharu.kotatsu.backups.domain
import android.app.backup.BackupManager
import android.content.Context
@@ -13,7 +13,13 @@ import javax.inject.Singleton
@Singleton
class BackupObserver @Inject constructor(
@ApplicationContext context: Context,
) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) {
) : InvalidationTracker.Observer(
arrayOf(
TABLE_HISTORY,
TABLE_FAVOURITES,
TABLE_FAVOURITE_CATEGORIES,
),
) {
private val backupManager = BackupManager(context)

View File

@@ -0,0 +1,30 @@
package org.koitharu.kotatsu.backups.domain
import java.util.Locale
import java.util.zip.ZipEntry
enum class BackupSection(
val entryName: String,
) {
INDEX("index"),
HISTORY("history"),
CATEGORIES("categories"),
FAVOURITES("favourites"),
SETTINGS("settings"),
SETTINGS_READER_GRID("reader_grid"),
BOOKMARKS("bookmarks"),
SOURCES("sources"),
SCROBBLING("scrobbling"),
STATS("statistics"),
SAVED_FILTERS("saved_filters"),
;
companion object {
fun of(entry: ZipEntry): BackupSection? {
val name = entry.name.lowercase(Locale.ROOT)
return entries.find { x -> x.entryName == name }
}
}
}

View File

@@ -0,0 +1,42 @@
package org.koitharu.kotatsu.backups.domain
import android.content.Context
import androidx.annotation.CheckResult
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import java.io.File
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object BackupUtils {
private const val DIR_BACKUPS = "backups"
private val dateTimeFormat = SimpleDateFormat("yyyyMMdd-HHmm")
@CheckResult
fun createTempFile(context: Context): File {
val dir = getAppBackupDir(context)
dir.mkdirs()
return File(dir, generateFileName(context))
}
fun getAppBackupDir(context: Context) = context.run {
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
}
fun parseBackupDateTime(fileName: String): Date? = try {
dateTimeFormat.parse(fileName.substringAfterLast('_').substringBefore('.'))
} catch (e: ParseException) {
e.printStackTraceDebug()
null
}
fun generateFileName(context: Context) = buildString {
append(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))
append('_')
append(dateTimeFormat.format(Date()))
append(".bk.zip")
}
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.core.backup
package org.koitharu.kotatsu.backups.domain
import android.content.Context
import android.net.Uri
@@ -28,7 +28,7 @@ class ExternalBackupStorage @Inject constructor(
BackupFile(
uri = it.uri,
dateTime = it.name?.let { fileName ->
BackupZipOutput.parseBackupDateTime(fileName)
BackupUtils.parseBackupDateTime(fileName)
} ?: return@mapNotNull null,
)
} else {
@@ -44,7 +44,12 @@ class ExternalBackupStorage @Inject constructor(
}.getOrNull()
suspend fun put(file: File): Uri = runInterruptible(Dispatchers.IO) {
val out = checkNotNull(getRootOrThrow().createFile("application/zip", file.nameWithoutExtension)) {
val out = checkNotNull(
getRootOrThrow().createFile(
"application/zip",
file.nameWithoutExtension,
),
) {
"Cannot create target backup file"
}
checkNotNull(context.contentResolver.openOutputStream(out.uri, "wt")).sink().use { sink ->

View File

@@ -0,0 +1,136 @@
package org.koitharu.kotatsu.backups.ui
import android.content.Context
import android.net.Uri
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.app.ShareCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.CompositeResult
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getFileDisplayName
import androidx.appcompat.R as appcompatR
abstract class BaseBackupRestoreService : CoroutineIntentService() {
protected abstract val notificationTag: String
protected abstract val isRestoreService: Boolean
protected lateinit var notificationManager: NotificationManagerCompat
private set
override fun onCreate() {
super.onCreate()
notificationManager = NotificationManagerCompat.from(applicationContext)
createNotificationChannel(this)
}
override fun IntentJobContext.onError(error: Throwable) {
showResultNotification(null, CompositeResult.failure(error))
}
protected fun IntentJobContext.showResultNotification(
fileUri: Uri?,
result: CompositeResult,
) {
if (!applicationContext.checkNotificationPermission(CHANNEL_ID)) {
return
}
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(0)
.setSilent(true)
.setAutoCancel(true)
.setSubText(fileUri?.let { contentResolver.getFileDisplayName(it) })
when {
result.isAllSuccess -> {
if (isRestoreService) {
notification
.setContentTitle(getString(R.string.restoring_backup))
.setContentText(getString(R.string.data_restored_success))
} else {
notification
.setContentTitle(getString(R.string.backup_saved))
.setContentText(fileUri?.let { contentResolver.getFileDisplayName(it) })
.setSubText(null)
}
notification.setSmallIcon(R.drawable.ic_stat_done)
}
result.isAllFailed || !isRestoreService -> {
val title = getString(if (isRestoreService) R.string.data_not_restored else R.string.error_occurred)
val message = result.failures.joinToString("\n") {
it.getDisplayMessage(applicationContext.resources)
}
notification
.setContentText(if (isRestoreService) getString(R.string.data_not_restored_text) else message)
.setBigText(title, message)
.setSmallIcon(android.R.drawable.stat_notify_error)
result.failures.firstNotNullOfOrNull { error ->
ErrorReporterReceiver.getNotificationAction(applicationContext, error, startId, notificationTag)
}?.let { action ->
notification.addAction(action)
}
}
else -> {
notification
.setContentTitle(getString(R.string.restoring_backup))
.setContentText(getString(R.string.data_restored_with_errors))
.setSmallIcon(R.drawable.ic_stat_done)
}
}
notification.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
0,
AppRouter.homeIntent(this@BaseBackupRestoreService),
0,
false,
),
)
if (!isRestoreService && fileUri != null) {
val shareIntent = ShareCompat.IntentBuilder(this@BaseBackupRestoreService)
.setStream(fileUri)
.setType("application/zip")
.setChooserTitle(R.string.share_backup)
.createChooserIntent()
notification.addAction(
appcompatR.drawable.abc_ic_menu_share_mtrl_alpha,
getString(R.string.share),
PendingIntentCompat.getActivity(this@BaseBackupRestoreService, 0, shareIntent, 0, false),
)
}
notificationManager.notify(notificationTag, startId, notification.build())
}
protected fun NotificationCompat.Builder.setBigText(title: String, text: CharSequence) = setStyle(
NotificationCompat.BigTextStyle()
.bigText(text)
.setSummaryText(text)
.setBigContentTitle(title),
)
companion object {
const val CHANNEL_ID = "backup_restore"
fun createNotificationChannel(context: Context) {
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_HIGH)
.setName(context.getString(R.string.backup_restore))
.setShowBadge(true)
.setVibrationEnabled(false)
.setSound(null, null)
.setLightsEnabled(false)
.build()
NotificationManagerCompat.from(context).createNotificationChannel(channel)
}
}
}

View File

@@ -1,13 +1,11 @@
package org.koitharu.kotatsu.settings.backup
package org.koitharu.kotatsu.backups.ui.backup
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@@ -16,29 +14,14 @@ import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.core.util.progress.Progress
import org.koitharu.kotatsu.databinding.DialogProgressBinding
import java.io.File
import java.io.FileOutputStream
import kotlin.math.roundToInt
@AndroidEntryPoint
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
private val viewModel by viewModels<BackupViewModel>()
private var backup: File? = null
private val saveFileContract = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip"),
) { uri ->
val file = backup
if (uri != null && file != null) {
saveBackup(file, uri)
} else {
dismiss()
}
}
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -69,47 +52,20 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
dismiss()
}
private fun onProgressChanged(value: Float) {
private fun onProgressChanged(value: Progress) {
with(requireViewBinding().progressBar) {
isVisible = true
val wasIndeterminate = isIndeterminate
isIndeterminate = value < 0
if (value >= 0) {
setProgressCompat((value * max).roundToInt(), !wasIndeterminate)
isIndeterminate = value.isIndeterminate
if (!value.isIndeterminate) {
max = value.total
setProgressCompat(value.progress, !wasIndeterminate)
}
}
}
private fun onBackupDone(file: File) {
this.backup = file
if (!saveFileContract.tryLaunch(file.name)) {
Toast.makeText(requireContext(), R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
dismiss()
}
}
private fun saveBackup(file: File, output: Uri) {
try {
requireContext().contentResolver.openFileDescriptor(output, "w")?.use { fd ->
FileOutputStream(fd.fileDescriptor).use {
it.write(file.readBytes())
}
}
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()
dismiss()
} catch (e: InterruptedException) {
throw e
} catch (e: Exception) {
onError(e)
}
}
companion object {
private const val TAG = "BackupDialogFragment"
fun show(fm: FragmentManager) {
BackupDialogFragment().show(fm, TAG)
}
private fun onBackupDone(uri: Uri) {
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()
dismiss()
}
}

View File

@@ -0,0 +1,131 @@
package org.koitharu.kotatsu.backups.ui.backup
import android.annotation.SuppressLint
import android.app.Notification
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.net.Uri
import android.widget.Toast
import androidx.annotation.CheckResult
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.backups.data.BackupRepository
import org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.CompositeResult
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.powerManager
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
import org.koitharu.kotatsu.core.util.progress.Progress
import java.io.FileNotFoundException
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import androidx.appcompat.R as appcompatR
@AndroidEntryPoint
@SuppressLint("InlinedApi")
class BackupService : BaseBackupRestoreService() {
override val notificationTag = TAG
override val isRestoreService = false
@Inject
lateinit var repository: BackupRepository
override suspend fun IntentJobContext.processIntent(intent: Intent) {
val notification = buildNotification(Progress.INDETERMINATE)
setForeground(
FOREGROUND_NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
val destination = intent.getStringExtra(AppRouter.KEY_DATA)?.toUriOrNull() ?: throw FileNotFoundException()
powerManager.withPartialWakeLock(TAG) {
val progress = MutableStateFlow(Progress.INDETERMINATE)
val progressUpdateJob = if (checkNotificationPermission(CHANNEL_ID)) {
launch {
progress.collect {
notificationManager.notify(FOREGROUND_NOTIFICATION_ID, buildNotification(it))
}
}
} else {
null
}
try {
ZipOutputStream(contentResolver.openOutputStream(destination)).use { output ->
repository.createBackup(output, progress)
}
} catch (e: Throwable) {
try {
DocumentFile.fromSingleUri(applicationContext, destination)?.delete()
} catch (e2: Throwable) {
e.addSuppressed(e2)
}
throw e
}
progressUpdateJob?.cancelAndJoin()
contentResolver.notifyChange(destination, null)
showResultNotification(destination, CompositeResult.success())
withContext(Dispatchers.Main) {
Toast.makeText(this@BackupService, R.string.backup_saved, Toast.LENGTH_SHORT).show()
}
}
}
private fun IntentJobContext.buildNotification(progress: Progress): Notification {
return NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setContentTitle(getString(R.string.creating_backup))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(0)
.setSilent(true)
.setOngoing(true)
.setProgress(
progress.total.coerceAtLeast(0),
progress.progress.coerceAtLeast(0),
progress.isIndeterminate,
)
.setContentText(
if (progress.isIndeterminate) {
getString(R.string.processing_)
} else {
getString(R.string.fraction_pattern, progress.progress, progress.total)
},
)
.setSmallIcon(android.R.drawable.stat_sys_upload)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(
appcompatR.drawable.abc_ic_clear_material,
applicationContext.getString(android.R.string.cancel),
getCancelIntent(),
).build()
}
companion object {
private const val TAG = "BACKUP"
private const val FOREGROUND_NOTIFICATION_ID = 33
@CheckResult
fun start(context: Context, uri: Uri): Boolean = try {
val intent = Intent(context, BackupService::class.java)
intent.putExtra(AppRouter.KEY_DATA, uri.toString())
ContextCompat.startForegroundService(context, intent)
true
} catch (e: Exception) {
e.printStackTraceDebug()
false
}
}
}

View File

@@ -0,0 +1,44 @@
package org.koitharu.kotatsu.backups.ui.backup
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.backups.data.BackupRepository
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.progress.Progress
import java.util.zip.Deflater
import java.util.zip.ZipOutputStream
import javax.inject.Inject
@HiltViewModel
class BackupViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repository: BackupRepository,
@ApplicationContext context: Context,
) : BaseViewModel() {
val progress = MutableStateFlow(Progress.INDETERMINATE)
val onBackupDone = MutableEventFlow<Uri>()
private val destination = savedStateHandle.require<Uri>(AppRouter.KEY_DATA)
private val contentResolver: ContentResolver = context.contentResolver
init {
launchLoadingJob(Dispatchers.Default) {
ZipOutputStream(checkNotNull(contentResolver.openOutputStream(destination))).use {
it.setLevel(Deflater.BEST_COMPRESSION)
repository.createBackup(it, progress)
}
onBackupDone.call(destination)
}
}
}

View File

@@ -0,0 +1,105 @@
package org.koitharu.kotatsu.backups.ui.periodical
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.backups.data.BackupRepository
import org.koitharu.kotatsu.backups.domain.BackupUtils
import org.koitharu.kotatsu.backups.domain.ExternalBackupStorage
import org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService
import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import java.util.zip.ZipOutputStream
import javax.inject.Inject
@AndroidEntryPoint
class PeriodicalBackupService : CoroutineIntentService() {
@Inject
lateinit var externalBackupStorage: ExternalBackupStorage
@Inject
lateinit var telegramBackupUploader: TelegramBackupUploader
@Inject
lateinit var repository: BackupRepository
@Inject
lateinit var settings: AppSettings
override suspend fun IntentJobContext.processIntent(intent: Intent) {
if (!settings.isPeriodicalBackupEnabled || settings.periodicalBackupDirectory == null) {
return
}
val lastBackupDate = externalBackupStorage.getLastBackupDate()
if (lastBackupDate != null && lastBackupDate.time + settings.periodicalBackupFrequencyMillis > System.currentTimeMillis()) {
return
}
val output = BackupUtils.createTempFile(applicationContext)
try {
ZipOutputStream(output.outputStream()).use {
repository.createBackup(it, null)
}
externalBackupStorage.put(output)
externalBackupStorage.trim(settings.periodicalBackupMaxCount)
if (settings.isBackupTelegramUploadEnabled && telegramBackupUploader.isAvailable) {
telegramBackupUploader.uploadBackup(output)
}
} finally {
output.delete()
}
}
override fun IntentJobContext.onError(error: Throwable) {
if (!applicationContext.checkNotificationPermission(CHANNEL_ID)) {
return
}
BaseBackupRestoreService.createNotificationChannel(applicationContext)
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(0)
.setSilent(true)
.setAutoCancel(true)
val title = getString(R.string.periodic_backups)
val message = getString(
R.string.inline_preference_pattern,
getString(R.string.packup_creation_failed),
error.getDisplayMessage(resources),
)
notification
.setContentText(message)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(message)
.setSummaryText(getString(R.string.packup_creation_failed))
.setBigContentTitle(title),
)
ErrorReporterReceiver.getNotificationAction(applicationContext, error, startId, TAG)?.let { action ->
notification.addAction(action)
}
notification.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
0,
AppRouter.periodicBackupSettingsIntent(applicationContext),
0,
false,
),
)
NotificationManagerCompat.from(applicationContext).notify(TAG, startId, notification.build())
}
private companion object {
const val CHANNEL_ID = BaseBackupRestoreService.CHANNEL_ID
const val TAG = "periodical_backup"
}
}

View File

@@ -0,0 +1,107 @@
package org.koitharu.kotatsu.backups.ui.periodical
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.format.DateUtils
import android.view.View
import androidx.activity.result.ActivityResultCallback
import androidx.fragment.app.viewModels
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.settings.utils.EditTextFallbackSummaryProvider
import java.util.Date
import javax.inject.Inject
@AndroidEntryPoint
class PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodic_backups),
ActivityResultCallback<Uri?> {
@Inject
lateinit var telegramBackupUploader: TelegramBackupUploader
private val viewModel by viewModels<PeriodicalBackupSettingsViewModel>()
private val outputSelectCall = OpenDocumentTreeHelper(this, this)
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_backup_periodic)
findPreference<PreferenceCategory>(AppSettings.KEY_BACKUP_TG)?.isVisible = viewModel.isTelegramAvailable
findPreference<EditTextPreference>(AppSettings.KEY_BACKUP_TG_CHAT)?.summaryProvider =
EditTextFallbackSummaryProvider(R.string.telegram_chat_id_summary)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.lastBackupDate.observe(viewLifecycleOwner, ::bindLastBackupInfo)
viewModel.backupsDirectory.observe(viewLifecycleOwner, ::bindOutputSummary)
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
viewModel.isTelegramCheckLoading.observe(viewLifecycleOwner) {
findPreference<Preference>(AppSettings.KEY_BACKUP_TG_TEST)?.isEnabled = !it
}
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val result = when (preference.key) {
AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT -> outputSelectCall.tryLaunch(null)
AppSettings.KEY_BACKUP_TG_OPEN -> telegramBackupUploader.openBotInApp(router)
AppSettings.KEY_BACKUP_TG_TEST -> {
viewModel.checkTelegram()
true
}
else -> return super.onPreferenceTreeClick(preference)
}
if (!result) {
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
return true
}
override fun onActivityResult(result: Uri?) {
if (result != null) {
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context?.contentResolver?.takePersistableUriPermission(result, takeFlags)
settings.periodicalBackupDirectory = result
viewModel.updateSummaryData()
}
}
private fun bindOutputSummary(path: String?) {
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT) ?: return
preference.summary = when (path) {
null -> getString(R.string.invalid_value_message)
"" -> null
else -> path
}
preference.icon = if (path == null) {
getWarningIcon()
} else {
null
}
}
private fun bindLastBackupInfo(lastBackupDate: Date?) {
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_LAST) ?: return
preference.summary = lastBackupDate?.let {
preference.context.getString(
R.string.last_successful_backup,
DateUtils.getRelativeTimeSpanString(it.time),
)
}
preference.isVisible = lastBackupDate != null
}
}

View File

@@ -0,0 +1,79 @@
package org.koitharu.kotatsu.backups.ui.periodical
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.backups.domain.BackupUtils
import org.koitharu.kotatsu.backups.domain.ExternalBackupStorage
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.resolveFile
import java.util.Date
import javax.inject.Inject
@HiltViewModel
class PeriodicalBackupSettingsViewModel @Inject constructor(
private val settings: AppSettings,
private val telegramUploader: TelegramBackupUploader,
private val backupStorage: ExternalBackupStorage,
@ApplicationContext private val appContext: Context,
) : BaseViewModel() {
val isTelegramAvailable
get() = telegramUploader.isAvailable
val lastBackupDate = MutableStateFlow<Date?>(null)
val backupsDirectory = MutableStateFlow<String?>("")
val isTelegramCheckLoading = MutableStateFlow(false)
val onActionDone = MutableEventFlow<ReversibleAction>()
init {
updateSummaryData()
}
fun checkTelegram() {
launchJob(Dispatchers.Default) {
try {
isTelegramCheckLoading.value = true
telegramUploader.sendTestMessage()
onActionDone.call(ReversibleAction(R.string.connection_ok, null))
} finally {
isTelegramCheckLoading.value = false
}
}
}
fun updateSummaryData() {
updateBackupsDirectory()
updateLastBackupDate()
}
private fun updateBackupsDirectory() = launchJob(Dispatchers.Default) {
val dir = settings.periodicalBackupDirectory
backupsDirectory.value = if (dir != null) {
dir.toUserFriendlyString()
} else {
BackupUtils.getAppBackupDir(appContext).path
}
}
private fun updateLastBackupDate() = launchJob(Dispatchers.Default) {
lastBackupDate.value = backupStorage.getLastBackupDate()
}
private fun Uri.toUserFriendlyString(): String? {
val df = DocumentFile.fromTreeUri(appContext, this)
if (df?.canWrite() != true) {
return null
}
return resolveFile(appContext)?.path ?: toString()
}
}

View File

@@ -0,0 +1,96 @@
package org.koitharu.kotatsu.backups.ui.periodical
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)
val isAvailable: Boolean
get() = botToken.isNotEmpty()
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,4 +1,4 @@
package org.koitharu.kotatsu.settings.backup
package org.koitharu.kotatsu.backups.ui.restore
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.BaseListAdapter
@@ -8,18 +8,18 @@ import org.koitharu.kotatsu.databinding.ItemCheckableMultipleBinding
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_CHECKED_CHANGED
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
class BackupEntriesAdapter(
clickListener: OnListItemClickListener<BackupEntryModel>,
) : BaseListAdapter<BackupEntryModel>() {
class BackupSectionsAdapter(
clickListener: OnListItemClickListener<BackupSectionModel>,
) : BaseListAdapter<BackupSectionModel>() {
init {
addDelegate(ListItemType.NAV_ITEM, backupEntryAD(clickListener))
addDelegate(ListItemType.NAV_ITEM, backupSectionAD(clickListener))
}
}
private fun backupEntryAD(
clickListener: OnListItemClickListener<BackupEntryModel>,
) = adapterDelegateViewBinding<BackupEntryModel, BackupEntryModel, ItemCheckableMultipleBinding>(
private fun backupSectionAD(
clickListener: OnListItemClickListener<BackupSectionModel>,
) = adapterDelegateViewBinding<BackupSectionModel, BackupSectionModel, ItemCheckableMultipleBinding>(
{ layoutInflater, parent -> ItemCheckableMultipleBinding.inflate(layoutInflater, parent, false) },
) {

View File

@@ -0,0 +1,47 @@
package org.koitharu.kotatsu.backups.ui.restore
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.backups.domain.BackupSection
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
data class BackupSectionModel(
val section: BackupSection,
val isChecked: Boolean,
val isEnabled: Boolean,
) : ListModel {
@get:StringRes
val titleResId: Int
get() = when (section) {
BackupSection.INDEX -> 0 // should not appear here
BackupSection.HISTORY -> R.string.history
BackupSection.CATEGORIES -> R.string.favourites_categories
BackupSection.FAVOURITES -> R.string.favourites
BackupSection.SETTINGS -> R.string.settings
BackupSection.SETTINGS_READER_GRID -> R.string.reader_actions
BackupSection.BOOKMARKS -> R.string.bookmarks
BackupSection.SOURCES -> R.string.remote_sources
BackupSection.SCROBBLING -> R.string.tracking
BackupSection.STATS -> R.string.statistics
BackupSection.SAVED_FILTERS -> R.string.saved_filters
}
override fun areItemsTheSame(other: ListModel): Boolean {
return other is BackupSectionModel && other.section == section
}
override fun getChangePayload(previousState: ListModel): Any? {
if (previousState !is BackupSectionModel) {
return null
}
return if (previousState.isEnabled != isEnabled) {
ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED
} else if (previousState.isChecked != isChecked) {
ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED
} else {
super.getChangePayload(previousState)
}
}
}

View File

@@ -1,35 +1,31 @@
package org.koitharu.kotatsu.settings.backup
package org.koitharu.kotatsu.backups.ui.restore
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.backup.CompositeResult
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.DialogRestoreBinding
import org.koitharu.kotatsu.main.ui.welcome.WelcomeSheet
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.math.roundToInt
@AndroidEntryPoint
class RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnListItemClickListener<BackupEntryModel>,
class RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnListItemClickListener<BackupSectionModel>,
View.OnClickListener {
private val viewModel: RestoreViewModel by viewModels()
@@ -41,13 +37,11 @@ class RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnLis
override fun onViewBindingCreated(binding: DialogRestoreBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
val adapter = BackupEntriesAdapter(this)
val adapter = BackupSectionsAdapter(this)
binding.recyclerView.adapter = adapter
binding.buttonCancel.setOnClickListener(this)
binding.buttonRestore.setOnClickListener(this)
viewModel.availableEntries.observe(viewLifecycleOwner, adapter)
viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)
viewModel.onRestoreDone.observeEvent(viewLifecycleOwner, this::onRestoreDone)
viewModel.onError.observeEvent(viewLifecycleOwner, this::onError)
combine(
viewModel.isLoading,
@@ -66,15 +60,23 @@ class RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnLis
override fun onClick(v: View) {
when (v.id) {
R.id.button_cancel -> dismiss()
R.id.button_restore -> viewModel.restore()
R.id.button_restore -> {
if (startRestoreService()) {
Toast.makeText(v.context, R.string.backup_restored_background, Toast.LENGTH_SHORT).show()
router.closeWelcomeSheet()
dismiss()
} else {
Toast.makeText(v.context, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onItemClick(item: BackupEntryModel, view: View) {
override fun onItemClick(item: BackupSectionModel, view: View) {
viewModel.onItemClick(item)
}
private fun onLoadingChanged(value: Triple<Boolean, List<BackupEntryModel>, Date?>) {
private fun onLoadingChanged(value: Triple<Boolean, List<BackupSectionModel>, Date?>) {
val (isLoading, entries, backupDate) = value
val hasEntries = entries.isNotEmpty()
with(requireViewBinding()) {
@@ -90,6 +92,14 @@ class RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnLis
}
}
private fun startRestoreService(): Boolean {
return RestoreService.start(
context ?: return false,
viewModel.uri ?: return false,
viewModel.getCheckedSections(),
)
}
private fun Date.formatBackupDate(): String {
return getString(
R.string.backup_date_,
@@ -105,59 +115,4 @@ class RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnLis
.show()
dismiss()
}
private fun onProgressChanged(value: Float) {
with(requireViewBinding().progressBar) {
isVisible = true
val wasIndeterminate = isIndeterminate
isIndeterminate = value < 0
if (value >= 0) {
setProgressCompat((value * max).roundToInt(), !wasIndeterminate)
}
}
}
private fun onRestoreDone(result: CompositeResult) {
val builder = MaterialAlertDialogBuilder(context ?: return)
when {
result.isEmpty -> {
builder.setTitle(R.string.data_not_restored)
.setMessage(R.string.data_not_restored_text)
}
result.isAllSuccess -> {
builder.setTitle(R.string.data_restored)
.setMessage(R.string.data_restored_success)
}
result.isAllFailed -> builder.setTitle(R.string.error)
.setMessage(
result.failures.map {
it.getDisplayMessage(resources)
}.distinct().joinToString("\n"),
)
else -> builder.setTitle(R.string.data_restored)
.setMessage(R.string.data_restored_with_errors)
}
builder.setPositiveButton(android.R.string.ok, null)
.show()
if (!result.isEmpty && !result.isAllFailed) {
WelcomeSheet.dismiss(parentFragmentManager)
}
dismiss()
}
companion object {
const val ARG_FILE = "file"
private const val TAG = "RestoreDialogFragment"
fun show(fm: FragmentManager, uri: Uri) {
RestoreDialogFragment().withArgs(1) {
putString(ARG_FILE, uri.toString())
}.show(fm, TAG)
}
}
}

View File

@@ -0,0 +1,118 @@
package org.koitharu.kotatsu.backups.ui.restore
import android.annotation.SuppressLint
import android.app.Notification
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.net.Uri
import androidx.annotation.CheckResult
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.backups.data.BackupRepository
import org.koitharu.kotatsu.backups.domain.BackupSection
import org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.core.util.ext.powerManager
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
import org.koitharu.kotatsu.core.util.progress.Progress
import java.io.FileNotFoundException
import java.util.zip.ZipInputStream
import javax.inject.Inject
import androidx.appcompat.R as appcompatR
@AndroidEntryPoint
@SuppressLint("InlinedApi")
class RestoreService : BaseBackupRestoreService() {
override val notificationTag = TAG
override val isRestoreService = true
@Inject
lateinit var repository: BackupRepository
override suspend fun IntentJobContext.processIntent(intent: Intent) {
val notification = buildNotification(Progress.INDETERMINATE)
setForeground(
FOREGROUND_NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
val source = intent.getStringExtra(AppRouter.KEY_DATA)?.toUriOrNull() ?: throw FileNotFoundException()
val sections =
requireNotNull(intent.getSerializableExtraCompat<Array<BackupSection>>(AppRouter.KEY_ENTRIES)?.toSet())
powerManager.withPartialWakeLock(TAG) {
val progress = MutableStateFlow(Progress.INDETERMINATE)
val progressUpdateJob = if (checkNotificationPermission(CHANNEL_ID)) {
launch {
progress.collect {
notificationManager.notify(FOREGROUND_NOTIFICATION_ID, buildNotification(it))
}
}
} else {
null
}
val result = ZipInputStream(contentResolver.openInputStream(source)).use { input ->
repository.restoreBackup(input, sections, progress)
}
progressUpdateJob?.cancelAndJoin()
showResultNotification(source, result)
}
}
private fun IntentJobContext.buildNotification(progress: Progress): Notification {
return NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setContentTitle(getString(R.string.restoring_backup))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(0)
.setSilent(true)
.setOngoing(true)
.setProgress(
progress.total.coerceAtLeast(0),
progress.progress.coerceAtLeast(0),
progress.isIndeterminate,
)
.setContentText(
if (progress.isIndeterminate) {
getString(R.string.processing_)
} else {
getString(R.string.fraction_pattern, progress.progress, progress.total)
},
)
.setSmallIcon(android.R.drawable.stat_sys_upload)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(
appcompatR.drawable.abc_ic_clear_material,
applicationContext.getString(android.R.string.cancel),
getCancelIntent(),
).build()
}
companion object {
private const val TAG = "RESTORE"
private const val FOREGROUND_NOTIFICATION_ID = 39
@CheckResult
fun start(context: Context, uri: Uri, sections: Set<BackupSection>): Boolean = try {
val intent = Intent(context, RestoreService::class.java)
intent.putExtra(AppRouter.KEY_DATA, uri.toString())
intent.putExtra(AppRouter.KEY_ENTRIES, sections.toTypedArray())
ContextCompat.startForegroundService(context, intent)
true
} catch (e: Exception) {
e.printStackTraceDebug()
false
}
}
}

View File

@@ -0,0 +1,112 @@
package org.koitharu.kotatsu.backups.ui.restore
import android.content.Context
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runInterruptible
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.koitharu.kotatsu.backups.data.model.BackupIndex
import org.koitharu.kotatsu.backups.domain.BackupSection
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import java.io.FileNotFoundException
import java.io.InputStream
import java.util.Date
import java.util.EnumMap
import java.util.EnumSet
import java.util.zip.ZipInputStream
import javax.inject.Inject
@HiltViewModel
class RestoreViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
@ApplicationContext context: Context,
) : BaseViewModel() {
val uri = savedStateHandle.get<String>(AppRouter.KEY_FILE)?.toUriOrNull()
private val contentResolver = context.contentResolver
val availableEntries = MutableStateFlow<List<BackupSectionModel>>(emptyList())
val backupDate = MutableStateFlow<Date?>(null)
init {
launchLoadingJob(Dispatchers.Default) {
loadBackupInfo()
}
}
private suspend fun loadBackupInfo() {
val sections = runInterruptible(Dispatchers.IO) {
if (uri == null) throw FileNotFoundException()
ZipInputStream(contentResolver.openInputStream(uri)).use { stream ->
val result = EnumSet.noneOf(BackupSection::class.java)
var entry = stream.nextEntry
while (entry != null) {
val s = BackupSection.of(entry)
if (s != null) {
result.add(s)
if (s == BackupSection.INDEX) {
backupDate.value = stream.readDate()
}
}
stream.closeEntry()
entry = stream.nextEntry
}
result
}
}
availableEntries.value = BackupSection.entries.mapNotNull { entry ->
if (entry == BackupSection.INDEX || entry !in sections) {
return@mapNotNull null
}
BackupSectionModel(
section = entry,
isChecked = true,
isEnabled = true,
)
}
}
fun onItemClick(item: BackupSectionModel) {
val map = availableEntries.value.associateByTo(EnumMap(BackupSection::class.java)) { it.section }
map[item.section] = item.copy(isChecked = !item.isChecked)
map.validate()
availableEntries.value = map.values.sortedBy { it.section.ordinal }
}
fun getCheckedSections(): Set<BackupSection> = availableEntries.value
.mapNotNullTo(EnumSet.noneOf(BackupSection::class.java)) {
if (it.isChecked) it.section else null
}
/**
* Check for inconsistent user selection
* Favorites cannot be restored without categories
*/
private fun MutableMap<BackupSection, BackupSectionModel>.validate() {
val favorites = this[BackupSection.FAVOURITES] ?: return
val categories = this[BackupSection.CATEGORIES]
if (categories?.isChecked == true) {
if (!favorites.isEnabled) {
this[BackupSection.FAVOURITES] = favorites.copy(isEnabled = true)
}
} else {
if (favorites.isEnabled) {
this[BackupSection.FAVOURITES] = favorites.copy(isEnabled = false, isChecked = false)
}
}
}
private fun InputStream.readDate(): Date? = runCatching {
val index = Json.decodeFromStream<List<BackupIndex>>(this)
Date(index.single().createdAt)
}.onFailure { e ->
e.printStackTraceDebug()
}.getOrNull()
}

View File

@@ -6,7 +6,10 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
@Dao
@@ -17,9 +20,9 @@ abstract class BookmarksDao {
@Transaction
@Query(
"SELECT * FROM manga JOIN bookmarks ON bookmarks.manga_id = manga.manga_id ORDER BY percent",
"SELECT * FROM manga JOIN bookmarks ON bookmarks.manga_id = manga.manga_id ORDER BY percent LIMIT :limit OFFSET :offset",
)
abstract suspend fun findAll(): Map<MangaWithTags, List<BookmarkEntity>>
abstract suspend fun findAll(offset: Int, limit: Int): Map<MangaWithTags, List<BookmarkEntity>>
@Query("SELECT * FROM bookmarks WHERE manga_id = :mangaId AND chapter_id = :chapterId AND page = :page ORDER BY percent")
abstract fun observe(mangaId: Long, chapterId: Long, page: Int): Flow<BookmarkEntity?>
@@ -47,4 +50,17 @@ abstract class BookmarksDao {
@Upsert
abstract suspend fun upsert(bookmarks: Collection<BookmarkEntity>)
fun dump(): Flow<Pair<MangaWithTags, List<BookmarkEntity>>> = flow {
val window = 4
var offset = 0
while (currentCoroutineContext().isActive) {
val list = findAll(offset, window)
if (list.isEmpty()) {
break
}
offset += window
list.forEach { emit(it.key to it.value) }
}
}
}

View File

@@ -1,7 +1,8 @@
package org.koitharu.kotatsu.bookmarks.domain
import org.koitharu.kotatsu.core.util.MimeTypes
import org.koitharu.kotatsu.core.util.ext.isImage
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.local.data.hasImageExtension
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import java.time.Instant
@@ -17,9 +18,6 @@ data class Bookmark(
val percent: Float,
) : ListModel {
val imageLoadData: Any
get() = if (isImageUrlDirect()) imageUrl else toMangaPage()
override fun areItemsTheSame(other: ListModel): Boolean {
return other is Bookmark &&
manga.id == other.manga.id &&
@@ -30,11 +28,9 @@ data class Bookmark(
fun toMangaPage() = MangaPage(
id = pageId,
url = imageUrl,
preview = null,
preview = imageUrl.takeIf {
MimeTypes.getMimeTypeFromUrl(it)?.isImage == true
},
source = manga.source,
)
private fun isImageUrlDirect(): Boolean {
return hasImageExtension(imageUrl)
}
}

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

@@ -9,28 +9,28 @@ 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 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
@@ -39,7 +39,7 @@ 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 org.koitharu.kotatsu.reader.ui.PageSaveHelper
import javax.inject.Inject
@AndroidEntryPoint
@@ -50,16 +50,22 @@ class AllBookmarksFragment :
ListSelectionController.Callback,
FastScroller.FastScrollListener, ListHeaderClickListener {
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var settings: AppSettings
@Inject
lateinit var pageSaveHelperFactory: PageSaveHelper.Factory
private lateinit var pageSaveHelper: PageSaveHelper
private val viewModel by viewModels<AllBookmarksViewModel>()
private var bookmarksAdapter: BookmarksAdapter? = null
private var selectionController: ListSelectionController? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageSaveHelper = pageSaveHelperFactory.create(this)
}
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -79,8 +85,6 @@ class AllBookmarksFragment :
callback = this,
)
bookmarksAdapter = BookmarksAdapter(
lifecycleOwner = viewLifecycleOwner,
coil = coil,
clickListener = this,
headerClickListener = this,
)
@@ -107,6 +111,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
@@ -115,26 +131,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)
.incognito()
.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(view, item.pageId) ?: false
return selectionController?.onItemLongClick(view, item.pageId) == true
}
override fun onItemContextClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.pageId) ?: false
return selectionController?.onItemContextClick(view, item.pageId) == true
}
override fun onRetryClick(error: Throwable) = Unit
@@ -173,17 +189,13 @@ class AllBookmarksFragment :
true
}
else -> false
}
}
R.id.action_save -> {
viewModel.savePages(pageSaveHelper, selectionController?.snapshot() ?: return false)
mode?.finish()
true
}
override fun onWindowInsetsChanged(insets: Insets) {
val rv = requireViewBinding().recyclerView
rv.updatePadding(
bottom = insets.bottom + rv.paddingTop,
)
rv.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = insets.bottom
else -> false
}
}
@@ -208,16 +220,4 @@ class AllBookmarksFragment :
invalidateSpanIndexCache()
}
}
companion object {
@Deprecated(
"",
ReplaceWith(
"BookmarksFragment()",
"org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment",
),
)
fun newInstance() = AllBookmarksFragment()
}
}

View File

@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.PageSaveHelper
import javax.inject.Inject
@HiltViewModel
@@ -56,6 +57,23 @@ class AllBookmarksViewModel @Inject constructor(
}
}
fun savePages(pageSaveHelper: PageSaveHelper, ids: Set<Long>) {
launchLoadingJob(Dispatchers.Default) {
val tasks = content.value.mapNotNull {
if (it !is Bookmark || it.pageId !in ids) return@mapNotNull null
PageSaveHelper.Task(
manga = it.manga,
chapterId = it.chapterId,
pageNumber = it.page + 1,
page = it.toMangaPage(),
)
}
val dest = pageSaveHelper.save(tasks)
val msg = if (dest.size == 1) R.string.page_saved else R.string.pages_saved
onActionDone.call(ReversibleAction(msg, null))
}
}
private fun mapList(data: Map<Manga, List<Bookmark>>): List<ListModel> {
val result = ArrayList<ListModel>(data.values.sumOf { it.size + 1 })
for ((manga, bookmarks) in data) {

View File

@@ -1,24 +1,13 @@
package org.koitharu.kotatsu.bookmarks.ui.adapter
import androidx.lifecycle.LifecycleOwner
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.databinding.ItemBookmarkLargeBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
fun bookmarkLargeAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Bookmark>,
) = adapterDelegateViewBinding<Bookmark, ListModel, ItemBookmarkLargeBinding>(
{ inflater, parent -> ItemBookmarkLargeBinding.inflate(inflater, parent, false) },
@@ -26,14 +15,7 @@ fun bookmarkLargeAD(
AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
bind {
binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run {
size(CoverSizeResolver(binding.imageViewThumb))
defaultPlaceholders(context)
allowRgb565(true)
bookmarkExtra(item)
decodeRegion(item.scroll)
enqueueWith(coil)
}
binding.imageViewThumb.setImageAsync(item)
binding.progressView.setProgress(item.percent, false)
}
}

View File

@@ -1,38 +0,0 @@
package org.koitharu.kotatsu.bookmarks.ui.adapter
import androidx.lifecycle.LifecycleOwner
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.databinding.ItemBookmarkBinding
// TODO check usages
fun bookmarkListAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Bookmark>,
) = adapterDelegateViewBinding<Bookmark, Bookmark, ItemBookmarkBinding>(
{ inflater, parent -> ItemBookmarkBinding.inflate(inflater, parent, false) },
) {
AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
bind {
binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run {
size(CoverSizeResolver(binding.imageViewThumb))
defaultPlaceholders(context)
allowRgb565(true)
bookmarkExtra(item)
decodeRegion(item.scroll)
enqueueWith(coil)
}
}
}

View File

@@ -1,8 +1,6 @@
package org.koitharu.kotatsu.bookmarks.ui.adapter
import android.content.Context
import androidx.lifecycle.LifecycleOwner
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,24 +8,24 @@ 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
import org.koitharu.kotatsu.list.ui.model.ListModel
class BookmarksAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Bookmark>,
headerClickListener: ListHeaderClickListener?,
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init {
addDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(coil, lifecycleOwner, clickListener))
addDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(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))
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null))
}
override fun getSectionText(context: Context, position: Int): CharSequence? {

View File

@@ -0,0 +1,20 @@
package org.koitharu.kotatsu.browser
import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import javax.inject.Inject
@AndroidEntryPoint
class AdListUpdateService : CoroutineIntentService() {
@Inject
lateinit var updater: AdBlock.Updater
override suspend fun IntentJobContext.processIntent(intent: Intent) {
updater.updateList()
}
override fun IntentJobContext.onError(error: Throwable) = Unit
}

View File

@@ -0,0 +1,111 @@
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.model.MangaSource
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
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.consumeAll
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import javax.inject.Inject
@AndroidEntryPoint
abstract class BaseBrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
@Inject
lateinit var proxyProvider: ProxyProvider
@Inject
lateinit var mangaRepositoryFactory: MangaRepository.Factory
@Inject
lateinit var adBlock: AdBlock
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)
val mangaSource = MangaSource(intent?.getStringExtra(AppRouter.KEY_SOURCE))
val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository
val userAgent = intent?.getStringExtra(AppRouter.KEY_USER_AGENT)?.nullIfEmpty()
?: repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)
viewBinding.webView.configureForParser(userAgent)
onCreate2(savedInstanceState, mangaSource, repository)
}
protected abstract fun onCreate2(
savedInstanceState: Bundle?,
source: MangaSource,
repository: ParserMangaRepository?
)
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,68 +1,49 @@
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.activity.result.contract.ActivityResultContract
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
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.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
class BrowserActivity : BaseBrowserActivity() {
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
@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))
val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository
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)
override fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
viewBinding.webView.webViewClient = BrowserClient(this, adBlock)
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)
}
}
}
}
@@ -80,14 +61,8 @@ 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
}
@@ -95,58 +70,22 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
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,
class Contract : ActivityResultContract<InteractiveActionRequiredException, Unit>() {
override fun createIntent(
context: Context,
input: InteractiveActionRequiredException
): Intent = AppRouter.browserIntent(
context = context,
url = input.url,
source = input.source,
title = null,
)
override fun parseResult(resultCode: Int, intent: Intent?): Unit = Unit
}
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)
}
const val TAG = "BrowserActivity"
}
}

View File

@@ -1,10 +1,27 @@
package org.koitharu.kotatsu.browser
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Looper
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
import java.io.ByteArrayInputStream
open class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
open class BrowserClient(
private val callback: BrowserCallback,
private val adBlock: AdBlock?,
) : WebViewClient() {
/**
* https://stackoverflow.com/questions/57414530/illegalstateexception-reasonphrase-cant-be-empty-with-android-webview
*/
override fun onPageFinished(webView: WebView, url: String) {
super.onPageFinished(webView, url)
@@ -16,7 +33,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)
}
@@ -25,4 +42,39 @@ open class BrowserClient(private val callback: BrowserCallback) : WebViewClient(
super.doUpdateVisitedHistory(view, url, isReload)
callback.onHistoryChanged()
}
@WorkerThread
@Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(
view: WebView?,
url: String?
): WebResourceResponse? = if (url.isNullOrEmpty() || adBlock?.shouldLoadUrl(url, view?.getUrlSafe()) ?: true) {
super.shouldInterceptRequest(view, url)
} else {
emptyResponse()
}
@WorkerThread
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? =
if (request == null || adBlock?.shouldLoadUrl(request.url.toString(), view?.getUrlSafe()) ?: true) {
super.shouldInterceptRequest(view, request)
} else {
emptyResponse()
}
private fun emptyResponse(): WebResourceResponse =
WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream(byteArrayOf()))
@SuppressLint("WrongThread")
@AnyThread
private fun WebView.getUrlSafe(): String? = if (Looper.myLooper() == Looper.getMainLooper()) {
url
} else {
runBlocking(Dispatchers.Main.immediate) {
url
}
}
}

View File

@@ -1,106 +0,0 @@
package org.koitharu.kotatsu.browser.cloudflare
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.net.toUri
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.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.parsers.model.MangaSource
class CaptchaNotifier(
private val context: Context,
) : EventListener() {
fun notify(exception: CloudFlareProtectedException) {
if (!context.checkNotificationPermission(CHANNEL_ID)) {
return
}
val manager = NotificationManagerCompat.from(context)
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
.setName(context.getString(R.string.captcha_required))
.setShowBadge(true)
.setVibrationEnabled(false)
.setSound(null, null)
.setLightsEnabled(false)
.build()
manager.createNotificationChannel(channel)
val intent = CloudFlareActivity.newIntent(context, exception)
.setData(exception.url.toUri())
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(channel.name)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setDefaults(0)
.setSmallIcon(R.drawable.ic_bot)
.setGroup(GROUP_CAPTCHA)
.setAutoCancel(true)
.setVisibility(
if (exception.source?.isNsfw() == true) {
NotificationCompat.VISIBILITY_SECRET
} else {
NotificationCompat.VISIBILITY_PUBLIC
},
)
.setContentText(
context.getString(
R.string.captcha_required_summary,
exception.source?.getTitle(context) ?: context.getString(R.string.app_name),
),
)
.setContentIntent(PendingIntentCompat.getActivity(context, 0, intent, 0, false))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val actionIntent = PendingIntentCompat.getActivity(
context, SETTINGS_ACTION_CODE,
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
.putExtra(Settings.EXTRA_CHANNEL_ID, CHANNEL_ID),
0, false,
)
notification.addAction(
R.drawable.ic_settings,
context.getString(R.string.notifications_settings),
actionIntent,
)
}
manager.notify(TAG, exception.source.hashCode(), notification.build())
}
fun dismiss(source: MangaSource) {
NotificationManagerCompat.from(context).cancel(TAG, source.hashCode())
}
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
val e = result.throwable
if (e is CloudFlareProtectedException && request.extras[ignoreCaptchaKey] != true) {
notify(e)
}
}
companion object {
fun ImageRequest.Builder.ignoreCaptchaErrors() = apply {
extras[ignoreCaptchaKey] = true
}
val ignoreCaptchaKey = Extras.Key(false)
private const val CHANNEL_ID = "captcha"
private const val TAG = CHANNEL_ID
private const val GROUP_CAPTCHA = "org.koitharu.kotatsu.CAPTCHA"
private const val SETTINGS_ACTION_CODE = 3
}
}

View File

@@ -1,87 +1,70 @@
package org.koitharu.kotatsu.browser.cloudflare
import android.app.Activity
import android.content.Context
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.exceptions.resolve.CaptchaHandler
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.ext.configureForParser
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
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
@Inject
lateinit var cookieJar: MutableCookieJar
private lateinit var cfClient: CloudFlareClient
private var onBackPressedCallback: WebViewBackPressedCallback? = null
@Inject
lateinit var captchaHandler: CaptchaHandler
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)
}
private lateinit var cfClient: CloudFlareClient
override fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
val url = intent?.dataString
if (url.isNullOrEmpty()) {
finishAfterTransition()
return
}
cfClient = CloudFlareClient(cookieJar, this, url)
viewBinding.webView.configureForParser(intent?.getStringExtra(ARG_UA))
cfClient = CloudFlareClient(cookieJar, this, adBlock, url)
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 {
@@ -89,17 +72,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()
@@ -115,21 +87,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
}
@@ -140,25 +104,22 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
override fun onCheckPassed() {
pendingResult = RESULT_OK
val source = intent?.getStringExtra(ARG_SOURCE)
if (source != null) {
CaptchaNotifier(this).dismiss(MangaSource(source))
lifecycleScope.launch {
val source = intent?.getStringExtra(AppRouter.KEY_SOURCE)
if (source != null) {
runCatchingCancellable {
captchaHandler.discard(MangaSource(source))
}.onFailure {
it.printStackTraceDebug()
}
}
finishAfterTransition()
}
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 =
subtitle?.toString()?.toHttpUrlOrNull()?.topPrivateDomain() ?: subtitle
supportActionBar?.subtitle = subtitle?.toString()?.toHttpUrlOrNull()?.host.ifNullOrEmpty { subtitle }
}
private fun restartCheck() {
@@ -182,38 +143,16 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
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?): Boolean {
return resultCode == Activity.RESULT_OK
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

@@ -4,6 +4,7 @@ import android.graphics.Bitmap
import android.webkit.WebView
import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.network.webview.adblock.AdBlock
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
private const val LOOP_COUNTER = 3
@@ -11,8 +12,9 @@ private const val LOOP_COUNTER = 3
class CloudFlareClient(
private val cookieJar: MutableCookieJar,
private val callback: CloudFlareCallback,
adBlock: AdBlock,
private val targetUrl: String,
) : BrowserClient(callback) {
) : BrowserClient(callback, adBlock) {
private val oldClearance = getClearance()
private var counter = 0
@@ -22,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()
}

View File

@@ -6,6 +6,7 @@ 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 coil3.ImageLoader
@@ -30,8 +31,9 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
import org.koitharu.kotatsu.backups.domain.BackupObserver
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler
import org.koitharu.kotatsu.core.image.AvifImageDecoder
import org.koitharu.kotatsu.core.image.CbzFetcher
import org.koitharu.kotatsu.core.image.MangaSourceHeaderInterceptor
@@ -40,25 +42,27 @@ import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.util.AcraScreenLogger
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.connectivityManager
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.FaviconCache
import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.PageCache
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
import org.koitharu.kotatsu.main.ui.protect.ScreenshotPolicyHelper
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.settings.backup.BackupObserver
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.widget.WidgetUpdater
import javax.inject.Provider
@@ -76,6 +80,12 @@ interface AppModule {
companion object {
@Provides
@LocalizedAppContext
fun provideLocalizedContext(
@ApplicationContext context: Context,
): Context = ContextCompat.getContextForLanguage(context)
@Provides
@Singleton
fun provideNetworkState(
@@ -92,13 +102,14 @@ interface AppModule {
@Provides
@Singleton
fun provideCoil(
@ApplicationContext context: Context,
@LocalizedAppContext context: Context,
@MangaHttpClient okHttpClientProvider: Provider<OkHttpClient>,
mangaRepositoryFactory: MangaRepository.Factory,
faviconFetcherFactory: FaviconFetcher.Factory,
imageProxyInterceptor: ImageProxyInterceptor,
pageFetcherFactory: MangaPageFetcher.Factory,
coverRestoreInterceptor: CoverRestoreInterceptor,
networkStateProvider: Provider<NetworkState>,
captchaHandler: CaptchaHandler,
): ImageLoader {
val diskCacheFactory = {
val rootDir = context.externalCacheDir ?: context.cacheDir
@@ -114,7 +125,7 @@ interface AppModule {
.diskCache(diskCacheFactory)
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
.allowRgb565(context.isLowRamDevice())
.eventListener(CaptchaNotifier(context))
.eventListener(captchaHandler)
.components {
add(
OkHttpNetworkFetcherFactory(
@@ -130,7 +141,7 @@ interface AppModule {
add(SvgDecoder.Factory())
add(CbzFetcher.Factory())
add(AvifImageDecoder.Factory())
add(FaviconFetcher.Factory(mangaRepositoryFactory))
add(faviconFetcherFactory)
add(MangaPageKeyer())
add(pageFetcherFactory)
add(imageProxyInterceptor)
@@ -187,5 +198,29 @@ interface AppModule {
fun provideWorkManager(
@ApplicationContext context: Context,
): WorkManager = WorkManager.getInstance(context)
@Provides
@Singleton
@PageCache
fun providePageCache(
@ApplicationContext context: Context,
) = LocalStorageCache(
context = context,
dir = CacheDir.PAGES,
defaultSize = FileSize.MEGABYTES.convert(200, FileSize.BYTES),
minSize = FileSize.MEGABYTES.convert(20, FileSize.BYTES),
)
@Provides
@Singleton
@FaviconCache
fun provideFaviconCache(
@ApplicationContext context: Context,
) = LocalStorageCache(
context = context,
dir = CacheDir.FAVICONS,
defaultSize = FileSize.MEGABYTES.convert(8, FileSize.BYTES),
minSize = FileSize.MEGABYTES.convert(2, FileSize.BYTES),
)
}
}

View File

@@ -8,12 +8,11 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.hilt.work.HiltWorkerFactory
import androidx.room.InvalidationTracker
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 okhttp3.internal.platform.PlatformRegistry
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.dialog
@@ -26,12 +25,13 @@ 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
@@ -61,9 +61,6 @@ open class BaseApp : Application(), Configuration.Provider {
@Inject
lateinit var workScheduleManager: WorkScheduleManager
@Inject
lateinit var workManagerProvider: Provider<WorkManager>
@Inject
lateinit var localMangaIndexProvider: Provider<LocalMangaIndex>
@@ -78,32 +75,32 @@ open class BaseApp : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()
PlatformRegistry.applicationContext = this // TODO replace with OkHttp.initialize
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()
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
if (ACRA.isACRASenderServiceProcess()) {
return
}
initAcra {
buildConfigClass = BuildConfig::class.java
reportFormat = StringFormat.JSON

View File

@@ -4,10 +4,13 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.BadParcelableException
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.net.toUri
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
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
@@ -15,24 +18,63 @@ 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
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0)
if (notificationId != 0 && context != null) {
val notificationTag = intent.getStringExtra(EXTRA_NOTIFICATION_TAG)
NotificationManagerCompat.from(context).cancel(notificationTag, notificationId)
}
e.report()
}
companion object {
private const val EXTRA_ERROR = "err"
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
private const val EXTRA_NOTIFICATION_ID = "notify.id"
private const val EXTRA_NOTIFICATION_TAG = "notify.tag"
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = try {
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = getPendingIntentInternal(
context = context,
e = e,
notificationId = 0,
notificationTag = null,
)
fun getNotificationAction(
context: Context,
e: Throwable,
notificationId: Int,
notificationTag: String?,
): NotificationCompat.Action? {
val intent = getPendingIntentInternal(
context = context,
e = e,
notificationId = notificationId,
notificationTag = notificationTag,
) ?: return null
return NotificationCompat.Action(
R.drawable.ic_alert_outline,
context.getString(R.string.report),
intent,
)
}
private fun getPendingIntentInternal(
context: Context,
e: Throwable,
notificationId: Int,
notificationTag: String?,
): PendingIntent? = runCatching {
val intent = Intent(context, ErrorReporterReceiver::class.java)
intent.setAction(ACTION_REPORT)
intent.setData(Uri.parse("err://${e.hashCode()}"))
intent.putExtra(EXTRA_ERROR, e)
intent.setData("err://${e.hashCode()}".toUri())
intent.putExtra(AppRouter.KEY_ERROR, e)
intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId)
intent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag)
PendingIntentCompat.getBroadcast(context, 0, intent, 0, false)
} catch (e: BadParcelableException) {
}.onFailure { e ->
// probably cannot write exception as serializable
e.printStackTraceDebug()
null
}
}.getOrNull()
}
}

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

@@ -1,22 +0,0 @@
package org.koitharu.kotatsu.core.backup
import org.json.JSONArray
class BackupEntry(
val name: Name,
val data: JSONArray
) {
enum class Name(
val key: String,
) {
INDEX("index"),
HISTORY("history"),
CATEGORIES("categories"),
FAVOURITES("favourites"),
SETTINGS("settings"),
BOOKMARKS("bookmarks"),
SOURCES("sources"),
}
}

View File

@@ -1,224 +0,0 @@
package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.prefs.AppSettings
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 java.util.Date
import javax.inject.Inject
private const val PAGE_SIZE = 10
class BackupRepository @Inject constructor(
private val db: MangaDatabase,
private val settings: AppSettings,
) {
suspend fun dumpHistory(): BackupEntry {
var offset = 0
val entry = BackupEntry(BackupEntry.Name.HISTORY, JSONArray())
while (true) {
val history = db.getHistoryDao().findAll(offset, PAGE_SIZE)
if (history.isEmpty()) {
break
}
offset += history.size
for (item in history) {
val manga = JsonSerializer(item.manga).toJson()
val tags = JSONArray()
item.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
manga.put("tags", tags)
val json = JsonSerializer(item.history).toJson()
json.put("manga", manga)
entry.data.put(json)
}
}
return entry
}
suspend fun dumpCategories(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.CATEGORIES, JSONArray())
val categories = db.getFavouriteCategoriesDao().findAll()
for (item in categories) {
entry.data.put(JsonSerializer(item).toJson())
}
return entry
}
suspend fun dumpFavourites(): BackupEntry {
var offset = 0
val entry = BackupEntry(BackupEntry.Name.FAVOURITES, JSONArray())
while (true) {
val favourites = db.getFavouritesDao().findAllRaw(offset, PAGE_SIZE)
if (favourites.isEmpty()) {
break
}
offset += favourites.size
for (item in favourites) {
val manga = JsonSerializer(item.manga).toJson()
val tags = JSONArray()
item.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
manga.put("tags", tags)
val json = JsonSerializer(item.favourite).toJson()
json.put("manga", manga)
entry.data.put(json)
}
}
return entry
}
suspend fun dumpBookmarks(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.BOOKMARKS, JSONArray())
val all = db.getBookmarksDao().findAll()
for ((m, b) in all) {
val json = JSONObject()
val manga = JsonSerializer(m.manga).toJson()
json.put("manga", manga)
val tags = JSONArray()
m.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
json.put("tags", tags)
val bookmarks = JSONArray()
b.forEach { bookmarks.put(JsonSerializer(it).toJson()) }
json.put("bookmarks", bookmarks)
entry.data.put(json)
}
return entry
}
fun dumpSettings(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.SETTINGS, JSONArray())
val settingsDump = settings.getAllValues().toMutableMap()
settingsDump.remove(AppSettings.KEY_APP_PASSWORD)
settingsDump.remove(AppSettings.KEY_PROXY_PASSWORD)
settingsDump.remove(AppSettings.KEY_PROXY_LOGIN)
settingsDump.remove(AppSettings.KEY_INCOGNITO_MODE)
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()
for (source in all) {
val json = JsonSerializer(source).toJson()
entry.data.put(json)
}
return entry
}
fun createIndex(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.INDEX, JSONArray())
val json = JSONObject()
json.put("app_id", BuildConfig.APPLICATION_ID)
json.put("app_version", BuildConfig.VERSION_CODE)
json.put("created_at", System.currentTimeMillis())
entry.data.put(json)
return entry
}
fun getBackupDate(entry: BackupEntry?): Date? {
val timestamp = entry?.data?.optJSONObject(0)?.getLongOrDefault("created_at", 0) ?: 0
return if (timestamp == 0L) null else Date(timestamp)
}
suspend fun restoreHistory(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = mangaJson.getJSONArray("tags").mapJSON {
JsonDeserializer(it).toTagEntity()
}
val history = JsonDeserializer(item).toHistoryEntity()
result += runCatchingCancellable {
db.withTransaction {
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga, tags)
db.getHistoryDao().upsert(history)
}
}
}
return result
}
suspend fun restoreCategories(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
val category = JsonDeserializer(item).toFavouriteCategoryEntity()
result += runCatchingCancellable {
db.getFavouriteCategoriesDao().upsert(category)
}
}
return result
}
suspend fun restoreFavourites(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = mangaJson.getJSONArray("tags").mapJSON {
JsonDeserializer(it).toTagEntity()
}
val favourite = JsonDeserializer(item).toFavouriteEntity()
result += runCatchingCancellable {
db.withTransaction {
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga, tags)
db.getFavouritesDao().upsert(favourite)
}
}
}
return result
}
suspend fun restoreBookmarks(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = item.getJSONArray("tags").mapJSON {
JsonDeserializer(it).toTagEntity()
}
val bookmarks = item.getJSONArray("bookmarks").mapJSON {
JsonDeserializer(it).toBookmarkEntity()
}
result += runCatchingCancellable {
db.withTransaction {
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga, tags)
db.getBookmarksDao().upsert(bookmarks)
}
}
}
return result
}
suspend fun restoreSources(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
val source = JsonDeserializer(item).toMangaSourceEntity()
result += runCatchingCancellable {
db.getSourcesDao().upsert(source)
}
}
return result
}
fun restoreSettings(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.asTypedList<JSONObject>()) {
result += runCatchingCancellable {
settings.upsertAll(JsonDeserializer(item).toMap())
}
}
return result
}
}

View File

@@ -1,61 +0,0 @@
package org.koitharu.kotatsu.core.backup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okhttp3.internal.closeQuietly
import okio.Closeable
import org.json.JSONArray
import org.koitharu.kotatsu.core.exceptions.BadBackupFormatException
import java.io.File
import java.util.EnumSet
import java.util.zip.ZipException
import java.util.zip.ZipFile
class BackupZipInput private constructor(val file: File) : Closeable {
private val zipFile = ZipFile(file)
suspend fun getEntry(name: BackupEntry.Name): BackupEntry? = runInterruptible(Dispatchers.IO) {
val entry = zipFile.getEntry(name.key) ?: return@runInterruptible null
val json = zipFile.getInputStream(entry).use {
JSONArray(it.bufferedReader().readText())
}
BackupEntry(name, json)
}
suspend fun entries(): Set<BackupEntry.Name> = runInterruptible(Dispatchers.IO) {
zipFile.entries().toList().mapNotNullTo(EnumSet.noneOf(BackupEntry.Name::class.java)) { ze ->
BackupEntry.Name.entries.find { it.key == ze.name }
}
}
override fun close() {
zipFile.close()
}
fun closeAndDelete() {
closeQuietly()
file.delete()
}
companion object {
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
}
}
}
}
}

View File

@@ -1,60 +0,0 @@
package org.koitharu.kotatsu.core.backup
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.zip.ZipOutput
import java.io.File
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.zip.Deflater
class BackupZipOutput(val file: File) : Closeable {
private val output = ZipOutput(file, Deflater.BEST_COMPRESSION)
suspend fun put(entry: BackupEntry) = runInterruptible(Dispatchers.IO) {
output.put(entry.name.key, entry.data.toString(2))
}
suspend fun finish() = runInterruptible(Dispatchers.IO) {
output.finish()
}
override fun close() {
output.close()
}
companion object {
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)))
}
}
}

View File

@@ -1,42 +0,0 @@
package org.koitharu.kotatsu.core.backup
class CompositeResult {
private var successCount: Int = 0
private val errors = ArrayList<Throwable?>()
val size: Int
get() = successCount + errors.size
val failures: List<Throwable>
get() = errors.filterNotNull()
val isEmpty: Boolean
get() = errors.isEmpty() && successCount == 0
val isAllSuccess: Boolean
get() = errors.none { it != null }
val isAllFailed: Boolean
get() = successCount == 0 && errors.isNotEmpty()
operator fun plusAssign(result: Result<*>) {
when {
result.isSuccess -> successCount++
result.isFailure -> errors.add(result.exceptionOrNull())
}
}
operator fun plusAssign(other: CompositeResult) {
this.successCount += other.successCount
this.errors += other.errors
}
operator fun plus(other: CompositeResult): CompositeResult {
val result = CompositeResult()
result.successCount = this.successCount + other.successCount
result.errors.addAll(this.errors)
result.errors.addAll(other.errors)
return result
}
}

View File

@@ -1,105 +0,0 @@
package org.koitharu.kotatsu.core.backup
import org.json.JSONObject
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
class JsonDeserializer(private val json: JSONObject) {
fun toFavouriteEntity() = FavouriteEntity(
mangaId = json.getLong("manga_id"),
categoryId = json.getLong("category_id"),
sortKey = json.getIntOrDefault("sort_key", 0),
createdAt = json.getLong("created_at"),
deletedAt = 0L,
)
fun toMangaEntity() = MangaEntity(
id = json.getLong("id"),
title = json.getString("title"),
altTitle = json.getStringOrNull("alt_title"),
url = json.getString("url"),
publicUrl = json.getStringOrNull("public_url").orEmpty(),
rating = json.getDouble("rating").toFloat(),
isNsfw = json.getBooleanOrDefault("nsfw", false),
coverUrl = json.getString("cover_url"),
largeCoverUrl = json.getStringOrNull("large_cover_url"),
state = json.getStringOrNull("state"),
author = json.getStringOrNull("author"),
source = json.getString("source"),
)
fun toTagEntity() = TagEntity(
id = json.getLong("id"),
title = json.getString("title"),
key = json.getString("key"),
source = json.getString("source"),
)
fun toHistoryEntity() = HistoryEntity(
mangaId = json.getLong("manga_id"),
createdAt = json.getLong("created_at"),
updatedAt = json.getLong("updated_at"),
chapterId = json.getLong("chapter_id"),
page = json.getInt("page"),
scroll = json.getDouble("scroll").toFloat(),
percent = json.getFloatOrDefault("percent", -1f),
chaptersCount = json.getIntOrDefault("chapters", -1),
deletedAt = 0L,
)
fun toFavouriteCategoryEntity() = FavouriteCategoryEntity(
categoryId = json.getInt("category_id"),
createdAt = json.getLong("created_at"),
sortKey = json.getInt("sort_key"),
title = json.getString("title"),
order = json.getStringOrNull("order") ?: SortOrder.NEWEST.name,
track = json.getBooleanOrDefault("track", true),
isVisibleInLibrary = json.getBooleanOrDefault("show_in_lib", true),
deletedAt = 0L,
)
fun toBookmarkEntity() = BookmarkEntity(
mangaId = json.getLong("manga_id"),
pageId = json.getLong("page_id"),
chapterId = json.getLong("chapter_id"),
page = json.getInt("page"),
scroll = json.getInt("scroll"),
imageUrl = json.getString("image_url"),
createdAt = json.getLong("created_at"),
percent = json.getDouble("percent").toFloat(),
)
fun toMangaSourceEntity() = MangaSourceEntity(
source = json.getString("source"),
isEnabled = json.getBoolean("enabled"),
sortKey = json.getInt("sort_key"),
addedIn = json.getIntOrDefault("added_in", 0),
lastUsedAt = json.getLongOrDefault("used_at", 0L),
isPinned = json.getBooleanOrDefault("pinned", false),
)
fun toMap(): Map<String, Any?> {
val map = mutableMapOf<String, Any?>()
val keys = json.keys()
while (keys.hasNext()) {
val key = keys.next()
val value = json.get(key)
map[key] = value
}
return map
}
}

View File

@@ -1,103 +0,0 @@
package org.koitharu.kotatsu.core.backup
import org.json.JSONObject
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
class JsonSerializer private constructor(private val json: JSONObject) {
constructor(e: FavouriteEntity) : this(
JSONObject().apply {
put("manga_id", e.mangaId)
put("category_id", e.categoryId)
put("sort_key", e.sortKey)
put("created_at", e.createdAt)
},
)
constructor(e: FavouriteCategoryEntity) : this(
JSONObject().apply {
put("category_id", e.categoryId)
put("created_at", e.createdAt)
put("sort_key", e.sortKey)
put("title", e.title)
put("order", e.order)
put("track", e.track)
put("show_in_lib", e.isVisibleInLibrary)
},
)
constructor(e: HistoryEntity) : this(
JSONObject().apply {
put("manga_id", e.mangaId)
put("created_at", e.createdAt)
put("updated_at", e.updatedAt)
put("chapter_id", e.chapterId)
put("page", e.page)
put("scroll", e.scroll)
put("percent", e.percent)
put("chapters", e.chaptersCount)
},
)
constructor(e: TagEntity) : this(
JSONObject().apply {
put("id", e.id)
put("title", e.title)
put("key", e.key)
put("source", e.source)
},
)
constructor(e: MangaEntity) : this(
JSONObject().apply {
put("id", e.id)
put("title", e.title)
put("alt_title", e.altTitle)
put("url", e.url)
put("public_url", e.publicUrl)
put("rating", e.rating)
put("nsfw", e.isNsfw)
put("cover_url", e.coverUrl)
put("large_cover_url", e.largeCoverUrl)
put("state", e.state)
put("author", e.author)
put("source", e.source)
},
)
constructor(e: BookmarkEntity) : this(
JSONObject().apply {
put("manga_id", e.mangaId)
put("page_id", e.pageId)
put("chapter_id", e.chapterId)
put("page", e.page)
put("scroll", e.scroll)
put("image_url", e.imageUrl)
put("created_at", e.createdAt)
put("percent", e.percent)
},
)
constructor(e: MangaSourceEntity) : this(
JSONObject().apply {
put("source", e.source)
put("enabled", e.isEnabled)
put("sort_key", e.sortKey)
put("added_in", e.addedIn)
put("used_at", e.lastUsedAt)
put("pinned", e.isPinned)
},
)
constructor(m: Map<String, *>) : this(
JSONObject(m),
)
fun toJson(): JSONObject = json
}

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
@@ -36,6 +38,11 @@ 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.Migration25To26
import org.koitharu.kotatsu.core.db.migrations.Migration26To27
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
@@ -63,14 +70,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 = 23
const val DATABASE_VERSION = 27
@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, LocalMangaIndexEntity::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,
)
@@ -103,6 +110,8 @@ abstract class MangaDatabase : RoomDatabase() {
abstract fun getStatsDao(): StatsDao
abstract fun getLocalMangaIndexDao(): LocalMangaIndexDao
abstract fun getChaptersDao(): ChaptersDao
}
fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
@@ -128,6 +137,11 @@ fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
Migration20To21(),
Migration21To22(),
Migration22To23(),
Migration23To24(),
Migration24To23(),
Migration24To25(),
Migration25To26(),
Migration26To27(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room

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