Compare commits

..

101 Commits

Author SHA1 Message Date
NGB-Was-Taken fd120c5081 Get manga info from tracker (#1271)
* Barebones setup (only AniList works)

* Show tracker selection dialog when entry has more than one tracker

* MangaUpdates implementation

* Add logging and toast on error.

* MyAnimeList implementation

* Kitsu implementation

* Fix MAL authors and artists

* Decode AL description

* Throw NotImplementedError instead of returning null

* Use logcat from LogcatExtensions

* Replace strings with MR strings

* Missed a string

* Delete unused Author class.

* Add Bangumi & Shikimori support for info edit (#2)

This adds the necessary API calls and DTOs to allow for editing an
entry's data to the data from a tracker, specifically adding support
for Bangumi and Shikimori.

* Exclude enhanced trackers from tracker select dialog

* MdList implementation

* Remember getTracks and trackerManager

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>

---------

Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com>
Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2024-12-08 15:25:26 -05:00
KenjieDec 34e9d9f146 Add support for .webp image extension (#1335) 2024-12-08 15:24:48 -05:00
Weblate (bot) b7f7187293 Translations update from Hosted Weblate (#1299)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Cauã Oliveira <caua.oli.santos@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Homura Akemi <amber_c001@protonmail.com>
Co-authored-by: Igor Coimbra Carvalheira <igorccarvalheira111@gmail.com>
Co-authored-by: Illia Stoianov <Walrus_Morj@protonmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: KenjieDec <kenjiedec@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Paulo Victor <paulovictorbarrosdecarvalho@gmail.com>
Co-authored-by: Ruben Lopes <lopes.ruben@ua.pt>
Co-authored-by: TheKingTermux <50316075+TheKingTermux@users.noreply.github.com>
Co-authored-by: cannnAvar <bartucanavar@proton.me>
Co-authored-by: jobobby04 <jobobby04@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
2024-12-08 15:23:50 -05:00
Jobobby04 4abadea4f9 Spotless 2024-12-08 15:22:57 -05:00
Jobobby04 1b3d76398b Minor cleanup 2024-12-08 15:18:51 -05:00
Weblate (bot) 688fdecaf8 Translations update from Hosted Weblate (#1531)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/uk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Illia Stoianov <Walrus_Morj@protonmail.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
(cherry picked from commit b4ad9ae0634330eb4b3a9479cd522de48819a886)
2024-12-08 14:37:37 -05:00
MajorTanya 0bedee1778 Always use software bitmap on certain devices (#1543)
* Include Coil's broken hardware bitmap device list

Declares all listed devices as unable to use hardware bitmaps.

Might fix #1541.

* Hide Hardware Bitmap Threshold setting if unusable

This hides the setting from the UI if the user's device in on Coil's
list of devices with problematic hardware bitmap implementations.

Also moved HARDWARE_BITMAP_UNSUPPORTED into the ImageUtil as a
property for more ergonomic access across the project.

* Add missing negation

* Update CHANGELOG.md

* Update CHANGELOG.md

* Needs to be and not or

Also fix typo in CHANGELOG.md

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 7f2cfb5eb224896d6d0ffa6960f0a98c7325e240)

# Conflicts:
#	CHANGELOG.md
#	core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
2024-12-08 14:23:57 -05:00
Weblate (bot) bb89f9f636 Translations update from Hosted Weblate (#1423)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sc/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/am/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/da/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ka/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sah/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sc/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Akhil Raj <89210430+akhi07rx@users.noreply.github.com>
Co-authored-by: AntsyLich <antsylich@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Horace Johnson <horacejohnson99@gmail.com>
Co-authored-by: Igor Coimbra Carvalheira <igorccarvalheira111@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: Leandro Cândido <123888466+marshfellow42@users.noreply.github.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
(cherry picked from commit a807722838d1f10141b29721957cbac5a95f147d)
2024-12-08 14:22:44 -05:00
MajorTanya f8011981eb Add a Honor system app to list of invalid browsers (#1520)
Closes #1348.

Specifically adds com.hihonor.android.internal.app to the list of
invalid browsers. It's very similar to the existing entry for Huawei,
so it stands to reason it is the same/similar problem as with Huawei's
internal app.

(cherry picked from commit 3bd8d3ecb7023d1b01930ab0f91482c23e89c946)
2024-12-08 14:22:34 -05:00
Mend Renovate 7e17e52e07 Update dependency org.jsoup:jsoup to v1.18.2 (#1515)
(cherry picked from commit 8ea95cb27fa3c263cc9905c63cd8493ffb831ef5)
2024-12-08 14:22:27 -05:00
Mend Renovate b65990ad29 Update dependency io.coil-kt.coil3:coil-bom to v3.0.4 (#1510)
(cherry picked from commit e280fd63b67355b60a6f303a7d02539785d02856)
2024-12-08 14:22:21 -05:00
Mend Renovate d9560d40de Update dependency gradle to v8.11.1 (#1475)
(cherry picked from commit addb4ae9ad5f9294c70bce8b5eebd806115158b2)
2024-12-08 14:22:14 -05:00
AntsyLich 036ab3351d Improve hardware bitmap threshold option
Also `spotlessApply`

(cherry picked from commit d6dfd24548eaa05a8c3e478068fe2e08f2ee4473)
2024-12-08 14:22:06 -05:00
Cuong-Tran 769293355f Fix app update error notification disappearing (#1476)
(cherry picked from commit 88aff2c77fbaed52ab101ce75c2cbe72f1747579)
2024-12-08 14:21:57 -05:00
AntsyLich 850d81600e Slightly tweak Preference.PreferenceItem.CustomPreference
(cherry picked from commit 81effea01c33d4b47f6802a3d5e31fa39609a6fb)
2024-12-08 14:21:47 -05:00
AntsyLich ce96b53f10 Fix loading screen not appearing when changing query in browser screen
Fixes #1438
Closes #1441

(cherry picked from commit 9aef08c333397caa4b897514cf76966592d3849c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
2024-12-08 14:20:29 -05:00
AntsyLich b98dfd65b5 Add option to lower the threshold for hardware bitmaps
Closes #1436
Closes #1486

(cherry picked from commit dcddac5daaff3ec89c8507c35dc13d345ffdb6d7)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt
2024-12-08 14:16:10 -05:00
AntsyLich 612e0a00bc Revert "Add option to always use SSIV for image decoding"
This reverts commit bb4d9fc81a043ac4f2d0105f19c09974ae2f7201.

(cherry picked from commit 1909126921ac78309f7f7c7c2aa85606611531b8)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt
2024-12-08 14:14:24 -05:00
AntsyLich d286cf3267 Switch to hardware bitmap in reader only if device can handle it
Closes #1460

(cherry picked from commit e6d96bd348ea5d18a005d6465222ad5f5123103e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt
2024-12-08 14:13:59 -05:00
Cuong-Tran 1a28c7fb35 Fix reader transition color scheme in auto background mode (#1487)
(cherry picked from commit 36d5ee0763be2b0bcc65f9d061961d86359fe6f6)
2024-12-08 14:10:36 -05:00
Mend Renovate 5909f90003 Update paging.version to v3.3.4 (#1481)
(cherry picked from commit 5a91d5c611faacacf5cf6fa135e93863c0332475)
2024-12-08 14:10:29 -05:00
Mend Renovate 48f7b701dc Update dependency androidx.viewpager:viewpager to v1.1.0-rc01 (#1480)
(cherry picked from commit e332590b1bbe3eaea76763db0761e9690ae684e2)
2024-12-08 14:10:23 -05:00
Mend Renovate b17530ccc3 Update dependency io.coil-kt.coil3:coil-bom to v3.0.3 (#1485)
(cherry picked from commit 39982c406351c93610dedda75ac5199d29b3d6a5)
2024-12-08 14:10:14 -05:00
Mend Renovate f844a48b67 Update dependency io.coil-kt.coil3:coil-bom to v3.0.2 (#1469)
(cherry picked from commit d1a970e3f3c9a2cfea2567a2e86245fc8a169c68)
2024-12-08 14:10:07 -05:00
Cuong-Tran 66929e097c Fix crash after removing last category while it's active in library (#1450)
(cherry picked from commit 9df21583dc1da6da4041709a6d059848c6c9bda0)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt
2024-12-08 14:10:00 -05:00
AntsyLich be30814d35 Update dependency androidx.work:work-runtime to v2.10.0
(cherry picked from commit 57e6e198b8101aa4ea60da89aea371f827b5f7e4)
2024-12-08 13:59:55 -05:00
Mend Renovate 5d56c1961d Update dependency com.android.tools:desugar_jdk_libs to v2.1.3 (#1453)
(cherry picked from commit 3a648e4fa50fa9c6cf8703b74062d67db237be1c)
2024-12-08 13:59:49 -05:00
Mend Renovate 4aa52a2576 Update dependency io.coil-kt.coil3:coil-bom to v3.0.1 (#1454)
(cherry picked from commit 6159bc36368910c024682ad5d0d2b298bc4fb17f)
2024-12-08 13:59:43 -05:00
Mend Renovate f7a1869066 Update dependency com.pinterest.ktlint:ktlint-cli to v1.4.1 (#1449)
(cherry picked from commit 3cfc2be104c2820eccbaa9d3a68b3df0ed37e39c)
2024-12-08 13:59:30 -05:00
Mend Renovate 2f1d76cbac Update dependency androidx.compose:compose-bom to v2024.10.01 (#1424)
(cherry picked from commit 9580a00aa674edd66c6a22ea127e6317f5d85498)
2024-12-08 13:59:25 -05:00
Mend Renovate 5c5e08b99b Update dependency androidx.core:core-ktx to v1.15.0 (#1417)
(cherry picked from commit cb2b0464d036496d7b029468a9a3efc2e95151d9)
2024-12-08 13:59:18 -05:00
Mend Renovate cc16d53ecc Update dependency com.android.tools.build:gradle to v8.7.2 (#1428)
(cherry picked from commit ef7992f9121828af9efa7a66ed1d2d731793d6b5)
2024-12-08 13:59:12 -05:00
Mend Renovate 28fa3855c2 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0 (#1444)
(cherry picked from commit a5349a881b650c15de57ba39e4e121a26918f913)
2024-12-08 13:59:00 -05:00
Mend Renovate 5a47a58e1e Update xml.serialization.version to v0.90.3 (#1446)
(cherry picked from commit 2ca2cec02b818d85c73885fadc23f8480e62a0af)
2024-12-08 13:58:53 -05:00
Jobobby04 c86714ef59 Fix some deprecations 2024-12-08 13:58:44 -05:00
AntsyLich 75fe57b851 Cleanup some code
(cherry picked from commit 2f4bb7cadb0297492cfb21393e75ca276e0539d7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
2024-12-08 13:58:32 -05:00
Jobobby04 b9fffc45cc Fix idle status set 2024-12-08 13:47:54 -05:00
Jobobby04 de6cd169d0 Return newpage joineditems check 2024-12-08 13:37:29 -05:00
Jobobby04 95e8a02e33 Forgot import 2024-12-08 13:36:19 -05:00
Jobobby04 c720f0ac5c Increase new updates count when updates found 2024-12-08 13:35:58 -05:00
Jobobby04 76af3b59f0 Improve favorites sync statuses 2024-12-08 13:35:36 -05:00
Jobobby04 3f8cce8a32 Update tag lists 2024-12-08 13:31:38 -05:00
Jobobby04 26cfb4811f Fix a possible crash with auto-zoom 2024-11-07 22:21:39 -05:00
Jobobby04 e5a6d1b456 Fix a crash with migration list screen 2024-11-07 22:21:18 -05:00
Jobobby04 f0b621dfe5 Fix multiple issues with the E-Hentai updater 2024-11-07 22:21:02 -05:00
NGB-Was-Taken d88f570f65 Do not sync automatically when not connected to a network. (#1312) 2024-11-03 23:42:39 -05:00
Jobobby04 b430e31da4 Fix app onStart sync 2024-11-03 22:44:31 -05:00
Jobobby04 271f2d37bb Fix crashes from Exh Updater 2024-11-03 22:30:47 -05:00
AntsyLich c2e36b4c5c Add option to always use SSIV for image decoding
(cherry picked from commit bb4d9fc81a043ac4f2d0105f19c09974ae2f7201)

# Conflicts:
#	CHANGELOG.md
2024-11-03 22:01:55 -05:00
Weblate (bot) cb25deb5ac Translations update from Hosted Weblate (#1111)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ro/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sc/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sq/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/th/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Akhil Raj <89210430+akhi07rx@users.noreply.github.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: AntsyLich <antsylich@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: Chiro-kun <chirokun863@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eji-san <ejierubani@gmail.com>
Co-authored-by: Eren Eroğlu <ereneroglum@yahoo.com>
Co-authored-by: Fadhil Muhammad <alpanumerik1@gmail.com>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: HDYOU <308485965@qq.com>
Co-authored-by: Homura Akemi <amber_c001@protonmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: Kryptox <info.kryptox@gmail.com>
Co-authored-by: Leandro Cândido <123888466+marshfellow42@users.noreply.github.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Marco Espinoza <maviesco@gmail.com>
Co-authored-by: Milihraim <kirill06678@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: N. Hao <nguyenviethao2002@gmail.com>
Co-authored-by: NGB-Was-Taken <myalternate34@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Noah Kenzie Rodriguez-Beus <noahbeus@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: SBS1313 <simonsaade005@gmail.com>
Co-authored-by: Saft Octavian <saftoctavian@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Valerio Marini <marinivalerio97@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Co-authored-by: altinat <al@altqx.com>
Co-authored-by: altinat <altinat@duck.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gekka <1778962971@qq.com>
Co-authored-by: orkan gökçe alaz aşina <examplehuman@outlook.com>
Co-authored-by: phlostically <phlostically@mailinator.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: 赤星悠太 <yuta1219aka@gmail.com>
(cherry picked from commit 79e711efc20855f42cb544697edc124963506414)

# Conflicts:
#	i18n/src/commonMain/moko-resources/nl/strings.xml
#	i18n/src/commonMain/moko-resources/vi/strings.xml
#	i18n/src/commonMain/moko-resources/zh-rTW/strings.xml
2024-11-03 22:01:14 -05:00
AntsyLich a6c6cf77bb Address some build warnings and cleanup (#1412)
(cherry picked from commit a1c60897916f418726107fec80ad79b2a4b8d500)
2024-11-03 21:59:24 -05:00
AntsyLich e3dae57e0b Fix long strip images not loading in some old devices
Fixes #1398

(cherry picked from commit 06efc3b25c5af51f42448af27a269ee459d9093d)

# Conflicts:
#	CHANGELOG.md
2024-11-03 21:59:17 -05:00
AntsyLich 226321f334 Fix a rare crash when invoking "Mark previous as read" action
Closes #1421

(cherry picked from commit f508d10ad13560d7316df8642bc93fe66c05b9a8)

# Conflicts:
#	CHANGELOG.md
2024-11-03 21:58:50 -05:00
AntsyLich 2187731d70 Auto format extension repo URLs
Closes #1392
Closes #1393

(cherry picked from commit 22d8aad598bea8f00f2831779e45a6645392ca0f)

# Conflicts:
#	CHANGELOG.md
2024-11-03 21:58:28 -05:00
AntsyLich fd32f2e879 Bump default user agent
(cherry picked from commit 76dcf903403d565056f44c66d965c1ea8affffc3)

# Conflicts:
#	CHANGELOG.md
2024-11-03 21:58:07 -05:00
Mend Renovate 5a094850d1 Update dependency io.coil-kt.coil3:coil-bom to v3.0.0-rc02 (#1401)
(cherry picked from commit f33a6d25209fa9a1291f3dae222fc0ff8d95dba9)
2024-11-03 21:57:47 -05:00
Mend Renovate e74053e989 Update dependency androidx.constraintlayout:constraintlayout to v2.2.0 (#1416)
(cherry picked from commit 2914d166fe0ad5d6bb126fd5fe89d8ca3074787b)
2024-11-03 21:57:39 -05:00
Mend Renovate 798db44908 Update lifecycle.version to v2.8.7 (#1415)
(cherry picked from commit 328ec8c752f276a6e75f68102a257880e4b18753)
2024-11-03 21:57:32 -05:00
MajorTanya 7715b5bdd0 Some improvements to Bangumi tracker search (#1396)
In short:
- fetch & show actual summary
- fallback to "name" if "name_cn" is empty
- request larger responseGroup to get & display the summary & rating
- add type filter query param to make Bangumi filter, not us

Previously, we only displayed the "name" in the summary area and used
"name_cn" as the entry name. However, "name_cn" (Chinese name) can be
an empty string at times, resulting in an awkward looking search
result list where some, many, or even all the results have no title
displayed and only show the "name" (Japanese name) in the summary
area. This has been solved by using "name" as a fallback value should
"name_cn" be empty.

If a Chinese name is available, the original name is prepended to the
summary with the addition "作品原名:" (meaning "original series title").

By using the "responseGroup=large" query parameter, we can request
the required data we need to display the actual summary for an entry
and the entry's average rating.
The "name" is prepended to the summary contents, if any exist, so it
is still accessible for series identification if a "name_cn" exists
too and was used for the result title.

Adding the "type=1" filter query parameter means Bangumi will only
return entries of type 1 ("book") instead of all types and Mihon
needing to filter, resulting in potentially missed entry matches.

(cherry picked from commit 78f9a84b14e0ece988f80d61011f63c0f7e92a67)

# Conflicts:
#	CHANGELOG.md
2024-11-03 21:57:28 -05:00
Mend Renovate 084e11f21d Update dependency androidx.annotation:annotation to v1.9.1 (#1413)
(cherry picked from commit eedece5adfbb95c882d4d59a5020f7e27c634c13)
2024-11-03 21:57:05 -05:00
Mend Renovate 01792c0618 Update dependency androidx.viewpager:viewpager to v1.1.0-beta01 (#1414)
(cherry picked from commit 9d6ddb5d91bd062876bdb108ca3ce278359551e5)
2024-11-03 21:56:57 -05:00
AntsyLich 0b93ceaa8f Switch to spotless 7.0.0 Beta 4
(cherry picked from commit b8b053b1d720a6de5c3d4d8a683eed7bc8cdcc5f)
2024-11-03 21:56:44 -05:00
MajorTanya bfdbe18509 Fix sporadically recurring spotless CI failure (#1407)
Somehow this specific issue keeps getting flagged by unrelated PRs'
CI runs (but only sometimes? Somehow? Other times the CI run would
succeed with no spotless issues.)

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit ed9e13a365ba1b55cec21c26b93b1c62d29485c8)
2024-11-03 21:56:35 -05:00
AntsyLich e3245d0610 Here lies "currentTab was used multiple times"
Fixes #282

(cherry picked from commit 371c1432e218f6dcf129f05405dceb2cd351c647)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt
2024-11-03 21:56:22 -05:00
Jobobby04 c2df6ee54a Fix InterceptActivity crash 2024-11-03 21:03:22 -05:00
Jobobby04 ffc1e2d97b SpotlessApply 2024-10-27 23:08:42 -04:00
Jobobby04 d0c8b0c98a Fix tests 2024-10-27 22:56:04 -04:00
Jobobby04 f206ab8b32 Release 1.11.0 2024-10-27 22:32:00 -04:00
Jobobby04 a443629234 Fix reflection 2024-10-27 14:07:28 -04:00
Jobobby04 3de4711e03 Try this Shizuku fix 2024-10-27 13:34:33 -04:00
Weblate (bot) 106f63a657 Translations update from Hosted Weblate (#1289)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eji-san <ejierubani@gmail.com>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: Noah Kenzie Rodriguez-Beus <noahbeus@protonmail.com>
Co-authored-by: Sergeev Vladimir Dmitrievich <sekhmych@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
2024-10-26 23:44:53 -04:00
Luqman 3c09343f7b allow chapter 0 dupe auto mark as read (#1291) 2024-10-26 23:31:44 -04:00
Jobobby04 86e1406565 Spotless 2024-10-26 23:30:07 -04:00
jobobby04 b48556aa9f Fix for ExHentai 2024-10-26 23:06:30 -04:00
Cuong-Tran f3e905513f Fix app crash when removing tracked entry from tracker (#1380)
(cherry picked from commit 6de06419f8afd842f7037e3ecb51200037af3a85)
2024-10-26 22:25:23 -04:00
Roshan Varughese 633a1892b3 Allow completely disabling "Update tracker" snackbar on mark as read (#1374)
Also fixes #1369

(cherry picked from commit fc2f339ea1cdc43c0041b2fed497dcfda853b85e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
2024-10-26 22:25:11 -04:00
Cuong-Tran 74cf08b47b Add libs.material to presentation-widget (#1373)
Fixes some build issues

(cherry picked from commit 264030d6ecbc7492d884eb328b74399cd722dcb0)
2024-10-26 22:16:09 -04:00
Jobobby04 cc7ce80abf Ignore Shizuku min sdk since desurging is enabled 2024-10-26 22:15:35 -04:00
AntsyLich e06941f82d Update dependency com.pinterest.ktlint:ktlint-cli to v1.4.0
Co-authored-by: Mend Renovate <bot@renovateapp.com>
(cherry picked from commit 140083ee39df7d458e5ff9abc6d0ee9831d99387)
2024-10-26 21:59:54 -04:00
AntsyLich a8a290d03d Cleanup Slider usage
(cherry picked from commit df9fff60da3a38acd8fcd540b5fdd275be93f2d5)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt
#	app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt
2024-10-26 21:59:33 -04:00
Mend Renovate b49ca3ce4c Update dependency me.zhanghai.android.libarchive:library to v1.1.4 (#1378)
(cherry picked from commit aae0e3459ce13398a64b5cd9995f4a40a0120822)
2024-10-26 21:53:38 -04:00
Cuong-Tran c51c364cdd Avoid blocking call to load categories in settings (#1364)
(cherry picked from commit f7752a98b2452a69f22a469d0bcbf761fb1c6569)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt
2024-10-26 21:53:27 -04:00
abdurisaq 366415d323 Fix settings SliderItem steps count (#1356)
(cherry picked from commit 2ba7ed32802ffca1946d567b8afe49bfd3f4326e)
2024-10-26 21:52:33 -04:00
Roshan Varughese 14f6fd7908 Rework Auto Track on Mark as Read (#1365)
(cherry picked from commit c153ac01f545ce9259c58aa5d5f7223d2f8f628b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
2024-10-26 21:52:24 -04:00
Mend Renovate 15f1ee2205 Update dependency com.google.firebase:firebase-bom to v33.5.1 (#1362)
(cherry picked from commit 78d2cc75d5dc04fe5cddcfaeb0a4502d48392f2d)
2024-10-26 21:51:14 -04:00
AntsyLich 651579b243 Update shizuku.version to v13.1.0
(cherry picked from commit c550a81598c98ef9a22dac8f6a408f5c15235fde)
2024-10-26 21:50:55 -04:00
Mend Renovate 8f596069fa Update dependency com.google.firebase:firebase-bom to v33.5.0 (#1352)
(cherry picked from commit 0be36a10c36d3b8c5802ff515302ed29cc8fa013)
2024-10-26 21:50:41 -04:00
Mend Renovate a28d526102 Update dependency org.junit.jupiter:junit-jupiter to v5.11.3 (#1351)
(cherry picked from commit e16c3953c709a6c35c4655f916119fdf665baa62)
2024-10-26 21:50:34 -04:00
Jobobby04 bbaa74d99c Fix build 2024-10-20 01:51:54 -04:00
AntsyLich 310b1ad69b Pass uncaught exception to default handler in GlobalExceptionHandler
Fixes #1347

(cherry picked from commit f3a2f566c8a09ab862758ae69b43da2a2cd8f1db)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/crash/GlobalExceptionHandler.kt
2024-10-20 01:04:23 -04:00
AntsyLich 7f37989c4e Rework Firebase setup
Fixes #1332
Closes #1339

(cherry picked from commit 15e3f28aa36bec3c31f212c572ab57ce960cc862)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
2024-10-20 01:03:36 -04:00
AntsyLich 185920b984 Address deprecation, suggestion and spotless
(cherry picked from commit 3bf70b230fc2c3eda58c4d46dec3fb75668e4f69)
2024-10-20 01:02:06 -04:00
AntsyLich 4639077756 Revert "Tweak Preference.collectAsState"
This reverts commit 3bddb5538528c19388e364d21e6a6c16487af759.

Fixes #1341

(cherry picked from commit eb3bea8150ce9bf2320d15c879cbebaa6d51a4c6)
2024-10-20 01:01:58 -04:00
Mend Renovate 0bf1519c25 Update dependency androidx.compose:compose-bom to v2024.10.00 (#1338)
(cherry picked from commit 5612ae0149e9231c9691ee782da8159489a0d057)
2024-10-20 01:01:48 -04:00
Mend Renovate 45a36cef32 Update xml.serialization.version to v0.90.2 (#1331)
* Update xml.serialization.version to v0.90.2

* Fix build

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit dbf6ad2ca7e0525f597010709e87d094d10e4f8d)

# Conflicts:
#	source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt
2024-10-20 01:01:41 -04:00
AntsyLich dece1bc0cb Change "Invalidate downloads index" to "Reindex downloads"
(cherry picked from commit d2afbfe4ede283076aae40633c79c3f90b4390e7)
2024-10-20 01:01:09 -04:00
Mend Renovate eaffd3f2dc Update dependency androidx.annotation:annotation to v1.9.0 (#1336)
(cherry picked from commit 337806d9e17e92a9134d59324e9857d05abc4db3)
2024-10-20 01:00:58 -04:00
Mend Renovate aabe409ee5 Update dependency androidx.glance:glance-appwidget to v1.1.1 (#1335)
(cherry picked from commit 443f6e0ae53dadce1f66818fac0cd1eeaa5fec27)
2024-10-20 01:00:50 -04:00
Mend Renovate e626cdd030 Update dependency androidx.benchmark:benchmark-macro-junit4 to v1.3.3 (#1334)
(cherry picked from commit 572ee2f02a980a60a1120e7c0c88060fb1a7b3d2)
2024-10-20 01:00:43 -04:00
Mend Renovate b161c333ec Update dependency androidx.activity:activity-compose to v1.9.3 (#1333)
(cherry picked from commit ba1343bed8c00d5ed976111c710c9b5648676a59)
2024-10-20 01:00:36 -04:00
FlaminSarge e587bb7f44 [skip ci] Update i18n readme (#1328)
(cherry picked from commit 9f3d5d13d4fedcca53ebb779a2cfca1e286c79da)
2024-10-20 01:00:26 -04:00
242 changed files with 6828 additions and 1774 deletions
+4 -4
View File
@@ -53,7 +53,7 @@ body:
label: TachiyomiSY version
description: You can find your TachiyomiSY version in **More → About**.
placeholder: |
Example: "1.10.5"
Example: "1.11.0"
validations:
required: true
@@ -63,7 +63,7 @@ body:
label: Android version
description: You can find this somewhere in your Android settings.
placeholder: |
Example: "Android 11"
Example: "Android 14"
validations:
required: true
@@ -73,7 +73,7 @@ body:
label: Device
description: List your device and model.
placeholder: |
Example: "Google Pixel 5"
Example: "Google Pixel 8"
validations:
required: true
@@ -96,7 +96,7 @@ body:
required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https:/mihon.app/docs/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[1.10.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
+1 -1
View File
@@ -31,7 +31,7 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: I have updated the app to version **[1.10.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+22 -25
View File
@@ -1,7 +1,8 @@
@file:Suppress("ChromeOsAbiSupport")
import mihon.buildlogic.getBuildTime
import mihon.buildlogic.getCommitCount
import mihon.buildlogic.getGitSha
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("mihon.android.application")
@@ -30,8 +31,8 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 69
versionName = "1.10.5"
versionCode = 71
versionName = "1.11.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -141,6 +142,24 @@ android {
}
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll(
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}
}
dependencies {
implementation(projects.i18n)
// SY -->
@@ -307,28 +326,6 @@ androidComponents {
}
}
tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> {
compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}
}
buildscript {
dependencies {
classpath(kotlinx.gradle)
@@ -1,9 +0,0 @@
package mihon.core.firebase
import android.content.Context
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import kotlinx.coroutines.CoroutineScope
object Firebase {
fun setup(context: Context, preference: PrivacyPreferences, scope: CoroutineScope) = Unit
}
@@ -0,0 +1,11 @@
package mihon.core.firebase
import android.content.Context
object FirebaseConfig {
fun init(context: Context) = Unit
fun setAnalyticsEnabled(enabled: Boolean) = Unit
fun setCrashlyticsEnabled(enabled: Boolean) = Unit
}
+3
View File
@@ -415,4 +415,7 @@
</activity>
</application>
<uses-sdk tools:overrideLibrary="rikka.shizuku.api"
tools:ignore="ManifestOrder" />
</manifest>
@@ -1,5 +1,6 @@
package eu.kanade.core.util
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -45,21 +46,6 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
}
}
/**
* Returns a list containing only elements matching the given [predicate].
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) }
val destination = ArrayList<T>()
fastForEach { if (predicate(it)) destination.add(it) }
return destination
}
/**
* Returns a list containing all elements not matching the given [predicate].
*
@@ -70,27 +56,7 @@ inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
@OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) }
val destination = ArrayList<T>()
fastForEach { if (!predicate(it)) destination.add(it) }
return destination
}
/**
* Returns a list containing only the non-null results of applying the
* given [transform] function to each element in the original collection.
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
contract { callsInPlace(transform) }
val destination = ArrayList<R>()
fastForEach { element ->
transform(element)?.let(destination::add)
}
return destination
return fastFilter { !predicate(it) }
}
/**
@@ -131,26 +97,3 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
fastForEach { if (predicate(it)) --count }
return count
}
/**
* Returns a list containing only elements from the given collection
* having distinct keys returned by the given [selector] function.
*
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
* The elements in the resulting list are in the same order as they were in the source collection.
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
contract { callsInPlace(selector) }
val set = HashSet<K>()
val list = ArrayList<T>()
fastForEach {
val key = selector(it)
if (set.add(key)) list.add(it)
}
return list
}
@@ -2,6 +2,7 @@ package eu.kanade.domain.base
import android.content.Context
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.util.system.GLUtil
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.i18n.MR
@@ -30,4 +31,6 @@ class BasePreferences(
}
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
}
@@ -0,0 +1,10 @@
package eu.kanade.domain.track.model
import dev.icerock.moko.resources.StringResource
import tachiyomi.i18n.MR
enum class AutoTrackState(val titleRes: StringResource) {
ALWAYS(MR.strings.lock_always),
ASK(MR.strings.default_category_summary),
NEVER(MR.strings.lock_never),
}
@@ -1,9 +1,11 @@
package eu.kanade.domain.track.service
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.anilist.Anilist
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum
class TrackPreferences(
private val preferenceStore: PreferenceStore,
@@ -35,4 +37,9 @@ class TrackPreferences(
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
"pref_auto_update_manga_on_mark_read",
AutoTrackState.ALWAYS,
)
}
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
@@ -14,7 +15,6 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -33,20 +33,13 @@ import tachiyomi.presentation.core.i18n.stringResource
fun TabbedScreen(
titleRes: StringResource,
tabs: ImmutableList<TabContent>,
startIndex: Int? = null,
state: PagerState = rememberPagerState { tabs.size },
searchQuery: String? = null,
onChangeSearchQuery: (String?) -> Unit = {},
) {
val scope = rememberCoroutineScope()
val state = rememberPagerState { tabs.size }
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(startIndex) {
if (startIndex != null) {
state.scrollToPage(startIndex)
}
}
Scaffold(
topBar = {
val tab = tabs[state.currentPage]
@@ -21,9 +21,7 @@ internal fun LibraryTabs(
getNumberOfMangaForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit,
) {
// SY -->
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
// SY <--
Column(
modifier = Modifier.zIndex(1f),
) {
@@ -15,7 +15,6 @@ import androidx.compose.ui.window.DialogProperties
import exh.favorites.FavoritesSyncStatus
import kotlinx.coroutines.delay
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import kotlin.time.Duration.Companion.seconds
@@ -23,7 +22,6 @@ import kotlin.time.Duration.Companion.seconds
data class SyncFavoritesProgressProperties(
val title: String,
val text: String,
val canDismiss: Boolean,
val positiveButtonText: String? = null,
val positiveButton: (() -> Unit)? = null,
val negativeButtonText: String? = null,
@@ -34,18 +32,23 @@ data class SyncFavoritesProgressProperties(
fun SyncFavoritesProgressDialog(
status: FavoritesSyncStatus,
setStatusIdle: () -> Unit,
openManga: (Manga) -> Unit,
openManga: (Long) -> Unit,
) {
val context = LocalContext.current
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
when (status) {
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message),
canDismiss = false,
text = context.stringResource(
SYMR.strings.favorites_sync_bad_library_state,
context.stringResource(
SYMR.strings.favorites_sync_gallery_in_multiple_categories, status.mangaTitle,
status.categories.joinToString(),
),
),
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
positiveButton = {
openManga(status.manga)
openManga(status.mangaId)
setStatusIdle()
},
negativeButtonText = context.stringResource(MR.strings.action_ok),
@@ -53,31 +56,122 @@ fun SyncFavoritesProgressDialog(
)
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message),
canDismiss = false,
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message),
canDismiss = false,
text = context.stringResource(
SYMR.strings.favorites_sync_done_errors_message,
status.messages.joinToString(separator = "\n") {
when (it) {
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, it.url,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, it.title, it.gid)
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
}
},
),
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Idle -> value = null
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> {
is FavoritesSyncStatus.Initializing -> {
value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = status.message,
canDismiss = false,
text = context.stringResource(SYMR.strings.favorites_sync_initializing),
)
if (status is FavoritesSyncStatus.Processing && status.title != null) {
}
is FavoritesSyncStatus.SyncError -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(
SYMR.strings.favorites_sync_error_string,
when (status) {
FavoritesSyncStatus.SyncError.NotLoggedInSyncError -> context.stringResource(SYMR.strings.please_login)
FavoritesSyncStatus.SyncError.FailedToFetchFavorites ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_featch)
is FavoritesSyncStatus.SyncError.UnknownSyncError ->
context.stringResource(SYMR.strings.favorites_sync_unknown_error, status.message)
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_error, status.title, status.reason,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, status.title, status.url,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, status.title, status.gid)
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
},
),
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Processing -> {
val properties = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = when (status) {
FavoritesSyncStatus.Processing.VerifyingLibrary ->
context.stringResource(SYMR.strings.favorites_sync_verifying_library)
FavoritesSyncStatus.Processing.DownloadingFavorites ->
context.stringResource(SYMR.strings.favorites_sync_downloading)
FavoritesSyncStatus.Processing.CalculatingRemoteChanges ->
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes)
FavoritesSyncStatus.Processing.CalculatingLocalChanges ->
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes)
FavoritesSyncStatus.Processing.SyncingCategoryNames ->
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names)
is FavoritesSyncStatus.Processing.RemovingRemoteGalleries ->
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, status.galleryCount)
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
if (status.isThrottling) {
context.stringResource(
SYMR.strings.favorites_sync_processing_throttle,
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total),
)
} else {
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
}
is FavoritesSyncStatus.Processing.RemovingGalleryFromLocal ->
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, status.index, status.total)
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
if (status.isThrottling) {
context.stringResource(
SYMR.strings.favorites_sync_processing_throttle,
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total),
)
} else {
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
}
FavoritesSyncStatus.Processing.CleaningUp ->
context.stringResource(SYMR.strings.favorites_sync_cleaning_up)
},
)
value = properties
if (
status is FavoritesSyncStatus.Processing.AddingGalleryToRemote ||
status is FavoritesSyncStatus.Processing.AddingGalleryToLocal
) {
delay(5.seconds)
value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = status.delayedMessage ?: status.message,
canDismiss = false,
value = properties.copy(
text = when (status) {
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
properties.text + "\n\n" + status.title
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
properties.text + "\n\n" + status.title
else -> properties.text
},
)
}
}
@@ -112,8 +206,8 @@ fun SyncFavoritesProgressDialog(
}
},
properties = DialogProperties(
dismissOnClickOutside = dialog.canDismiss,
dismissOnBackPress = dialog.canDismiss,
dismissOnClickOutside = false,
dismissOnBackPress = false,
),
)
}
@@ -165,12 +165,12 @@ sealed class Preference {
data class CustomPreference(
override val title: String,
val content: @Composable (PreferenceItem<String>) -> Unit,
) : PreferenceItem<String>() {
val content: @Composable () -> Unit,
) : PreferenceItem<Unit>() {
override val enabled: Boolean = true
override val subtitle: String? = null
override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
override val onValueChanged: suspend (newValue: Unit) -> Boolean = { true }
}
}
@@ -167,7 +167,7 @@ internal fun PreferenceItem(
InfoWidget(text = item.title)
}
is Preference.PreferenceItem.CustomPreference -> {
item.content(item)
item.content()
}
}
}
@@ -59,6 +59,7 @@ import eu.kanade.tachiyomi.source.AndroidSourceManager
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.isDevFlavor
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
@@ -83,6 +84,7 @@ import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -369,6 +371,26 @@ object SettingsAdvancedScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
pref = basePreferences.hardwareBitmapThreshold(),
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
subtitleProvider = { value, options ->
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
},
enabled = !ImageUtil.HARDWARE_BITMAP_UNSUPPORTED &&
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.mapIndexed { index, option ->
val display = if (index == 0) {
stringResource(MR.strings.pref_hardware_bitmap_threshold_default, option)
} else {
option.toString()
}
option to display
}
.toMap()
.toImmutableMap(),
),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(),
@@ -537,7 +537,7 @@ object SettingsDataScreen : SearchableSettings {
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
onClick = {
if (!SyncDataJob.isRunning(context)) {
SyncDataJob.startNow(context)
SyncDataJob.startNow(context, manual = true)
} else {
context.toast(SYMR.strings.sync_in_progress)
}
@@ -15,7 +15,6 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
@@ -35,7 +34,7 @@ object SettingsDownloadScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() }
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
return listOf(
@@ -25,7 +25,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags
@@ -57,9 +56,7 @@ object SettingsLibraryScreen : SearchableSettings {
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val allCategories by getCategories.subscribe().collectAsState(
initial = runBlocking { getCategories.await() },
)
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
// SY -->
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
// SY <--
@@ -139,7 +139,7 @@ object SettingsMangadexScreen : SearchableSettings {
title = mdex.name + " Login",
content = {
BasePreferenceWidget(
title = it.title,
title = mdex.name + " Login",
widget = {
Icon(
imageVector = Icons.Outlined.PeopleAlt,
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.data.track.EnhancedTracker
@@ -53,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.source.service.SourceManager
@@ -85,6 +87,7 @@ object SettingsTrackingScreen : SearchableSettings {
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() }
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run {
@@ -125,6 +128,13 @@ object SettingsTrackingScreen : SearchableSettings {
pref = trackPreferences.autoUpdateTrack(),
title = stringResource(MR.strings.pref_auto_update_manga_sync),
),
Preference.PreferenceItem.ListPreference(
pref = trackPreferences.autoUpdateTrackOnMarkRead(),
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
entries = AutoTrackState.entries
.associateWith { stringResource(it.titleRes) }
.toPersistentMap(),
),
Preference.PreferenceGroup(
title = stringResource(MR.strings.services),
preferenceItems = persistentListOf(
@@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import kotlinx.serialization.Serializable
import nl.adaptivity.xmlutil.AndroidXmlReader
import nl.adaptivity.xmlutil.core.AndroidXmlReader
import nl.adaptivity.xmlutil.serialization.XML
import nl.adaptivity.xmlutil.serialization.XmlSerialName
import nl.adaptivity.xmlutil.serialization.XmlValue
@@ -84,7 +84,7 @@ fun ReaderAppBars(
enabledPrevious: Boolean,
currentPage: Int,
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
onPageIndexChange: (Int) -> Unit,
readingMode: ReadingMode,
onClickReadingMode: () -> Unit,
@@ -154,7 +154,7 @@ fun ReaderAppBars(
enabledPrevious = enabledPrevious,
currentPage = currentPage,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
onPageIndexChange = onPageIndexChange,
isVerticalSlider = true,
currentPageText = currentPageText,
)
@@ -182,7 +182,7 @@ fun ReaderAppBars(
enabledPrevious = enabledPrevious,
currentPage = currentPage,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
onPageIndexChange = onPageIndexChange,
isVerticalSlider = true,
currentPageText = currentPageText,
)
@@ -285,7 +285,7 @@ fun ReaderAppBars(
enabledPrevious = enabledPrevious,
currentPage = currentPage,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
onPageIndexChange = onPageIndexChange,
isVerticalSlider = false,
currentPageText = currentPageText,
)
@@ -18,7 +18,6 @@ import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
@@ -45,8 +44,8 @@ import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.util.isTabletUi
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Slider
import tachiyomi.presentation.core.i18n.stringResource
import kotlin.math.roundToInt
@Composable
fun ChapterNavigator(
@@ -61,7 +60,7 @@ fun ChapterNavigator(
currentPageText: String,
// SY <--
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
onPageIndexChange: (Int) -> Unit,
) {
// SY -->
if (isVerticalSlider) {
@@ -73,7 +72,7 @@ fun ChapterNavigator(
currentPage = currentPage,
currentPageText = currentPageText,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
onPageIndexChange = onPageIndexChange,
)
return
}
@@ -138,14 +137,11 @@ fun ChapterNavigator(
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp),
value = currentPage.toFloat(),
valueRange = 1f..totalPages.toFloat(),
steps = totalPages - 2,
onValueChange = {
val new = it.roundToInt() - 1
if (new != currentPage) {
onSliderValueChange(new)
}
value = currentPage,
valueRange = 1..totalPages,
onValueChange = f@{
if (it == currentPage) return@f
onPageIndexChange(it - 1)
},
interactionSource = interactionSource,
)
@@ -184,7 +180,7 @@ fun ChapterNavigatorVert(
currentPageText: String,
// SY <--
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
onPageIndexChange: (Int) -> Unit,
) {
val isTabletUi = isTabletUi()
val verticalPadding = if (isTabletUi) 24.dp else 8.dp
@@ -259,11 +255,11 @@ fun ChapterNavigatorVert(
}
}
.weight(1f),
value = currentPage.toFloat(),
valueRange = 1f..totalPages.toFloat(),
steps = totalPages,
onValueChange = {
onSliderValueChange(it.roundToInt() - 1)
value = currentPage,
valueRange = 1..totalPages,
onValueChange = f@{
if (it == currentPage) return@f
onPageIndexChange(it - 1)
},
interactionSource = interactionSource,
)
@@ -301,7 +297,7 @@ private fun ChapterNavigatorPreview() {
enabledPrevious = true,
currentPage = currentPage,
totalPages = 10,
onSliderValueChange = { currentPage = it },
onPageIndexChange = { currentPage = (it + 1) },
// SY -->
currentPageText = "1",
isVerticalSlider = false,
+31 -13
View File
@@ -15,6 +15,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.work.Configuration
import androidx.work.WorkManager
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
@@ -56,6 +58,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.cancelNotification
@@ -71,13 +74,14 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.LogPriority
import logcat.LogcatLogger
import mihon.core.firebase.Firebase
import mihon.core.firebase.FirebaseConfig
import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations
import org.conscrypt.Conscrypt
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR
@@ -100,6 +104,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
@SuppressLint("LaunchActivityFromNotification")
override fun onCreate() {
super<Application>.onCreate()
FirebaseConfig.init(applicationContext)
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
@@ -127,12 +132,12 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
setupExhLogging() // EXH logging
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
Firebase.setup(applicationContext, privacyPreferences, ProcessLifecycleOwner.get().lifecycleScope)
setupNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
val scope = ProcessLifecycleOwner.get().lifecycleScope
// Show notification to disable Incognito Mode when it's enabled
basePreferences.incognitoMode().changes()
.onEach { enabled ->
@@ -160,19 +165,38 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
cancelNotification(Notifications.ID_INCOGNITO_MODE)
}
}
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
.launchIn(scope)
privacyPreferences.analytics()
.changes()
.onEach(FirebaseConfig::setAnalyticsEnabled)
.launchIn(scope)
privacyPreferences.crashlytics()
.changes()
.onEach(FirebaseConfig::setCrashlyticsEnabled)
.launchIn(scope)
basePreferences.hardwareBitmapThreshold().let { preference ->
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
}
basePreferences.hardwareBitmapThreshold().changes()
.onEach { ImageUtil.hardwareBitmapThreshold = it }
.launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
// Updates widget update
with(WidgetManager(Injekt.get(), Injekt.get())) {
init(ProcessLifecycleOwner.get().lifecycleScope)
}
WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
/*if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) {
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
}*/
if (!WorkManager.isInitialized()) {
WorkManager.initialize(this, Configuration.Builder().build())
}
val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
@@ -274,12 +298,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
}
val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
SyncDataJob.startNow(this@App)
}
}
// EXH
@@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.crash
import android.content.Context
import android.content.Intent
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
@@ -13,7 +11,6 @@ import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
import kotlin.system.exitProcess
class GlobalExceptionHandler private constructor(
private val applicationContext: Context,
@@ -33,14 +30,9 @@ class GlobalExceptionHandler private constructor(
}
override fun uncaughtException(thread: Thread, exception: Throwable) {
try {
logcat(priority = LogPriority.ERROR, throwable = exception)
Firebase.crashlytics.recordException(exception)
launchActivity(applicationContext, activityToBeLaunched, exception)
exitProcess(0)
} catch (_: Exception) {
defaultHandler.uncaughtException(thread, exception)
}
logcat(priority = LogPriority.ERROR, throwable = exception)
launchActivity(applicationContext, activityToBeLaunched, exception)
defaultHandler.uncaughtException(thread, exception)
}
private fun launchActivity(
@@ -15,7 +15,6 @@ import coil3.request.bitmapConfig
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
import eu.kanade.tachiyomi.util.system.GLUtil
import mihon.core.common.archive.archiveReader
import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil
@@ -71,7 +70,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
options.bitmapConfig == Bitmap.Config.HARDWARE &&
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
ImageUtil.canUseHardwareBitmap(bitmap)
) {
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
if (hwBitmap != null) {
@@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models
@@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models
@@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models
@@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.database.models
@@ -96,13 +96,13 @@ class DownloadCache(
private val diskCacheFile: File
get() = File(context.cacheDir, "dl_index_cache_v3")
private val rootDownloadsDirLock = Mutex()
private val rootDownloadsDirMutex = Mutex()
private var rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory())
init {
// Attempt to read cache file
scope.launch {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
try {
if (diskCacheFile.exists()) {
val diskCache = diskCacheFile.inputStream().use {
@@ -112,7 +112,7 @@ class DownloadCache(
lastRenew = System.currentTimeMillis()
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Failed to initialize disk cache" }
logcat(LogPriority.ERROR, e) { "Failed to initialize from disk cache" }
diskCacheFile.delete()
}
}
@@ -200,7 +200,7 @@ class DownloadCache(
* @param manga the manga of the chapter.
*/
suspend fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
// Retrieve the cached source directory or cache a new one
var sourceDir = rootDownloadsDir.sourceDirs[manga.source]
if (sourceDir == null) {
@@ -232,7 +232,7 @@ class DownloadCache(
* @param manga the manga of the chapter.
*/
suspend fun removeChapter(chapter: Chapter, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.mangaDirs[
provider.getMangaDirName(
@@ -251,7 +251,7 @@ class DownloadCache(
// SY -->
suspend fun removeFolders(folders: List<String>, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.ogTitle)] ?: return
folders.forEach { chapter ->
@@ -271,7 +271,7 @@ class DownloadCache(
* @param manga the manga of the chapter.
*/
suspend fun removeChapters(chapters: List<Chapter>, manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.mangaDirs[
provider.getMangaDirName(
@@ -296,7 +296,7 @@ class DownloadCache(
* @param manga the manga to remove.
*/
suspend fun removeManga(manga: Manga) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDirName = provider.getMangaDirName(/* SY --> */ manga.ogTitle /* SY <-- */)
if (sourceDir.mangaDirs.containsKey(mangaDirName)) {
@@ -308,7 +308,7 @@ class DownloadCache(
}
suspend fun removeSource(source: Source) {
rootDownloadsDirLock.withLock {
rootDownloadsDirMutex.withLock {
rootDownloadsDir.sourceDirs -= source.id
}
@@ -349,10 +349,10 @@ class DownloadCache(
val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id }
rootDownloadsDirLock.withLock {
rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory())
rootDownloadsDirMutex.withLock {
val updatedRootDir = RootDirectory(storageManager.getDownloadsDirectory())
val sourceDirs = rootDownloadsDir.dir?.listFiles().orEmpty()
updatedRootDir.sourceDirs = updatedRootDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.mapNotNull { dir ->
val sourceId = sourceMap[dir.name!!.lowercase()]
@@ -360,36 +360,35 @@ class DownloadCache(
}
.toMap()
rootDownloadsDir.sourceDirs = sourceDirs
updatedRootDir.sourceDirs.values.map { sourceDir ->
async {
sourceDir.mangaDirs = sourceDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.associate { it.name!! to MangaDirectory(it) }
sourceDirs.values
.map { sourceDir ->
async {
sourceDir.mangaDirs = sourceDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.associate { it.name!! to MangaDirectory(it) }
sourceDir.mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir?.listFiles().orEmpty()
.mapNotNull {
when {
// Ignore incomplete downloads
it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true -> null
// Folder of images
it.isDirectory -> it.name
// CBZ files
it.isFile && it.extension == "cbz" -> it.nameWithoutExtension
// Anything else is irrelevant
else -> null
}
sourceDir.mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir?.listFiles().orEmpty()
.mapNotNull {
when {
// Ignore incomplete downloads
it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true -> null
// Folder of images
it.isDirectory -> it.name
// CBZ files
it.isFile && it.extension == "cbz" -> it.nameWithoutExtension
// Anything else is irrelevant
else -> null
}
.toMutableSet()
}
.toMutableSet()
mangaDir.chapterDirs = chapterDirs
}
mangaDir.chapterDirs = chapterDirs
}
}
}
.awaitAll()
rootDownloadsDir = updatedRootDir
}
_isInitializing.emit(false)
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.library
import android.content.Context
import android.content.pm.ServiceInfo
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.work.BackoffPolicy
import androidx.work.Constraints
@@ -135,10 +137,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
override suspend fun doWork(): Result {
if (tags.contains(WORK_NAME_AUTO)) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
}
// Find a running manual worker. If exists, try again later
@@ -404,7 +408,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
it.read
}
val newReadChapters = this.filter { chapter ->
chapter.chapterNumber > 0 &&
chapter.chapterNumber >= 0 &&
readChapters.any {
it.chapterNumber == chapter.chapterNumber
}
@@ -768,15 +772,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
if (interval > 0) {
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val constraints = Constraints(
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
},
requiresCharging = DEVICE_CHARGING in restrictions,
requiresBatteryNotLow = true,
)
val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
}
val networkRequestBuilder = NetworkRequest.Builder()
if (DEVICE_ONLY_ON_WIFI in restrictions) {
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
}
if (DEVICE_NETWORK_NOT_METERED in restrictions) {
networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
}
val constraints = Constraints.Builder()
// 'networkRequest' only applies to Android 9+, otherwise 'networkType' is used
.setRequiredNetworkRequest(networkRequestBuilder.build(), networkType)
.setRequiresCharging(DEVICE_CHARGING in restrictions)
.setRequiresBatteryNotLow(true)
.build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(),
@@ -30,6 +30,9 @@ object Notifications {
const val ID_LIBRARY_SIZE_WARNING = -103
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
const val ID_LIBRARY_ERROR = -102
const val CHANNEL_LIBRARY_EHENTAI = "library_ehentai_channel"
const val ID_EHENTAI_PROGRESS = -199
const val ID_EHENTAI_ERROR = -198
/**
* Notification channel and ids used by the downloader.
@@ -71,6 +74,7 @@ object Notifications {
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
const val ID_APP_UPDATER = 1
const val ID_APP_UPDATE_PROMPT = 2
const val ID_APP_UPDATE_ERROR = 3
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
const val ID_UPDATES_TO_EXTS = -401
const val ID_EXTENSION_INSTALLER = -402
@@ -166,6 +170,13 @@ object Notifications {
setGroup(GROUP_APK_UPDATES)
setName(context.stringResource(MR.strings.channel_ext_updates))
},
// SY -->
buildNotificationChannel(CHANNEL_LIBRARY_EHENTAI, IMPORTANCE_LOW) {
setName("EHentai")
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
// SY <--
),
)
}
@@ -175,6 +175,7 @@ sealed class Image(
}
sealed interface Location {
@ConsistentCopyVisibility
data class Pictures private constructor(val relativePath: String) : Location {
companion object {
fun create(relativePath: String = ""): Pictures {
@@ -15,6 +15,7 @@ import androidx.work.WorkerParameters
import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager
@@ -31,6 +32,9 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
override suspend fun doWork(): Result {
if (tags.contains(TAG_AUTO)) {
if (!context.isOnline()) {
return Result.retry()
}
// Find a running manual worker. If exists, try again later
if (context.workManager.isRunning(TAG_MANUAL)) {
return Result.retry()
@@ -93,17 +97,18 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
}
}
fun startNow(context: Context) {
fun startNow(context: Context, manual: Boolean = false) {
val wm = context.workManager
if (wm.isRunning(TAG_JOB)) {
// Already running either as a scheduled or manual job
return
}
val tag = if (manual) TAG_MANUAL else TAG_AUTO
val request = OneTimeWorkRequestBuilder<SyncDataJob>()
.addTag(TAG_JOB)
.addTag(TAG_MANUAL)
.addTag(tag)
.build()
context.workManager.enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request)
context.workManager.enqueueUniqueWork(tag, ExistingWorkPolicy.KEEP, request)
}
fun stop(context: Context) {
@@ -6,6 +6,7 @@ import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.Flow
@@ -120,6 +121,10 @@ abstract class BaseTracker(
updateRemote(track)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
throw NotImplementedError("Not implemented.")
}
private suspend fun updateRemote(track: Track): Unit = withIOContext {
try {
update(track)
@@ -5,6 +5,7 @@ import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
@@ -82,4 +83,6 @@ interface Tracker {
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata?
}
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -232,6 +233,10 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
interceptor.setAuth(null)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
fun saveOAuth(alOAuth: ALOAuth?) {
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
}
@@ -5,15 +5,18 @@ import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
@@ -288,6 +291,71 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
val query = """
|query (${'$'}mangaId: Int!) {
|Media (id: ${'$'}mangaId) {
|id
|title {
|userPreferred
|}
|coverImage {
|large
|}
|description
|staff {
|edges {
|role
|node {
|name {
|userPreferred
|}
|}
|}
|}
|}
|}
|
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("mangaId", track.remoteId)
}
}
with(json) {
authClient.newCall(
POST(
API_URL,
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<ALMangaMetadata>()
.let {
val media = it.data.media
TrackMangaMetadata(
remoteId = media.id,
title = media.title.userPreferred,
thumbnailUrl = media.coverImage.large,
description = media.description?.htmlDecode()?.ifEmpty { null },
authors = media.staff.edges
.filter { it.role == "Story" || it.role == "Story & Art" }
.map { it.node.name.userPreferred }
.joinToString(", ")
.ifEmpty { null },
artists = media.staff.edges
.filter { it.role == "Art" || it.role == "Story & Art" }
.map { it.node.name.userPreferred }
.joinToString(", ")
.ifEmpty { null },
)
}
}
}
}
private fun createDate(dateValue: Long): JsonObject {
if (dateValue == 0L) {
return buildJsonObject {
@@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.data.track.anilist.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ALMangaMetadata(
val data: ALMangaMetadataData,
)
@Serializable
data class ALMangaMetadataData(
@SerialName("Media")
val media: ALMangaMetadataMedia,
)
@Serializable
data class ALMangaMetadataMedia(
val id: Long,
val title: ALItemTitle,
val coverImage: ItemCover,
val description: String?,
val staff: ALStaff,
)
@Serializable
data class ALStaff(
val edges: List<ALStaffEdge>,
)
@Serializable
data class ALStaffEdge(
val role: String,
val node: ALStaffNode,
)
@Serializable
data class ALStaffNode(
val name: ALItemTitle,
)
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -75,6 +76,10 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
return api.search(query)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
override suspend fun refresh(track: Track): Track {
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
track.copyPersonalFrom(remoteStatusTrack)
@@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMCollectionResponse
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchResult
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSubject
import eu.kanade.tachiyomi.data.track.bangumi.dto.Infobox
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
@@ -21,6 +24,7 @@ import tachiyomi.core.common.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import tachiyomi.domain.track.model.Track as DomainTrack
class BangumiApi(
private val trackId: Long,
@@ -71,6 +75,8 @@ class BangumiApi(
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
.toUri()
.buildUpon()
.appendQueryParameter("type", "1")
.appendQueryParameter("responseGroup", "large")
.appendQueryParameter("max_results", "20")
.build()
with(json) {
@@ -81,7 +87,6 @@ class BangumiApi(
if (result.code == 404) emptyList<TrackSearch>()
result.list
?.filter { it.type == 1 }
?.map { it.toTrackSearch(trackId) }
.orEmpty()
}
@@ -126,6 +131,34 @@ class BangumiApi(
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
with(json) {
authClient.newCall(GET("${API_URL}/v0/subjects/${track.remoteId}"))
.awaitSuccess()
.parseAs<BGMSubject>()
.let {
TrackMangaMetadata(
remoteId = it.id,
title = it.nameCn,
thumbnailUrl = it.images?.common,
description = it.summary,
authors = it.infobox
.filter { it.key == "作者" }
.filterIsInstance<Infobox.SingleValue>()
.map { it.value }
.joinToString(", "),
artists = it.infobox
.filter { it.key == "插图" }
.filterIsInstance<Infobox.SingleValue>()
.map { it.value }
.joinToString(", "),
)
}
}
}
}
suspend fun accessToken(code: String): BGMOAuth {
return withIOContext {
with(json) {
@@ -17,6 +17,7 @@ data class BGMSearchItem(
val nameCn: String,
val name: String,
val type: Int,
val summary: String?,
val images: BGMSearchItemCovers?,
@SerialName("eps_count")
val epsCount: Long?,
@@ -25,9 +26,13 @@ data class BGMSearchItem(
) {
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
remote_id = this@BGMSearchItem.id
title = nameCn
cover_url = images?.common ?: ""
summary = this@BGMSearchItem.name
title = nameCn.ifBlank { name }
cover_url = images?.common.orEmpty()
summary = if (nameCn.isNotBlank()) {
"作品原名:$name" + this@BGMSearchItem.summary?.let { "\n$it" }.orEmpty()
} else {
this@BGMSearchItem.summary.orEmpty()
}
score = rating?.score ?: -1.0
tracking_url = url
total_chapters = epsCount ?: 0
@@ -0,0 +1,62 @@
package eu.kanade.tachiyomi.data.track.bangumi.dto
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class BGMSubject(
val images: BGMSearchItemCovers?,
val summary: String,
val name: String,
@SerialName("name_cn")
val nameCn: String,
val infobox: List<Infobox>,
val id: Long,
)
// infobox deserializer and related classes courtesy of
// https://github.com/Snd-R/komf/blob/4c260a3dcd326a5e1d74ac9662eec8124ab7e461/komf-core/src/commonMain/kotlin/snd/komf/providers/bangumi/model/BangumiSubject.kt#L53-L89
object InfoBoxSerializer : JsonContentPolymorphicSerializer<Infobox>(Infobox::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Infobox> {
if (element !is JsonObject) throw SerializationException("Expected JsonObject go ${element::class}")
val value = element["value"]
return when (value) {
is JsonArray -> Infobox.MultipleValues.serializer()
is JsonPrimitive -> Infobox.SingleValue.serializer()
else -> throw SerializationException("Unexpected element type ${element::class}")
}
}
}
@Serializable(with = InfoBoxSerializer::class)
sealed interface Infobox {
val key: String
@Serializable
class SingleValue(
override val key: String,
val value: String,
) : Infobox
@Serializable
class MultipleValues(
override val key: String,
val value: List<InfoboxNestedValue>,
) : Infobox
}
@Serializable
data class InfoboxNestedValue(
@SerialName("k")
val key: String? = null,
@SerialName("v")
val value: String,
)
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -139,6 +140,10 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
interceptor.newAuth(null)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return api.getMangaMetadata(track)
}
private fun getUserId(): String {
return getPassword()
}
@@ -6,8 +6,10 @@ import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAddMangaResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAlgoliaSearchResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuMangaMetadata
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET
@@ -15,6 +17,7 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
@@ -240,11 +243,80 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
val query = """
|query(${'$'}libraryId: ID!, ${'$'}staffCount: Int) {
|findLibraryEntryById(id: ${'$'}libraryId) {
|media {
|id
|titles {
|preferred
|}
|posterImage {
|original {
|url
|}
|}
|description
|staff(first: ${'$'}staffCount) {
|nodes {
|role
|person {
|name
|}
|}
|}
|}
|}
|}
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("libraryId", track.remoteId)
put("staffCount", 25) // 25 based on nothing
}
}
with(json) {
authClient.newCall(
POST(
GRAPHQL_URL,
headers = headersOf("Accept-Language", "en"),
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<KitsuMangaMetadata>()
.let {
val manga = it.data.findLibraryEntryById.media
TrackMangaMetadata(
remoteId = manga.id.toLong(),
title = manga.titles.preferred,
thumbnailUrl = manga.posterImage.original.url,
description = manga.description.en?.htmlDecode()?.ifEmpty { null },
authors = manga.staff.nodes
.filter { it.role == "Story" || it.role == "Story & Art" }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
artists = manga.staff.nodes
.filter { it.role == "Art" || it.role == "Story & Art" }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
)
}
}
}
}
companion object {
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val BASE_URL = "https://kitsu.app/api/edge/"
private const val GRAPHQL_URL = "https://kitsu.app/api/graphql"
private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
@@ -0,0 +1,63 @@
package eu.kanade.tachiyomi.data.track.kitsu.dto
import kotlinx.serialization.Serializable
@Serializable
data class KitsuMangaMetadata(
val data: KitsuMangaMetadataData,
)
@Serializable
data class KitsuMangaMetadataData(
val findLibraryEntryById: KitsuMangaMetadataById,
)
@Serializable
data class KitsuMangaMetadataById(
val media: KitsuMangaMetadataMedia,
)
@Serializable
data class KitsuMangaMetadataMedia(
val id: String,
val titles: KitsuMangaTitle,
val posterImage: KitsuMangaCover,
val description: KitsuMangaDescription,
val staff: KitsuMangaStaff,
)
@Serializable
data class KitsuMangaTitle(
val preferred: String,
)
@Serializable
data class KitsuMangaCover(
val original: KitsuMangaCoverUrl,
)
@Serializable
data class KitsuMangaCoverUrl(
val url: String,
)
@Serializable
data class KitsuMangaDescription(
val en: String?,
)
@Serializable
data class KitsuMangaStaff(
val nodes: List<KitsuMangaStaffNode>,
)
@Serializable
data class KitsuMangaStaffNode(
val role: String,
val person: KitsuMangaStaffPerson,
)
@Serializable
data class KitsuMangaStaffPerson(
val name: String,
)
@@ -10,7 +10,9 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUListItem
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import tachiyomi.i18n.MR
@@ -117,6 +119,20 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
interceptor.newAuth(authenticated.sessionToken)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
val series = api.getSeries(track)
return series?.let {
TrackMangaMetadata(
it.seriesId,
it.title?.htmlDecode(),
it.image?.url?.original,
it.description?.htmlDecode(),
it.authors?.filter { it.type == "Author" }?.joinToString(separator = ", ") { it.name ?: "" },
it.authors?.filter { it.type == "Artist" }?.joinToString(separator = ", ") { it.name ?: "" },
)
}
}
fun restoreSession(): String? {
return trackPreferences.trackPassword(this).get().ifBlank { null }
}
@@ -190,6 +190,14 @@ class MangaUpdatesApi(
}
}
suspend fun getSeries(track: DomainTrack): MURecord {
return with(json) {
client.newCall(GET("$BASE_URL/v1/series/${track.remoteId}"))
.awaitSuccess()
.parseAs<MURecord>()
}
}
companion object {
private const val BASE_URL = "https://api.mangaupdates.com"
@@ -21,6 +21,7 @@ data class MURecord(
val ratingVotes: Int? = null,
@SerialName("latest_chapter")
val latestChapter: Int? = null,
val authors: List<MUAuthor>? = null,
)
fun MURecord.toTrackSearch(id: Long): TrackSearch {
@@ -36,3 +37,9 @@ fun MURecord.toTrackSearch(id: Long): TrackSearch {
start_date = this@toTrackSearch.year.toString()
}
}
@Serializable
data class MUAuthor(
val type: String? = null,
val name: String? = null,
)
@@ -2,9 +2,11 @@ package eu.kanade.tachiyomi.data.track.mdlist
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
@@ -168,6 +170,21 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
trackPreferences.trackToken(this).delete()
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return withIOContext {
val mdex = mdex ?: throw MangaDexNotFoundException()
val manga = mdex.getMangaMetadata(track.toDbTrack())
TrackMangaMetadata(
remoteId = 0,
title = manga?.title,
thumbnailUrl = manga?.thumbnail_url, // Doesn't load the actual cover because of Refer header
description = manga?.description,
authors = manga?.author,
artists = manga?.artist,
)
}
}
override val isLoggedIn: Boolean
get() = trackPreferences.trackToken(this).get().isNotEmpty()
@@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.data.track.model
data class TrackMangaMetadata(
val remoteId: Long? = null,
val title: String? = null,
val thumbnailUrl: String? = null,
val description: String? = null,
val authors: String? = null,
val artists: String? = null,
)
@@ -1,4 +1,4 @@
@file:Suppress("PropertyName", "ktlint:standard:property-naming")
@file:Suppress("PropertyName")
package eu.kanade.tachiyomi.data.track.model
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import kotlinx.collections.immutable.ImmutableList
@@ -156,6 +157,10 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
interceptor.setAuth(null)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
fun getIfAuthExpired(): Boolean {
return trackPreferences.trackAuthExpired(this).get()
}
@@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALMangaMetadata
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
@@ -193,6 +195,41 @@ class MyAnimeListApi(
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return withIOContext {
val url = "$BASE_API_URL/manga".toUri().buildUpon()
.appendPath(track.remoteId.toString())
.appendQueryParameter(
"fields",
"id,title,synopsis,main_picture,authors{first_name,last_name}",
)
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<MALMangaMetadata>()
.let {
TrackMangaMetadata(
remoteId = it.id,
title = it.title,
thumbnailUrl = it.covers.large.ifEmpty { null } ?: it.covers.medium,
description = it.synopsis,
authors = it.authors
.filter { it.role == "Story" || it.role == "Story & Art" }
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
.joinToString(separator = ", ")
.ifEmpty { null },
artists = it.authors
.filter { it.role == "Art" || it.role == "Story & Art" }
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
.joinToString(separator = ", ")
.ifEmpty { null },
)
}
}
}
}
private suspend fun getListPage(offset: Int): MALUserSearchResult {
return withIOContext {
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
@@ -23,4 +23,29 @@ data class MALManga(
@Serializable
data class MALMangaCovers(
val large: String = "",
val medium: String,
)
@Serializable
data class MALMangaMetadata(
val id: Long,
val title: String,
val synopsis: String?,
@SerialName("main_picture")
val covers: MALMangaCovers,
val authors: List<MALAuthor>,
)
@Serializable
data class MALAuthor(
val node: MALAuthorNode,
val role: String,
)
@Serializable
data class MALAuthorNode(
@SerialName("first_name")
val firstName: String,
@SerialName("last_name")
val lastName: String,
)
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
import kotlinx.collections.immutable.ImmutableList
@@ -98,6 +99,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
return track
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
override fun getLogo() = R.drawable.ic_tracker_shikimori
override fun getLogoColor() = Color.rgb(40, 40, 40)
@@ -3,9 +3,11 @@ package eu.kanade.tachiyomi.data.track.shikimori
import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMManga
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMMetadata
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry
@@ -132,6 +134,65 @@ class ShikimoriApi(
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
val query = """
|query(${'$'}ids: String!) {
|mangas(ids: ${'$'}ids) {
|id
|name
|description
|poster {
|originalUrl
|}
|personRoles {
|person {
|name
|}
|rolesEn
|}
|}
|}
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("ids", "${track.remoteId}")
}
}
with(json) {
authClient.newCall(
POST(
"https://shikimori.one/api/graphql",
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<SMMetadata>()
.let {
if (it.data.mangas.isEmpty()) throw Exception("Could not get metadata from Shikimori")
val manga = it.data.mangas[0]
TrackMangaMetadata(
remoteId = manga.id.toLong(),
title = manga.name,
thumbnailUrl = manga.poster.originalUrl,
description = manga.description,
authors = manga.personRoles
.filter { it.rolesEn.contains("Story") || it.rolesEn.contains("Story & Art") }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
artists = manga.personRoles
.filter { it.rolesEn.contains("Art") || it.rolesEn.contains("Story & Art") }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
)
}
}
}
}
suspend fun accessToken(code: String): SMOAuth {
return withIOContext {
with(json) {
@@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.data.track.shikimori.dto
import kotlinx.serialization.Serializable
@Serializable
data class SMMetadata(
val data: SMMetadataData,
)
@Serializable
data class SMMetadataData(
val mangas: List<SMMetadataResult>,
)
@Serializable
data class SMMetadataResult(
val id: String,
val name: String,
val description: String,
val poster: SMMangaPoster,
val personRoles: List<SMMangaPersonRoles>,
)
@Serializable
data class SMMangaPoster(
val originalUrl: String,
)
@Serializable
data class SMMangaPersonRoles(
val person: SMPerson,
val rolesEn: List<String>,
)
@Serializable
data class SMPerson(
val name: String,
)
@@ -181,9 +181,9 @@ internal class AppUpdateNotifier(private val context: Context) {
addAction(
R.drawable.ic_close_24dp,
context.stringResource(MR.strings.action_cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_ERROR),
)
}
notificationBuilder.show(Notifications.ID_APP_UPDATER)
notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR)
}
}
@@ -14,10 +14,12 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import logcat.LogPriority
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuRemoteProcess
import tachiyomi.core.common.util.system.logcat
import tachiyomi.i18n.MR
import java.io.BufferedReader
import java.io.InputStream
import java.lang.reflect.Method
class ShizukuInstaller(private val service: Service) : Installer(service) {
@@ -93,9 +95,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
super.onDestroy()
}
private val newProcess: Method
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
@Suppress("DEPRECATION")
val process = Shizuku.newProcess(arrayOf("sh", "-c", command), null, null)
val process = newProcess.invoke(null, arrayOf("sh", "-c", command), null, null) as ShizukuRemoteProcess
if (stdin != null) {
process.outputStream.use { stdin.copyTo(it) }
}
@@ -122,6 +124,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
service.stopSelf()
false
}
newProcess = Shizuku::class.java
.getDeclaredMethod("newProcess", Array<out String>::class.java, Array<out String>::class.java, String::class.java)
newProcess.isAccessible = true
}
}
@@ -449,7 +449,11 @@ class EHentai(
private fun parseChapterPage(response: Element) = with(response) {
select(".gdtm a").map {
Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
}.sortedBy(Pair<Int, String>::first).map { it.second }
}.plus(
select("#gdt a").map {
Pair(it.child(0).attr("title").removePrefix("Page ").substringBefore(":").toInt(), it.attr("href"))
},
).sortedBy(Pair<Int, String>::first).map { it.second }
}
private fun chapterPageCall(np: String): Observable<Response> {
@@ -1214,7 +1218,8 @@ class EHentai(
val body = doc.body()
val previews = body
.select("#gdt div div")
.select("#gdt > div > div")
.plus(body.select("#gdt > a"))
.map {
val preview = parseNormalPreview(it)
PagePreviewInfo(preview.index, imageUrl = preview.toUrl())
@@ -1250,8 +1255,15 @@ class EHentai(
* Parse normal previews with regular expressions
*/
private fun parseNormalPreview(element: Element): EHentaiThumbnailPreview {
val index = element.selectFirst("img")!!.attr("alt").toInt()
val styles = element.attr("style").split(";").mapNotNull { it.trimOrNull() }
val imgElement = element.selectFirst("img")
val index = imgElement?.attr("alt")?.toInt()
?: element.child(0).attr("title").removePrefix("Page ").substringBefore(":").toInt()
val styleElement = if (imgElement != null) {
element
} else {
element.child(0)
}
val styles = styleElement.attr("style").split(";").mapNotNull { it.trimOrNull() }
val width = styles.first { it.startsWith("width:") }
.removePrefix("width:")
.removeSuffix("px")
@@ -1275,7 +1287,7 @@ class EHentai(
.removeSuffix("px")
.toInt()
return EHentaiThumbnailPreview(url, width, height, widthOffset, index).also(::println)
return EHentaiThumbnailPreview(url, width, height, widthOffset, index)
}
data class EHentaiThumbnailPreview(
val imageUrl: String,
@@ -313,6 +313,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return similarHandler.getRelated(manga)
}
suspend fun getMangaMetadata(track: Track): SManga? {
return mangaHandler.getMangaMetadata(track, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc())
}
companion object {
private const val dataSaverPref = "dataSaverV5"
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -27,14 +28,16 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
import eu.kanade.tachiyomi.ui.main.MainActivity
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
data class BrowseTab(
private val toExtensions: Boolean = false,
) : Tab {
data object BrowseTab : Tab {
override val options: TabOptions
@Composable
@@ -52,6 +55,12 @@ data class BrowseTab(
navigator.push(GlobalSearchScreen())
}
private val switchToExtensionTabChannel = Channel<Unit>(1, BufferOverflow.DROP_OLDEST)
fun showExtension() {
switchToExtensionTabChannel.trySend(Unit)
}
@Composable
override fun Content() {
val context = LocalContext.current
@@ -65,35 +74,43 @@ data class BrowseTab(
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
val extensionsState by extensionsScreenModel.state.collectAsState()
// SY -->
val tabs = if (hideFeedTab) {
persistentListOf(
sourcesTab(),
extensionsTab(extensionsScreenModel),
migrateSourceTab(),
)
} else if (feedTabInFront) {
persistentListOf(
feedTab(),
sourcesTab(),
extensionsTab(extensionsScreenModel),
migrateSourceTab(),
)
} else {
persistentListOf(
sourcesTab(),
feedTab(),
extensionsTab(extensionsScreenModel),
migrateSourceTab(),
)
}
// SY <--
val state = rememberPagerState { tabs.size }
TabbedScreen(
titleRes = MR.strings.browse,
// SY -->
tabs = if (hideFeedTab) {
persistentListOf(
sourcesTab(),
extensionsTab(extensionsScreenModel),
migrateSourceTab(),
)
} else if (feedTabInFront) {
persistentListOf(
feedTab(),
sourcesTab(),
extensionsTab(extensionsScreenModel),
migrateSourceTab(),
)
} else {
persistentListOf(
sourcesTab(),
feedTab(),
extensionsTab(extensionsScreenModel),
migrateSourceTab(),
)
},
startIndex = 2.takeIf { toExtensions },
// SY <--
tabs = tabs,
state = state,
searchQuery = extensionsState.searchQuery,
onChangeSearchQuery = extensionsScreenModel::search,
)
LaunchedEffect(Unit) {
switchToExtensionTabChannel.receiveAsFlow()
.collectLatest { state.scrollToPage(/* SY --> */2/* SY <-- */) }
}
LaunchedEffect(Unit) {
(context as? MainActivity)?.ready = true
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -29,8 +27,7 @@ import tachiyomi.i18n.sy.SYMR
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
@delegate:Transient
var newSelectedItem by mutableStateOf<Pair<Long, Long>?>(null)
var newSelectedItem: Pair<Long, Long>? = null
@Composable
override fun Content() {
@@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import exh.ui.ifSourcesLoaded
import kotlinx.collections.immutable.persistentListOf
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold
@@ -71,7 +71,6 @@ data class SourceSearchScreen(
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
val openMigrateDialog: (Manga) -> Unit = {
// SY -->
navigator.items
@@ -83,7 +82,7 @@ data class SourceSearchScreen(
}
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.InputChip
import androidx.compose.material3.InputChipDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -154,7 +155,7 @@ fun AutoCompleteTextField(
null
},
modifier = Modifier
.menuAnchor()
.menuAnchor(MenuAnchorType.PrimaryEditable)
.fillMaxWidth()
.runOnEnterKeyPressed { submit() },
singleLine = true,
@@ -32,7 +32,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -61,6 +60,7 @@ import exh.ui.ifSourcesLoaded
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.UnsortedPreferences
@@ -240,11 +240,9 @@ data class BrowseSourceScreen(
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -38,7 +38,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object HistoryTab : Tab {
data object HistoryTab : Tab {
private val snackbarHostState = SnackbarHostState()
@@ -30,13 +30,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabNavigator
import eu.kanade.core.preference.asState
import eu.kanade.core.util.fastFilter
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.util.Screen
@@ -73,11 +73,11 @@ object HomeScreen : Screen() {
private const val TAB_FADE_DURATION = 200
private const val TAB_NAVIGATOR_KEY = "HomeTabs"
private val tabs = listOf(
private val TABS = listOf(
LibraryTab,
UpdatesTab,
HistoryTab,
BrowseTab(),
BrowseTab,
MoreTab,
)
@@ -102,7 +102,7 @@ object HomeScreen : Screen() {
startBar = {
if (isTabletUi()) {
NavigationRail {
tabs
TABS
// SY -->
.fastFilter { it.isEnabled() }
// SY <--
@@ -123,7 +123,7 @@ object HomeScreen : Screen() {
exit = shrinkVertically(),
) {
NavigationBar {
tabs
TABS
// SY -->
.fastFilter { it.isEnabled() }
// SY <--
@@ -179,7 +179,12 @@ object HomeScreen : Screen() {
is Tab.Library -> LibraryTab
Tab.Updates -> UpdatesTab
Tab.History -> HistoryTab
is Tab.Browse -> BrowseTab(it.toExtensions)
is Tab.Browse -> {
if (it.toExtensions) {
BrowseTab.showExtension()
}
BrowseTab
}
is Tab.More -> MoreTab
}
@@ -7,16 +7,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMapNotNull
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.PreferenceMutableState
import eu.kanade.core.preference.asState
import eu.kanade.core.util.fastDistinctBy
import eu.kanade.core.util.fastFilter
import eu.kanade.core.util.fastFilterNot
import eu.kanade.core.util.fastMapNotNull
import eu.kanade.core.util.fastPartition
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.interactor.SetReadStatus
@@ -75,7 +75,7 @@ import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object LibraryTab : Tab {
data object LibraryTab : Tab {
override val options: TabOptions
@Composable
@@ -165,7 +165,7 @@ object LibraryTab : Tab {
},
onClickSyncNow = {
if (!SyncDataJob.isRunning(context)) {
SyncDataJob.startNow(context)
SyncDataJob.startNow(context, manual = true)
} else {
context.toast(SYMR.strings.sync_in_progress)
}
@@ -334,8 +334,8 @@ object LibraryTab : Tab {
// SY -->
SyncFavoritesProgressDialog(
status = screenModel.favoritesSync.status.collectAsState().value,
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
openManga = { navigator.push(MangaScreen(it.id)) },
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle },
openManga = { navigator.push(MangaScreen(it)) },
)
// SY <--
@@ -346,12 +346,13 @@ class MainActivity : BaseActivity() {
@Composable
private fun HandleOnNewIntent(context: Context, navigator: Navigator) {
LaunchedEffect(Unit) {
callbackFlow<Intent> {
callbackFlow {
val componentActivity = context as ComponentActivity
val consumer = Consumer<Intent> { trySend(it) }
componentActivity.addOnNewIntentListener(consumer)
awaitClose { componentActivity.removeOnNewIntentListener(consumer) }
}.collectLatest { handleIntentAction(it, navigator) }
}
.collectLatest { handleIntentAction(it, navigator) }
}
}
@@ -407,6 +408,7 @@ class MainActivity : BaseActivity() {
* When custom animation is used, status and navigation bar color will be set to transparent and will be restored
* after the animation is finished.
*/
@Suppress("Deprecation")
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
val root = findViewById<View>(android.R.id.content)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
@@ -3,20 +3,26 @@ package eu.kanade.tachiyomi.ui.manga
import android.content.Context
import android.view.LayoutInflater
import android.widget.ArrayAdapter
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.core.view.children
@@ -26,22 +32,34 @@ import coil3.transform.RoundedCornersTransformation
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
import exh.util.dropBlank
import exh.util.trimOrNull
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
fun EditMangaDialog(
@@ -61,6 +79,10 @@ fun EditMangaDialog(
var binding by remember {
mutableStateOf<EditMangaDialogBinding?>(null)
}
val showTrackerSelectionDialogue = remember { mutableStateOf(false) }
val getTracks = remember { Injekt.get<GetTracks>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val tracks = remember { mutableStateOf(emptyList<Pair<Track, Tracker>>()) }
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
@@ -109,7 +131,7 @@ fun EditMangaDialog(
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
.also { binding = it }
.apply {
onViewCreated(manga, factoryContext, this, scope)
onViewCreated(manga, factoryContext, this, scope, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
}
.root
},
@@ -118,9 +140,61 @@ fun EditMangaDialog(
}
},
)
if (showTrackerSelectionDialogue.value) {
TrackerSelectDialog(
tracks = tracks.value,
onDismissRequest = { showTrackerSelectionDialogue.value = false },
onTrackerSelect = { tracker, track ->
scope.launch {
autofillFromTracker(binding!!, track, tracker)
}
},
)
}
}
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope) {
@Composable
private fun TrackerSelectDialog(
tracks: List<Pair<Track, Tracker>>,
onDismissRequest: () -> Unit,
onTrackerSelect: (
tracker: Tracker,
track: Track,
) -> Unit,
) {
AlertDialog(
modifier = Modifier.fillMaxWidth(),
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(MR.strings.action_cancel))
}
},
title = {
Text(stringResource(SYMR.strings.select_tracker))
},
text = {
FlowRow(
modifier = Modifier
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
tracks.forEach { (track, tracker) ->
TrackLogoIcon(
tracker,
onClick = {
onTrackerSelect(tracker, track)
onDismissRequest()
},
)
}
}
},
)
}
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
loadCover(manga, binding)
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
@@ -203,6 +277,55 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
binding.autofillFromTracker.setOnClickListener {
scope.launch {
getTrackers(manga, binding, context, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
}
}
}
private suspend fun getTrackers(manga: Manga, binding: EditMangaDialogBinding, context: Context, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
tracks.value = getTracks.await(manga.id).map { track ->
track to trackerManager.get(track.trackerId)!!
}
.filterNot { (_, tracker) -> tracker is EnhancedTracker }
if (tracks.value.isEmpty()) {
context.toast(context.stringResource(SYMR.strings.entry_not_tracked))
return
}
if (tracks.value.size > 1) {
showTrackerSelectionDialogue.value = true
return
}
autofillFromTracker(binding, tracks.value.first().first, tracks.value.first().second)
}
private fun setTextIfNotBlank(field: (String) -> Unit, value: String?) {
value?.takeIf { it.isNotBlank() }?.let { field(it) }
}
private suspend fun autofillFromTracker(binding: EditMangaDialogBinding, track: Track, tracker: Tracker) {
try {
val trackerMangaMetadata = tracker.getMangaMetadata(track)
setTextIfNotBlank(binding.title::setText, trackerMangaMetadata?.title)
setTextIfNotBlank(binding.mangaAuthor::setText, trackerMangaMetadata?.authors)
setTextIfNotBlank(binding.mangaArtist::setText, trackerMangaMetadata?.artists)
setTextIfNotBlank(binding.thumbnailUrl::setText, trackerMangaMetadata?.thumbnailUrl)
setTextIfNotBlank(binding.mangaDescription::setText, trackerMangaMetadata?.description)
} catch (e: Throwable) {
tracker.logcat(LogPriority.ERROR, e)
binding.root.context.toast(
binding.root.context.stringResource(
MR.strings.track_error,
tracker.name,
e.message ?: "",
),
)
}
}
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
@@ -29,7 +29,9 @@ import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.interactor.TrackChapter
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadAction
@@ -48,6 +50,7 @@ import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.system.toast
import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateHelper
import exh.log.xLogD
@@ -145,6 +148,7 @@ class MangaScreenModel(
private val isFromSource: Boolean,
val smartSearched: Boolean,
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val trackPreferences: TrackPreferences = Injekt.get(),
readerPreferences: ReaderPreferences = Injekt.get(),
uiPreferences: UiPreferences = Injekt.get(),
private val trackerManager: TrackerManager = Injekt.get(),
@@ -208,6 +212,7 @@ class MangaScreenModel(
val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get()
var autoTrackState = trackPreferences.autoUpdateTrackOnMarkRead().get()
private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
@@ -1258,19 +1263,29 @@ class MangaScreenModel(
*/
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
toggleAllSelection(false)
if (chapters.isEmpty()) return
screenModelScope.launchIO {
setReadStatus.await(
read = read,
chapters = chapters.toTypedArray(),
)
if (!read) return@launchIO
if (!read || successState?.hasLoggedInTrackers == false || autoTrackState == AutoTrackState.NEVER) {
return@launchIO
}
val tracks = getTracks.await(mangaId)
val maxChapterNumber = chapters.maxOf { it.chapterNumber }
val shouldPromptTrackingUpdate = tracks.any { track -> maxChapterNumber > track.lastChapterRead }
if (!shouldPromptTrackingUpdate) return@launchIO
if (autoTrackState == AutoTrackState.ALWAYS) {
trackChapter.await(context, mangaId, maxChapterNumber)
withUIContext {
context.toast(context.stringResource(MR.strings.trackers_updated_summary, maxChapterNumber.toInt()))
}
return@launchIO
}
val result = snackbarHostState.showSnackbar(
message = context.stringResource(MR.strings.confirm_tracker_update, maxChapterNumber.toInt()),
@@ -824,7 +824,11 @@ private data class TrackerRemoveScreen(
fun deleteMangaFromService() {
screenModelScope.launchNonCancellable {
(tracker as DeletableTracker).delete(track)
try {
(tracker as DeletableTracker).delete(track)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to delete entry from service" }
}
}
}
@@ -42,7 +42,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object MoreTab : Tab {
data object MoreTab : Tab {
override val options: TabOptions
@Composable
@@ -478,7 +478,7 @@ class ReaderActivity : BaseActivity() {
enabledPrevious = state.viewerChapters?.prevChapter != null,
currentPage = state.currentPage,
totalPages = state.totalPages,
onSliderValueChange = {
onPageIndexChange = {
isScrollingThroughPages = true
moveToPageIndex(it)
},
@@ -700,7 +700,7 @@ class ReaderViewModel @JvmOverloads constructor(
// SY <--
readerChapter.chapter.read = true
// SY -->
if (readerChapter.chapter.chapter_number > 0 && readerPreferences.markReadDupe().get()) {
if (readerChapter.chapter.chapter_number >= 0 && readerPreferences.markReadDupe().get()) {
getChaptersByMangaId.await(manga!!.id).sortedByDescending { it.sourceOrder }
.filter {
it.id != readerChapter.chapter.id &&
@@ -36,10 +36,10 @@ import com.github.chrisbanes.photoview.PhotoView
import eu.kanade.tachiyomi.data.coil.cropBorders
import eu.kanade.tachiyomi.data.coil.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil
/**
* A wrapper view for showing page image.
@@ -116,21 +116,22 @@ open class ReaderPageImageView @JvmOverloads constructor(
}
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
val config = config
if (config != null &&
config!!.landscapeZoom &&
config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
config.landscapeZoom &&
config.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
sWidth > sHeight &&
scale == minScale
) {
handler?.postDelayed(500) {
val point = when (config!!.zoomStartPosition) {
val point = when (config.zoomStartPosition) {
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
ZoomStartPosition.CENTER -> center
}
val targetScale = height.toFloat() / sHeight.toFloat()
animateScaleAndCenter(targetScale, point)!!
(animateScaleAndCenter(targetScale, point) ?: return@postDelayed)
.withDuration(500)
.withEasing(EASE_IN_OUT_QUAD)
.withInterruptible(true)
@@ -232,7 +233,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} else {
SubsamplingScaleImageView(context)
}.apply {
setMaxTileSize(GLUtil.maxTextureSize)
setMaxTileSize(ImageUtil.hardwareBitmapThreshold)
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
setMinimumTileDpi(180)
@@ -287,35 +288,44 @@ open class ReaderPageImageView @JvmOverloads constructor(
},
)
if (isWebtoon) {
val request = ImageRequest.Builder(context)
.data(data)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val image = result as BitmapImage
setImage(ImageSource.bitmap(image.bitmap))
isVisible = true
},
onError = {
this@ReaderPageImageView.onImageLoadError()
},
)
.size(ViewSizeResolver(this@ReaderPageImageView))
.precision(Precision.INEXACT)
.cropBorders(config.cropBorders)
.customDecoder(true)
.crossfade(false)
.build()
context.imageLoader.enqueue(request)
} else {
when (data) {
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream()))
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
when (data) {
is BitmapDrawable -> {
setImage(ImageSource.bitmap(data.bitmap))
isVisible = true
}
is BufferedSource -> {
if (!isWebtoon) {
setHardwareConfig(ImageUtil.canUseHardwareBitmap(data))
setImage(ImageSource.inputStream(data.inputStream()))
isVisible = true
return@apply
}
ImageRequest.Builder(context)
.data(data)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val image = result as BitmapImage
setImage(ImageSource.bitmap(image.bitmap))
isVisible = true
},
onError = {
onImageLoadError()
},
)
.size(ViewSizeResolver(this@ReaderPageImageView))
.precision(Precision.INEXACT)
.cropBorders(config.cropBorders)
.customDecoder(true)
.crossfade(false)
.build()
.let(context.imageLoader::enqueue)
}
else -> {
throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
}
isVisible = true
}
}
@@ -353,8 +353,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
else -> oldCurrent?.first ?: return
}
val index = when (newPage) {
is ChapterTransition -> {
val index = when {
newPage is ChapterTransition && joinedItems.none { it.first == newPage || it.second == newPage } -> {
val filteredPages = joinedItems.filter {
it.first is ReaderPage &&
(it.first as ReaderPage).chapter == newPage.to
@@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.ui.stats
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastMapNotNull
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.fastCountNot
import eu.kanade.core.util.fastDistinctBy
import eu.kanade.core.util.fastFilter
import eu.kanade.core.util.fastFilterNot
import eu.kanade.core.util.fastMapNotNull
import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.tachiyomi.data.download.DownloadManager
@@ -37,7 +37,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object UpdatesTab : Tab {
data object UpdatesTab : Tab {
override val options: TabOptions
@Composable
@@ -15,6 +15,7 @@ import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
@@ -107,9 +108,13 @@ fun Context.createFileInCacheDir(name: String): File {
fun Context.createReaderThemeContext(): Context {
val preferences = Injekt.get<UiPreferences>()
val readerPreferences = Injekt.get<ReaderPreferences>()
val themeMode = preferences.themeMode().get()
val isDarkBackground = when (readerPreferences.readerTheme().get()) {
1, 2 -> true // Black, Gray
3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default
3 -> when (themeMode) { // Automatic bg uses activity background by default
ThemeMode.SYSTEM -> applicationContext.isNightMode()
else -> themeMode == ThemeMode.DARK
}
else -> false // White
}
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
@@ -119,4 +119,10 @@ data class DummyTracker(
track: eu.kanade.tachiyomi.data.database.models.Track,
epochMillis: Long,
) = Unit
override suspend fun getMangaMetadata(
track: tachiyomi.domain.track.model.Track,
): eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata = eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata(
0, "test", "test", "test", "test", "test",
)
}
@@ -54,7 +54,7 @@ class EHentaiUpdateHelper(context: Context) {
suspend fun findAcceptedRootAndDiscardOthers(
sourceId: Long,
chapters: List<Chapter>,
): Triple<ChapterChain, List<ChapterChain>, Boolean> {
): Triple<ChapterChain, List<ChapterChain>, List<Chapter>> {
// Find other chains
val chains = chapters
.flatMap { chapter ->
@@ -149,7 +149,7 @@ class EHentaiUpdateHelper(context: Context) {
setMangaCategories.await(it.manga.id, newCategories)
}
Triple(newAccepted, toDiscard, new)
Triple(newAccepted, toDiscard, newChapters)
} else {
/*val notNeeded = chains.filter { it.manga.id != accepted.manga.id }
val (newChapters, new) = getChapterList(accepted, notNeeded, chainsAsChapters)
@@ -158,7 +158,7 @@ class EHentaiUpdateHelper(context: Context) {
// Insert new chapters for accepted manga
db.insertChapters(newAccepted.chapters).await()*/
Triple(accepted, emptyList(), false)
Triple(accepted, emptyList(), emptyList())
}
}
@@ -0,0 +1,109 @@
package exh.eh
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.math.RoundingMode
import java.text.NumberFormat
class EHentaiUpdateNotifier(private val context: Context) {
private val securityPreferences: SecurityPreferences by injectLazy()
private val percentFormatter = NumberFormat.getPercentInstance().apply {
roundingMode = RoundingMode.DOWN
maximumFractionDigits = 0
}
/**
* Bitmap of the app for notifications.
*/
private val notificationBitmap by lazy {
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
}
/**
* Cached progress notification to avoid creating a lot.
*/
val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_EHENTAI) {
setContentTitle(context.stringResource(MR.strings.app_name))
setSmallIcon(R.drawable.ic_refresh_24dp)
setLargeIcon(notificationBitmap)
setOngoing(true)
setOnlyAlertOnce(true)
}
}
/**
* Shows the notification containing the currently updating manga and the progress.
*
* @param manga the manga that are being updated.
* @param current the current progress.
* @param total the total progress.
*/
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
progressNotificationBuilder
.setContentTitle(
context.stringResource(
MR.strings.notification_updating_progress,
percentFormatter.format(current.toFloat() / total),
),
)
if (!securityPreferences.hideNotificationContent().get()) {
val updatingText = manga.title.chop(40)
progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
}
context.notify(
Notifications.ID_EHENTAI_PROGRESS,
progressNotificationBuilder
.setProgress(total, current, false)
.build(),
)
}
/**
* Shows notification containing update entries that failed with action to open full log.
*
* @param failed Number of entries that failed to update.
* @param uri Uri for error log file containing all titles that failed.
*/
fun showUpdateErrorNotification(failed: Int, uri: Uri) {
if (failed == 0) {
return
}
context.notify(
Notifications.ID_EHENTAI_ERROR,
Notifications.CHANNEL_LIBRARY_EHENTAI,
) {
setContentTitle(context.stringResource(MR.strings.notification_update_error, failed))
setContentText(context.stringResource(MR.strings.action_show_errors))
setSmallIcon(R.drawable.ic_tachi)
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
}
}
/**
* Cancels the progress notification.
*/
fun cancelProgressNotification() {
context.cancelNotification(Notifications.ID_EHENTAI_PROGRESS)
}
}
@@ -1,11 +1,15 @@
package exh.eh
import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters
import com.elvishew.xlog.Logger
@@ -14,8 +18,10 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager
import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
@@ -27,9 +33,11 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
@@ -46,9 +54,10 @@ import kotlin.time.Duration.Companion.days
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val preferences: UnsortedPreferences by injectLazy()
private val libraryPreferences: LibraryPreferences by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val updateHelper: EHentaiUpdateHelper by injectLazy()
private val logger: Logger = xLog()
private val logger: Logger by lazy { xLog() }
private val updateManga: UpdateManga by injectLazy()
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
private val getChaptersByMangaId: GetChaptersByMangaId by injectLazy()
@@ -56,22 +65,38 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
private val insertFlatMetadata: InsertFlatMetadata by injectLazy()
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
private val updateNotifier by lazy { EHentaiUpdateNotifier(context) }
private val libraryUpdateNotifier by lazy { LibraryUpdateNotifier(context) }
override suspend fun doWork(): Result {
return try {
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
Result.success() // retry again later
} else {
setForegroundSafely()
startUpdating()
logger.d("Update job completed!")
Result.success()
}
} catch (e: Exception) {
Result.success() // retry again later
} finally {
updateNotifier.cancelProgressNotification()
}
}
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(
Notifications.ID_EHENTAI_PROGRESS,
updateNotifier.progressNotificationBuilder.build(),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
} else {
0
},
)
}
private suspend fun startUpdating() {
logger.d("Update job started!")
val startTime = System.currentTimeMillis()
@@ -138,6 +163,11 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
}
val (new, chapters) = try {
updateNotifier.showProgressNotification(
manga,
updatedThisIteration + failuresThisIteration,
mangaMetaToUpdateThisIter.size,
)
updateEntryAndGetChapters(manga)
} catch (e: GalleryNotUpdatedException) {
if (e.network) {
@@ -169,13 +199,15 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
}
// Find accepted root and discard others
val (acceptedRoot, discardedRoots, hasNew) =
val (acceptedRoot, discardedRoots, exhNew) =
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
(hasNew && updatedManga.none { it.first.id == acceptedRoot.manga.id })
) {
if (new.isNotEmpty() && manga.id == acceptedRoot.manga.id) {
libraryPreferences.newUpdatesCount().getAndSet { it + new.size }
updatedManga += acceptedRoot.manga to new.toTypedArray()
} else if (exhNew.isNotEmpty() && updatedManga.none { it.first.id == acceptedRoot.manga.id }) {
libraryPreferences.newUpdatesCount().getAndSet { it + exhNew.size }
updatedManga += acceptedRoot.manga to exhNew.toTypedArray()
}
modifiedThisIteration += acceptedRoot.manga.id
@@ -193,8 +225,9 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
),
)
updateNotifier.cancelProgressNotification()
if (updatedManga.isNotEmpty()) {
updateNotifier.showUpdateNotifications(updatedManga)
libraryUpdateNotifier.showUpdateNotifications(updatedManga)
}
}
}
@@ -237,7 +270,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
fun launchBackgroundTest(context: Context) {
context.workManager.enqueue(OneTimeWorkRequestBuilder<EHentaiUpdateWorker>().build())
context.workManager.enqueue(
OneTimeWorkRequestBuilder<EHentaiUpdateWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.addTag(TAG)
.build(),
)
}
fun scheduleBackground(context: Context, prefInterval: Int? = null, prefRestrictions: Set<String>? = null) {
@@ -92,7 +92,7 @@ class MemAutoFlushingLookupTable<T>(
val size = bb.getInt(4)
val strBArr = ByteArray(size)
if (!input.requireBytes(strBArr, size)) break
table.put(k, serializer.read(strBArr.toString(Charsets.UTF_8)))
table.put(k, serializer.read(strBArr.decodeToString()))
}
}
} catch (e: FileNotFoundException) {
@@ -131,7 +131,7 @@ class MemAutoFlushingLookupTable<T>(
try {
val out = fos.sink().buffer()
table.forEach { key, value ->
val v = serializer.write(value).toByteArray(Charsets.UTF_8)
val v = serializer.write(value).encodeToByteArray()
bb.putInt(0, key)
bb.putInt(4, v.size)
out.write(bb.array())
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+133
View File
@@ -7,22 +7,31 @@ object Cosplayer : TagList {
"cosplayer:abby dark star",
"cosplayer:adamae-dono",
"cosplayer:ai lei jiang",
"cosplayer:ai xi",
"cosplayer:aiga mizuki",
"cosplayer:aimy",
"cosplayer:aizawa ren",
"cosplayer:ajo cosplay",
"cosplayer:akane araragi",
"cosplayer:akari yamazaki",
"cosplayer:akemi101xoxo",
"cosplayer:akiba cute star",
"cosplayer:akitsu honoka",
"cosplayer:aleksandra bodler",
"cosplayer:aleksandra lerman",
"cosplayer:aleksis hitc",
"cosplayer:alexis lust",
"cosplayer:alice bong",
"cosplayer:alice cosplay",
"cosplayer:alice wonder",
"cosplayer:aliceholic",
"cosplayer:alicekyo",
"cosplayer:alin ma",
"cosplayer:alisa arkhangelskaya",
"cosplayer:alisa kiss",
"cosplayer:alisa valeeva",
"cosplayer:alodia gosiengfiao",
"cosplayer:alva velasco",
"cosplayer:alycia elvie",
"cosplayer:amanda welp",
"cosplayer:amber hallibell",
@@ -31,16 +40,20 @@ object Cosplayer : TagList {
"cosplayer:anabelle joy",
"cosplayer:anastasia komori",
"cosplayer:angelina preobrazhenskaya",
"cosplayer:aniela verbin",
"cosplayer:aninnyan",
"cosplayer:anizu chie",
"cosplayer:anna kuramoto",
"cosplayer:annie seixas",
"cosplayer:anxi",
"cosplayer:aokotan",
"cosplayer:arai yomi",
"cosplayer:araki mai",
"cosplayer:ari.anna",
"cosplayer:arisa mizuhara",
"cosplayer:arty huang",
"cosplayer:asakawa ran",
"cosplayer:asakura kotomi",
"cosplayer:asakura naho",
"cosplayer:ashiya noriko",
"cosplayer:astasiadream",
@@ -48,28 +61,39 @@ object Cosplayer : TagList {
"cosplayer:atina",
"cosplayer:atsuki",
"cosplayer:audalove",
"cosplayer:aveline tetsuya",
"cosplayer:ayaka matsunaga",
"cosplayer:bailey jay",
"cosplayer:banbanko",
"cosplayer:beibei kappu",
"cosplayer:bella bunbun",
"cosplayer:bili bili",
"cosplayer:bishoujomom",
"cosplayer:blacqkl",
"cosplayer:bloodraven",
"cosplayer:bobbi starr",
"cosplayer:bonn1ethebunny",
"cosplayer:bonnie bonkers",
"cosplayer:boople snoot",
"cosplayer:breesknees",
"cosplayer:bukkitbrown",
"cosplayer:bunny ayumi",
"cosplayer:carry key",
"cosplayer:chadkasa",
"cosplayer:charess",
"cosplayer:charles dera",
"cosplayer:chisai cosplay",
"cosplayer:chokoboll mukakoi.",
"cosplayer:christina volkova",
"cosplayer:chunmomo",
"cosplayer:cocopie",
"cosplayer:comonun",
"cosplayer:cristina luise",
"cosplayer:dakko ja rrs",
"cosplayer:dani doe",
"cosplayer:danielle beaulieu",
"cosplayer:danielle vedovelli",
"cosplayer:daria kravets",
"cosplayer:darling cute",
"cosplayer:dessyy",
"cosplayer:dillion harper",
@@ -78,39 +102,56 @@ object Cosplayer : TagList {
"cosplayer:donnaloli",
"cosplayer:eira wang",
"cosplayer:electricbum",
"cosplayer:elena lenina",
"cosplayer:elena yuna",
"cosplayer:elisa cattabriga",
"cosplayer:elyhria",
"cosplayer:erin eevee",
"cosplayer:erotic doki",
"cosplayer:eroticneko",
"cosplayer:esther rutkovskaya-tudor",
"cosplayer:eunji pyo",
"cosplayer:evawxsh",
"cosplayer:evenink",
"cosplayer:evie evangelion",
"cosplayer:ezy summers",
"cosplayer:fatiaoliii",
"cosplayer:fay sg",
"cosplayer:fe galvao",
"cosplayer:felvelial",
"cosplayer:feng jiang jiang",
"cosplayer:ferin feirn",
"cosplayer:feywilde",
"cosplayer:firtsbornunicorn",
"cosplayer:flora daria",
"cosplayer:fluffy nemu",
"cosplayer:franxcos",
"cosplayer:frauleinmilk",
"cosplayer:fubuki ami",
"cosplayer:fuji serika",
"cosplayer:futaba emiru",
"cosplayer:g44 wa kizutsukanai",
"cosplayer:garo dazay",
"cosplayer:generic egirl",
"cosplayer:genthehobbit",
"cosplayer:ghostly cosplay",
"cosplayer:giorgia vecchini",
"cosplayer:giulia valeriani",
"cosplayer:goth egg",
"cosplayer:gumiho hannya",
"cosplayer:guo chengzi",
"cosplayer:hakuhi kaede",
"cosplayer:hamasaki rio",
"cosplayer:han yeri",
"cosplayer:hanamura misaki",
"cosplayer:hane ame",
"cosplayer:harukaism",
"cosplayer:helly von valentine",
"cosplayer:hessakai",
"cosplayer:hey shika",
"cosplayer:higurashi rin",
"cosplayer:himeecosplay",
"cosplayer:hinatasama",
"cosplayer:hinaughtya",
"cosplayer:hiyo nishizuku",
"cosplayer:holly ava",
@@ -121,18 +162,30 @@ object Cosplayer : TagList {
"cosplayer:imokawa naoko",
"cosplayer:ino cosplay",
"cosplayer:iori moe",
"cosplayer:iri",
"cosplayer:ishikawa asami",
"cosplayer:jannet kat",
"cosplayer:jannet vinogradova",
"cosplayer:jasming chea",
"cosplayer:jaycee",
"cosplayer:jenezial",
"cosplayer:jenna lynn meowri",
"cosplayer:jenni kaellberg",
"cosplayer:jessica nigri",
"cosplayer:jessika jinx",
"cosplayer:jill",
"cosplayer:jinxie",
"cosplayer:jiuqujean",
"cosplayer:jiuyan",
"cosplayer:jiyun choi",
"cosplayer:joanna muller",
"cosplayer:julia larangeiras",
"cosplayer:julia shuenkova",
"cosplayer:jun ye tako",
"cosplayer:kae kaieda",
"cosplayer:kalinka fox",
"cosplayer:kamelya chan",
"cosplayer:kamijiri ichigo",
"cosplayer:kamui alice",
"cosplayer:kanda likitsangjaroen",
"cosplayer:kanda midori",
@@ -141,6 +194,7 @@ object Cosplayer : TagList {
"cosplayer:katiecakey",
"cosplayer:kay bear",
"cosplayer:kaya huang",
"cosplayer:kei shino",
"cosplayer:khainsaw",
"cosplayer:kibashi",
"cosplayer:kiera marie",
@@ -159,19 +213,27 @@ object Cosplayer : TagList {
"cosplayer:kotea dali",
"cosplayer:koyama rikako",
"cosplayer:kqueentsun",
"cosplayer:kurasaka kururu",
"cosplayer:kurumi.",
"cosplayer:kururugi aoi",
"cosplayer:kuuko w",
"cosplayer:kyonatix",
"cosplayer:lagertha",
"cosplayer:lea martinez",
"cosplayer:lelewu",
"cosplayer:lenfried",
"cosplayer:lewdoart",
"cosplayer:lex kuma",
"cosplayer:lightz",
"cosplayer:lili erlih",
"cosplayer:lilly rose",
"cosplayer:lilya victorovna",
"cosplayer:lilylit",
"cosplayer:lina erdel",
"cosplayer:ling li",
"cosplayer:linneas life",
"cosplayer:linzi jiang",
"cosplayer:little blue girl",
"cosplayer:lizyhsan",
"cosplayer:lmusicl",
"cosplayer:lovelyspacekitten",
@@ -180,11 +242,16 @@ object Cosplayer : TagList {
"cosplayer:mags.irl",
"cosplayer:mais conheyo",
"cosplayer:manyu hanausagi",
"cosplayer:mappy sanchez",
"cosplayer:marie-claude bourbonnais",
"cosplayer:mariigabii",
"cosplayer:masako yume",
"cosplayer:meagan vanburkleo",
"cosplayer:mei succubus",
"cosplayer:meikoui",
"cosplayer:meriol-chan",
"cosplayer:mianbing xianer",
"cosplayer:micro kitty",
"cosplayer:miih cosplay",
"cosplayer:miiya",
"cosplayer:mik allen",
@@ -192,18 +259,27 @@ object Cosplayer : TagList {
"cosplayer:milena hime",
"cosplayer:milky",
"cosplayer:mimi-chan",
"cosplayer:mimmi",
"cosplayer:mimo ningyo",
"cosplayer:minaduki miri",
"cosplayer:minato riku",
"cosplayer:minematsu rie",
"cosplayer:mingchudesu",
"cosplayer:mingming kizami",
"cosplayer:mingtao",
"cosplayer:minzy tea",
"cosplayer:miorin",
"cosplayer:misaco",
"cosplayer:mishka bear",
"cosplayer:missbrisolo",
"cosplayer:misty silver",
"cosplayer:mitsuki riyu",
"cosplayer:miura aika",
"cosplayer:miyoki",
"cosplayer:mizhimaoqiu",
"cosplayer:mizuki akira",
"cosplayer:mochichuu",
"cosplayer:mochimochi-nn",
"cosplayer:mochizuki eiko",
"cosplayer:mochizuki kanade",
"cosplayer:moiicos",
@@ -211,8 +287,11 @@ object Cosplayer : TagList {
"cosplayer:momoiro reku",
"cosplayer:momoko",
"cosplayer:momokun",
"cosplayer:momousagi mao",
"cosplayer:moody feet",
"cosplayer:morgana cosplay",
"cosplayer:mowky",
"cosplayer:moyu mommy",
"cosplayer:mozuku kimura",
"cosplayer:mu zhi ben lan",
"cosplayer:murasaki",
@@ -222,7 +301,11 @@ object Cosplayer : TagList {
"cosplayer:nadyasonika",
"cosplayer:nagisa",
"cosplayer:nan tao momoko",
"cosplayer:natalya ditrikh",
"cosplayer:natasha roik",
"cosplayer:nateephan thammasilbanyad",
"cosplayer:nawo019",
"cosplayer:nayfi bardales",
"cosplayer:nekob0icarti",
"cosplayer:neroko kaigan",
"cosplayer:niannian d",
@@ -235,58 +318,81 @@ object Cosplayer : TagList {
"cosplayer:nonbinate",
"cosplayer:nora fawn",
"cosplayer:nuko meguro",
"cosplayer:nuria gonzalez",
"cosplayer:nyako",
"cosplayer:nymph-princess",
"cosplayer:octokuro",
"cosplayer:odoru neko ningen",
"cosplayer:oharucosplay",
"cosplayer:oichi",
"cosplayer:okada yui",
"cosplayer:oki-cospi",
"cosplayer:olivia metric",
"cosplayer:olyashaa saxon",
"cosplayer:pattie cosplay",
"cosplayer:pattycake",
"cosplayer:peachtot",
"cosplayer:penkarui",
"cosplayer:penny walsh",
"cosplayer:pichapu",
"cosplayer:pokket",
"cosplayer:poon warunya",
"cosplayer:punk macarroni",
"cosplayer:purrblind",
"cosplayer:pushiku",
"cosplayer:qiqi nanazi",
"cosplayer:qiqi xiaojie",
"cosplayer:qiuhe keji",
"cosplayer:queenie",
"cosplayer:quist",
"cosplayer:rachel ravaged",
"cosplayer:rakuraku",
"cosplayer:ravvcoser",
"cosplayer:raynearts",
"cosplayer:rea kami",
"cosplayer:renee storm",
"cosplayer:rhylee passfield",
"cosplayer:ri care",
"cosplayer:riani haratina",
"cosplayer:rinami",
"cosplayer:ringo mitsuki",
"cosplayer:rinoa",
"cosplayer:rio-chan",
"cosplayer:rioko",
"cosplayer:rissoft",
"cosplayer:rocksy light",
"cosplayer:rolyatistaylor",
"cosplayer:rongrongzi",
"cosplayer:rusuwu",
"cosplayer:sachi budou",
"cosplayer:saiwari ph",
"cosplayer:saki kawanami",
"cosplayer:saku",
"cosplayer:sakura ema",
"cosplayer:sakurai",
"cosplayer:sakurai hinoki",
"cosplayer:samantha boon",
"cosplayer:sandykuroneko",
"cosplayer:saotome love",
"cosplayer:sara underwood",
"cosplayer:sarah quillian",
"cosplayer:sarawrcosplay",
"cosplayer:sasaki remi",
"cosplayer:sato yuri",
"cosplayer:savannah sixx",
"cosplayer:sawaka",
"cosplayer:scarlett afterdark",
"cosplayer:sean lawless",
"cosplayer:sei",
"cosplayer:seltin sweet",
"cosplayer:sena",
"cosplayer:sexy toys",
"cosplayer:sexyflowerwater",
"cosplayer:sharkparty",
"cosplayer:shermie",
"cosplayer:shibuya kaho",
"cosplayer:shimizu yuno",
"cosplayer:shinen",
"cosplayer:shiro kitsune",
"cosplayer:shiroluxx",
"cosplayer:siao ding",
@@ -295,14 +401,24 @@ object Cosplayer : TagList {
"cosplayer:soa lianna",
"cosplayer:son yeeun",
"cosplayer:sophie snomster",
"cosplayer:stanislava anushkina",
"cosplayer:starfmodel",
"cosplayer:stelar hoshi",
"cosplayer:sunny lin",
"cosplayer:sunny ray",
"cosplayer:sunnyvier",
"cosplayer:sunohara miki",
"cosplayer:sushiflavoredmilk",
"cosplayer:suspira grey",
"cosplayer:tachibana remika",
"cosplayer:tanaka hitomi",
"cosplayer:tanaka mana",
"cosplayer:tangtang",
"cosplayer:tanja kensinger",
"cosplayer:tao liang azhai",
"cosplayer:tara nicole azarian",
"cosplayer:tasha leigh",
"cosplayer:tatiana neva",
"cosplayer:tenleid",
"cosplayer:tenryu-0",
"cosplayer:tenshi myu.",
@@ -310,8 +426,10 @@ object Cosplayer : TagList {
"cosplayer:tiffany gordon",
"cosplayer:tiny asa",
"cosplayer:todopokie",
"cosplayer:tristan valdez",
"cosplayer:tsubaki zakuro",
"cosplayer:tsuki desu",
"cosplayer:tsuki miko",
"cosplayer:tsukimiya madoka",
"cosplayer:tsuyato",
"cosplayer:turkish chi-chi",
@@ -320,34 +438,49 @@ object Cosplayer : TagList {
"cosplayer:ukyuu nako",
"cosplayer:una cosplayer",
"cosplayer:uno megumi",
"cosplayer:ur senpai june",
"cosplayer:uru uruu",
"cosplayer:uta kohaku",
"cosplayer:valery himera",
"cosplayer:velvet",
"cosplayer:veroodle",
"cosplayer:vlada lutsak",
"cosplayer:wanco chan",
"cosplayer:whimpercat",
"cosplayer:wifey",
"cosplayer:wildhoney423",
"cosplayer:xansoon",
"cosplayer:xia xia zi",
"cosplayer:xiaoyao yaoyao",
"cosplayer:xiaoying shi zhi xiaomulong",
"cosplayer:xidaidai",
"cosplayer:xue qi-sama",
"cosplayer:yaki",
"cosplayer:yaokoututu",
"cosplayer:yaoyaoqwq",
"cosplayer:ying lili",
"cosplayer:yoko inui",
"cosplayer:yor succubus",
"cosplayer:yorkie w",
"cosplayer:youyou",
"cosplayer:yuki astra",
"cosplayer:yuki lefay",
"cosplayer:yuki teyi",
"cosplayer:yume",
"cosplayer:yummykimmy",
"cosplayer:yunie lannister",
"cosplayer:yunocos69",
"cosplayer:yurihime",
"cosplayer:yuumeilyn",
"cosplayer:yuyunte",
"cosplayer:yuzu chan",
"cosplayer:yuzuki",
"cosplayer:yuzukimiiu",
"cosplayer:yuzupyon",
"cosplayer:zara durose",
"cosplayer:zeico",
"cosplayer:zhenya zhuk",
"cosplayer:zhuimingyou",
"cosplayer:zyunka mukhina",
)
}
+8
View File
@@ -63,7 +63,9 @@ object Female : TagList {
"female:big vagina",
"female:bike shorts",
"female:bikini",
"female:bird girl",
"female:bisexual",
"female:bite mark",
"female:blackmail",
"female:blind",
"female:blindfold",
@@ -120,8 +122,10 @@ object Female : TagList {
"female:cockslapping",
"female:collar",
"female:condom",
"female:confinement",
"female:conjoined",
"female:coprophagia",
"female:corpse",
"female:corruption",
"female:corset",
"female:cosplaying",
@@ -218,6 +222,7 @@ object Female : TagList {
"female:frog",
"female:frog girl",
"female:frottage",
"female:full tour",
"female:full-packaged futanari",
"female:fundoshi",
"female:furry",
@@ -299,6 +304,7 @@ object Female : TagList {
"female:kindergarten uniform",
"female:kissing",
"female:kneepit sex",
"female:kodomo doushi",
"female:kunoichi",
"female:lab coat",
"female:lactation",
@@ -319,6 +325,7 @@ object Female : TagList {
"female:long tongue",
"female:low bestiality",
"female:low guro",
"female:low incest",
"female:low lolicon",
"female:low scat",
"female:low smegma",
@@ -369,6 +376,7 @@ object Female : TagList {
"female:muscle growth",
"female:mute",
"female:nakadashi",
"female:navel birth",
"female:navel fuck",
"female:nazi",
"female:necrophilia",
+119 -119
View File
@@ -15,6 +15,7 @@ object Group : TagList {
"group:008",
"group:0123456789",
"group:0909",
"group:1 equals 1ziz",
"group:1 slash 0 kansokujo",
"group:1-up",
"group:10 slash 19",
@@ -336,7 +337,6 @@ object Group : TagList {
"group:alice-do",
"group:alice.blood",
"group:alicegarden",
"group:aliceholic",
"group:alicemirror",
"group:alices house",
"group:alicesoft",
@@ -359,6 +359,7 @@ object Group : TagList {
"group:alz-hammer",
"group:am colon tiger",
"group:am400",
"group:am644",
"group:amaama-tei",
"group:amaembo",
"group:amagasa cycle",
@@ -380,6 +381,7 @@ object Group : TagList {
"group:amanogawa tsuushin",
"group:amapoteya",
"group:amarini senpaku",
"group:amaterasu tsukikage",
"group:amatoro bow",
"group:amatosui",
"group:amatouenpitsukezuri",
@@ -539,6 +541,7 @@ object Group : TagList {
"group:arkham products team ankoku baitai",
"group:armadillo",
"group:armanium",
"group:arnest room",
"group:aroimark",
"group:aroma gaeru",
"group:arsenothelus",
@@ -557,6 +560,7 @@ object Group : TagList {
"group:arukomu",
"group:aruku denpatou no kai",
"group:arumike",
"group:arumirua",
"group:aruto-ya",
"group:arysuivery",
"group:asa made go-ya",
@@ -612,6 +616,7 @@ object Group : TagList {
"group:at mztm",
"group:at no 464",
"group:at oz",
"group:at szkn",
"group:atara shindou",
"group:ataraxia",
"group:atelier d",
@@ -709,6 +714,7 @@ object Group : TagList {
"group:bakuhatsu brs.",
"group:bakunyu fullnerson",
"group:bakuretsu fusen",
"group:bakushu koujou",
"group:bakusou special",
"group:balgus rec",
"group:balklash.",
@@ -746,6 +752,7 @@ object Group : TagList {
"group:bbb-extra",
"group:bbuttondash",
"group:beaf emotion",
"group:bear valley",
"group:beart",
"group:beat-pop",
"group:beauty salon b and s",
@@ -764,6 +771,7 @@ object Group : TagList {
"group:beni namazu dan",
"group:benichigaya",
"group:beniiro kaitenkikou",
"group:benimaru suisan",
"group:benimomo dou",
"group:benisuzumedo",
"group:beniya",
@@ -804,6 +812,7 @@ object Group : TagList {
"group:biroon jr.",
"group:biruban",
"group:bisaid label",
"group:bishamon.",
"group:bishou neko",
"group:bishoujo production",
"group:bisketty",
@@ -1083,6 +1092,7 @@ object Group : TagList {
"group:chimamire yashiki",
"group:chimatsuriya honpo",
"group:chimchimteam",
"group:chimeishou",
"group:chimere marie",
"group:chin soft",
"group:chinasanchi",
@@ -1243,6 +1253,7 @@ object Group : TagList {
"group:coonelius",
"group:copo deluxe",
"group:coppo-otome",
"group:coscoteikoku",
"group:cosmic-3d-angels",
"group:cosplay kissa nyan nyan",
"group:cosplaydeviants",
@@ -1301,6 +1312,7 @@ object Group : TagList {
"group:d-ten",
"group:d.d.d.b.",
"group:d.n.a.lab.",
"group:d.o.",
"group:d2",
"group:da hootch",
"group:da pomb no tokoro",
@@ -1413,6 +1425,7 @@ object Group : TagList {
"group:deucesworld",
"group:dewdrop",
"group:dex plus",
"group:dez climax",
"group:dhr-ken",
"group:diablo",
"group:dicpic studio",
@@ -1424,6 +1437,7 @@ object Group : TagList {
"group:digital graffiti",
"group:digital lover",
"group:digital tambourine",
"group:dingiruutoushi",
"group:diogenes club",
"group:dioxin",
"group:dirty",
@@ -1445,12 +1459,14 @@ object Group : TagList {
"group:doing crew",
"group:doisakaken",
"group:dojin otome",
"group:dojiro books",
"group:dokkoi-tori gomoku",
"group:doku alice",
"group:doku doku kinoko",
"group:doku pepper",
"group:doku usagi tai",
"group:dokudenpa jushintei",
"group:dokudoku ryouki garou",
"group:dokugiri",
"group:dokukinokosha",
"group:dokupan koubou",
@@ -1461,11 +1477,13 @@ object Group : TagList {
"group:donaora",
"group:donburi beya",
"group:dondondon",
"group:dongurineko",
"group:dont understand",
"group:doomcomic",
"group:dopyunger oukoku",
"group:dorepooru",
"group:doro-coppelia",
"group:dorokuma kumaya",
"group:doronuma bunshitsu",
"group:doronuma kyoudai",
"group:doropanda tours",
@@ -1489,8 +1507,10 @@ object Group : TagList {
"group:doujin mukashibanashi",
"group:douke romance",
"group:doumo sumimasen",
"group:dounimo naranai nou",
"group:dourakuya honpo",
"group:doushin chaya",
"group:doushoku",
"group:douwa-kensetsu",
"group:doyondo.",
"group:dr.vermilion",
@@ -1533,6 +1553,7 @@ object Group : TagList {
"group:earthlyparadise",
"group:easymode",
"group:ebimayo",
"group:ebiten kaido",
"group:ecchi na taikendan kokuhaku toukou otoko jyuku",
"group:ecchuu douga honpo",
"group:eclair ringo tea",
@@ -1622,6 +1643,7 @@ object Group : TagList {
"group:etopi kan",
"group:eucalyptus house",
"group:euereuphorie",
"group:euglena factory",
"group:eunospress",
"group:everyday milk challenge",
"group:evil aratame baroque store",
@@ -1647,6 +1669,7 @@ object Group : TagList {
"group:fakereal",
"group:fakers manual",
"group:fakestar",
"group:falcon115",
"group:fallinmoon",
"group:famous comics",
"group:famous toons facial",
@@ -1730,6 +1753,7 @@ object Group : TagList {
"group:frill frill",
"group:frontwing",
"group:fruitsjam",
"group:fu rairyuu",
"group:fuantei",
"group:fudeoki seisakujo",
"group:fuegerstef",
@@ -1778,6 +1802,7 @@ object Group : TagList {
"group:fururi.",
"group:furuya",
"group:fuseimyaku",
"group:fusha fusha kingdom",
"group:fushichou no yoake",
"group:fushinsya guilty",
"group:fushizen doubutsu hogodantai",
@@ -1829,10 +1854,12 @@ object Group : TagList {
"group:gamera 8th army",
"group:gamma menia",
"group:gammaedge",
"group:ganbaru dou",
"group:gang bang comix",
"group:ganmo-no-oyatsu",
"group:ganryuu island",
"group:ganso sonodaya",
"group:gaoookyouryu",
"group:gara ayuri nisshi",
"group:garage-talk",
"group:garahadoh",
@@ -1849,6 +1876,7 @@ object Group : TagList {
"group:gas ketsu jinsei",
"group:gasshuukoku netamekoru",
"group:gate of xiii",
"group:gatekeeper",
"group:gaton.",
"group:gavial no sumika",
"group:gd-mechano",
@@ -1870,12 +1898,14 @@ object Group : TagList {
"group:general bacchus",
"group:genesys",
"group:genki no mizu no wakutokoro",
"group:genkin-dou souhonpo",
"group:genmaiya",
"group:genocidekiss",
"group:gensancha",
"group:gensou graphics",
"group:gensou kuukan",
"group:gensou stlavus",
"group:gensou yakai",
"group:gensyokuhakoniwa",
"group:genwakukinema",
"group:geregere negro",
@@ -1897,12 +1927,14 @@ object Group : TagList {
"group:giantessfan",
"group:giftbell",
"group:giftkuchen",
"group:giga omaru",
"group:gigameka",
"group:gikogakodo",
"group:gin eiji",
"group:gin no hoshitei",
"group:gin penguin",
"group:gin-ion",
"group:ginga no arakuremon",
"group:ginga no himitu kichi",
"group:ginga-ryusei",
"group:giniro noel",
@@ -1960,6 +1992,7 @@ object Group : TagList {
"group:gouhouwakan",
"group:gouriki hyakkaten",
"group:gouten doujou",
"group:gozen 4-ji one call",
"group:gozen niji no ushigaeru",
"group:gpen",
"group:gpx",
@@ -1969,6 +2002,9 @@ object Group : TagList {
"group:gravidan",
"group:great acta",
"group:great canyon",
)
override fun getTags2(): List<String> = listOf(
"group:great dadan",
"group:greatest18club",
"group:greatmanjuu",
@@ -2002,9 +2038,6 @@ object Group : TagList {
"group:gyara cter",
"group:gyaran rose",
"group:gyarandou",
)
override fun getTags2(): List<String> = listOf(
"group:gyogyou rengou",
"group:gyokasuisin",
"group:gyokotsu kouzou",
@@ -2018,6 +2051,7 @@ object Group : TagList {
"group:h na hon. ya san.",
"group:h plus",
"group:h senshokutai",
"group:h sparkle",
"group:h-m",
"group:h-na-ojisan",
"group:h-sys.",
@@ -2208,6 +2242,7 @@ object Group : TagList {
"group:hellfragrance",
"group:hellter skelter",
"group:helmet ga naosemasen",
"group:henachoko-domei",
"group:hengen monogatari",
"group:henntai-shinshi",
"group:henreikai",
@@ -2223,6 +2258,7 @@ object Group : TagList {
"group:hero hero tei",
"group:hero oukoku",
"group:heroes factory",
"group:herunian zokusei",
"group:heshi factory",
"group:heta no yoko zuki",
"group:hetalearts",
@@ -2250,6 +2286,7 @@ object Group : TagList {
"group:high heel syndrome",
"group:high risk revolution",
"group:high-octane",
"group:high-rised fossil garden",
"group:high-soft",
"group:high-spirit",
"group:highway-senmu",
@@ -2315,7 +2352,7 @@ object Group : TagList {
"group:hitsuji-1ban-shibori",
"group:hitsujin toko",
"group:hitujinoki",
"group:hiyashi chuuka hajimemashita",
"group:hiyashi chuuka owarimashita",
"group:hiyoko no gekijoh",
"group:hiyosanchi",
"group:hizadati zekkouchou",
@@ -2331,6 +2368,7 @@ object Group : TagList {
"group:hokoushayou shingou",
"group:hokuroza",
"group:holiday school",
"group:holy up",
"group:home not found",
"group:homepie koubou",
"group:homerun chaya",
@@ -2348,6 +2386,7 @@ object Group : TagList {
"group:honeypie",
"group:hong kong dou",
"group:honnokimochiya",
"group:honpo kes",
"group:hontoinu",
"group:hook",
"group:hooliganism",
@@ -2379,6 +2418,7 @@ object Group : TagList {
"group:hotori bocchi",
"group:hotpink",
"group:hougakuya",
"group:houjou-kun mania",
"group:houkago inokorigumi",
"group:houkago paradise",
"group:houkaiseki.",
@@ -2424,6 +2464,7 @@ object Group : TagList {
"group:ice to choco",
"group:ice-place",
"group:ichachi",
"group:ichi dollar kouka",
"group:ichi-kan",
"group:ichigiteishi",
"group:ichigo maririn",
@@ -2507,6 +2548,7 @@ object Group : TagList {
"group:intermikan",
"group:interracial-comics",
"group:intoku.info",
"group:inuchan equals land",
"group:inudrill.",
"group:inukamedou",
"group:inukichi club",
@@ -2587,12 +2629,15 @@ object Group : TagList {
"group:jet-black baselarde",
"group:jewel box",
"group:jhk",
"group:jibaku mecha",
"group:jibaku-system",
"group:jidaraku risutorante",
"group:jido-hikki",
"group:jigen no wataridori",
"group:jiggly girls",
"group:jigizagi",
"group:jigoku no cakeya-san",
"group:jigoku no monban",
"group:jigoku potion",
"group:jigyaku jihen",
"group:jikan-ya",
@@ -2697,6 +2742,7 @@ object Group : TagList {
"group:kairoudou",
"group:kairyuu",
"group:kaisanbou",
"group:kaitaiya",
"group:kaitatuku",
"group:kaiteisinden",
"group:kaiten sommelier",
@@ -2824,6 +2870,7 @@ object Group : TagList {
"group:keiyou tsudanuma juku",
"group:kemao coopercent",
"group:kemokomoya",
"group:kemomimi-chan ya",
"group:kemominnosuke",
"group:kemono ekaki no kousoku 2",
"group:kemono masshigura.",
@@ -2849,6 +2896,7 @@ object Group : TagList {
"group:khaos distance",
"group:khaos wind",
"group:kharisma jati",
"group:khpn style",
"group:kibawomuku",
"group:kichiku bansankai",
"group:kichiku koubou",
@@ -2959,6 +3007,7 @@ object Group : TagList {
"group:koge croquette",
"group:kogemaru tsuushinkyoku",
"group:kogitune",
"group:koh no atelier",
"group:kohagura.",
"group:kohau no heya",
"group:kohitsujitei",
@@ -3010,6 +3059,7 @@ object Group : TagList {
"group:komorikiri.",
"group:komugiko 100 percent",
"group:kon no pencase",
"group:konekoconnection",
"group:konekopan",
"group:konekopunch",
"group:kongou rikisi",
@@ -3057,6 +3107,7 @@ object Group : TagList {
"group:kousaien",
"group:kousoku gurihari-tei",
"group:kousoku purin",
"group:koutatsu dennou koushi",
"group:kouzaka-san to makino jimusho",
"group:kouzu shoukai",
"group:kouzuya",
@@ -3087,12 +3138,14 @@ object Group : TagList {
"group:kumo to koumori",
"group:kumohitode of world",
"group:kumonosu",
"group:kuni gamma",
"group:kunkakunka teikoku",
"group:kunon",
"group:kurage kyoudai",
"group:kurahashi shoin",
"group:kurai mori no soko de",
"group:kurakura-honey",
"group:kurasan",
"group:kuraudo.",
"group:kureboti ufo",
"group:kurige wagyuu",
@@ -3162,6 +3215,7 @@ object Group : TagList {
"group:kuukiisu",
"group:kuuronziyou",
"group:kuusou idol labo bellberry",
"group:kuusou kouko gakkai",
"group:kuusou switch",
"group:kuzuryuu",
"group:kyakuniku kanzume",
@@ -3178,6 +3232,7 @@ object Group : TagList {
"group:kyouken diners",
"group:kyouki na shiunten",
"group:kyoumo spaghe",
"group:kyourakuen",
"group:kyoushuugata",
"group:kyuu no mon",
"group:kyuu tekki jidai",
@@ -3246,6 +3301,7 @@ object Group : TagList {
"group:lily heart",
"group:lily lily rose",
"group:lily-put",
"group:lime green",
"group:limit break",
"group:limit max",
"group:limit over",
@@ -3260,6 +3316,7 @@ object Group : TagList {
"group:little mantis",
"group:little princess",
"group:littlehopper",
"group:littlepool.",
"group:littletail",
"group:live house",
"group:lo likyo new",
@@ -3274,6 +3331,7 @@ object Group : TagList {
"group:lolipop complete",
"group:lolitachannel",
"group:longhorntrain",
"group:longlong de cangku",
"group:looptheloop",
"group:lopet dan",
"group:lostscript",
@@ -3284,6 +3342,7 @@ object Group : TagList {
"group:love kitten",
"group:love kyun maiden",
"group:love lily",
"group:love love craft",
"group:love nyanko",
"group:love scythe",
"group:love shine",
@@ -3325,6 +3384,7 @@ object Group : TagList {
"group:m kichibeya",
"group:m plus dilore",
"group:m slash k club",
"group:m-family",
"group:m-i-p",
"group:m-keifu",
"group:m-koujou",
@@ -3364,6 +3424,7 @@ object Group : TagList {
"group:magono-tei",
"group:magudara kaihou doumei",
"group:maguro bokujo",
"group:maguro fiction",
"group:maguro koubou",
"group:mahiru no tsuki",
"group:mahiru nosora",
@@ -3417,9 +3478,11 @@ object Group : TagList {
"group:manjuu x",
"group:manles laboratory",
"group:manshin soui",
"group:mantohihi atoz",
"group:many menu",
"group:maokonzu",
"group:maple-go",
"group:mappa namatta",
"group:mara apocalypse",
"group:marashion",
"group:marble kid",
@@ -3431,6 +3494,7 @@ object Group : TagList {
"group:marge-loop",
"group:maria system00",
"group:mariana kaikou kikaku",
"group:marimo-ya",
"group:marinesapphire",
"group:marionette soukou ryouhei",
"group:marireimari inochi",
@@ -3651,6 +3715,7 @@ object Group : TagList {
"group:misaki shoujokei.",
"group:misakix megamix",
"group:misin koujou",
"group:misobolo dou",
"group:misonodenpatou",
"group:misoyahonpo",
"group:misssail",
@@ -3697,6 +3762,7 @@ object Group : TagList {
"group:mocha plus ccc",
"group:mocha2popcorn",
"group:mochi hasamiuchi da",
"group:mochi mochi bomb",
"group:mochi usagi",
"group:mochi-ya",
"group:mochimoonya",
@@ -3716,8 +3782,10 @@ object Group : TagList {
"group:mogura-dou",
"group:mogyutto cheesecake",
"group:mojibone",
"group:mokkindo",
"group:mokkorihan",
"group:mokkoubondobu",
"group:mokkuafunfun",
"group:mokomaru suisan",
"group:mokugyuutan",
"group:mokusa",
@@ -3738,6 +3806,7 @@ object Group : TagList {
"group:momoiro funenmono",
"group:momoiro mimic",
"group:momoiro tanzaku",
"group:momoiro zundoko",
"group:momoiro-gekijyou",
"group:momoiro-rip",
"group:momokamasu",
@@ -3787,8 +3856,8 @@ object Group : TagList {
"group:morning star",
"group:morning tea.",
"group:morningmoon merchandising products",
"group:morohei-ya",
"group:moroheiya break",
"group:moroheiya no agata",
"group:morokochiffon cake",
"group:morokosheet",
"group:moromi-ya",
@@ -3802,6 +3871,7 @@ object Group : TagList {
"group:mosoya",
"group:mosquitone.",
"group:motchie kingdom",
"group:motemote life",
"group:mothman",
"group:mou sukoshi hidari e",
"group:mouko mouretsu hasai dan",
@@ -3864,6 +3934,7 @@ object Group : TagList {
"group:mukokoro no kumo",
"group:mukousharan",
"group:mukuchi na hakoniwa",
"group:mukyou no utopia",
"group:mukyuu dynamic",
"group:mulberry",
"group:multi media studio l.o.e.r.",
@@ -3934,6 +4005,9 @@ object Group : TagList {
"group:nagomi-chaya",
"group:nagomisui",
"group:nagomiyasan",
)
override fun getTags3(): List<String> = listOf(
"group:nagucha.",
"group:nagumo curry-bu",
"group:nagumoya",
@@ -3957,9 +4031,11 @@ object Group : TagList {
"group:namahage-dou",
"group:namahamu sando",
"group:namaikichibi",
"group:namaitati teishoku",
"group:namakemono sou",
"group:namakura dou",
"group:namanecotei",
"group:namaniku aikoukai",
"group:namasute koubou",
"group:namazuchaya",
"group:nameco-soup",
@@ -4005,9 +4081,6 @@ object Group : TagList {
"group:nanto wachou ken",
"group:nantoka ikitemasu",
"group:nantoka suroun",
)
override fun getTags3(): List<String> = listOf(
"group:nappy",
"group:naraduke biyori",
"group:naragyogyo kumiai",
@@ -4018,6 +4091,7 @@ object Group : TagList {
"group:nasi-pasuya",
"group:nasu no mono",
"group:nasuan",
"group:natadecoco company",
"group:natakuga-yuku",
"group:natorina dou",
"group:natrinium",
@@ -4120,8 +4194,10 @@ object Group : TagList {
"group:nendo ningyo",
"group:nengaranenjuu",
"group:neo maiden",
"group:neoniro",
"group:neruneru",
"group:netemo sametemo",
"group:netorareru tamashigi no hitoshizuku",
"group:netsuzukeru ishiryoku",
"group:nettaigyo club",
"group:neutron city",
@@ -4152,6 +4228,7 @@ object Group : TagList {
"group:nighthawk",
"group:nightmare software",
"group:nigimitama no ya",
"group:nihao series",
"group:nihatsu shika ataranai",
"group:nihon dandy",
"group:nihon denga senmon gakkou",
@@ -4168,6 +4245,7 @@ object Group : TagList {
"group:nijiiro zakura",
"group:nijinoren",
"group:nijiyome",
"group:niko-chan planning",
"group:nikomark",
"group:nikomi omurice",
"group:nikoniko company",
@@ -4323,6 +4401,7 @@ object Group : TagList {
"group:obsession.",
"group:ochanomizu mokujinkai",
"group:ochawan",
"group:ochi mono kanzume",
"group:ochikochitei",
"group:ochikonium",
"group:ochimusha.",
@@ -4339,6 +4418,7 @@ object Group : TagList {
"group:office amagasa",
"group:ofuton de suyaa",
"group:ogeretsu-dan",
"group:ogon shinshi club",
"group:ogre no heya",
"group:oh-banzai studio",
"group:ohagi.",
@@ -4413,6 +4493,7 @@ object Group : TagList {
"group:onsen",
"group:oobari doujou",
"group:ookami no o",
"group:ookina gomibako",
"group:ookina kodomo no omocha bako",
"group:operating room",
"group:oppawi shitei",
@@ -4489,8 +4570,10 @@ object Group : TagList {
"group:owlpop",
"group:oxide.lab",
"group:oyako donburi tei",
"group:oyasumi kobe gyuu",
"group:ozawa kobo",
"group:p kikaku",
"group:p shoukai",
"group:p-collection",
"group:p-forest",
"group:p-pooh",
@@ -4632,6 +4715,7 @@ object Group : TagList {
"group:pink-noise",
"group:pinkbell software",
"group:pinkharlem",
"group:pinki wana",
"group:pinktips.info",
"group:pinkysoft",
"group:pinpoint",
@@ -4689,6 +4773,7 @@ object Group : TagList {
"group:ponchees kari",
"group:ponkotuna potunoya",
"group:ponpon-black",
"group:ponponponpo",
"group:pons lab",
"group:pony farm",
"group:ponyfarm",
@@ -4864,6 +4949,7 @@ object Group : TagList {
"group:remora works",
"group:ren-kon-an",
"group:renai mangaka",
"group:rengaworks",
"group:renge-dou",
"group:rengeza",
"group:renglet",
@@ -4967,6 +5053,7 @@ object Group : TagList {
"group:ruku-pusyu",
"group:running girl",
"group:runrun soft",
"group:ruri-iro special room",
"group:ruriiro honpo",
"group:rurirara star",
"group:ruruna and nimunimu",
@@ -5053,6 +5140,7 @@ object Group : TagList {
"group:sakuraproject",
"group:sakurasaku",
"group:sakurasaku koubou",
"group:sakurayakan no hanare",
"group:sakuryu",
"group:sakusaku kangen noushuku",
"group:sakusakusakuchan",
@@ -5241,6 +5329,7 @@ object Group : TagList {
"group:shiina club",
"group:shiinotomoshibitake",
"group:shiitake nouen",
"group:shijimi wari ningyou",
"group:shijou misaki",
"group:shikakui tori",
"group:shiki be careful",
@@ -5274,6 +5363,7 @@ object Group : TagList {
"group:shingeki no nameko",
"group:shining star",
"group:shining star lilys",
"group:shinise ikedaya",
"group:shinkirou akatsuki",
"group:shinkuraiku",
"group:shinnihon pepsitou",
@@ -5338,8 +5428,11 @@ object Group : TagList {
"group:short kami",
"group:shortcut koubou",
"group:shosekido",
"group:shota mangaya-san",
"group:shotacon-do",
"group:shouchuu mac",
"group:shoudansha",
"group:shougusha",
"group:shoujo 2-jou",
"group:shoujo gesshoku",
"group:shoujo kousaku",
@@ -5356,6 +5449,7 @@ object Group : TagList {
"group:showa saishuu sensen",
"group:showa shojo",
"group:showano",
"group:shubi-ryoku 4man",
"group:shudoushiki denki jidousha",
"group:shumi eshi",
"group:shumisen jiru",
@@ -5384,6 +5478,7 @@ object Group : TagList {
"group:silky to yukai na nakama-tachi",
"group:silkys plus wasabi",
"group:silmaril",
"group:silver rice",
"group:silver ring",
"group:silver-kingdom",
"group:silver-rx",
@@ -5407,6 +5502,7 @@ object Group : TagList {
"group:sirouto plan",
"group:sirubedou",
"group:sisinabeya",
"group:sistny and anasis",
"group:sittori oblaat",
"group:sketch-book",
"group:skid-mark",
@@ -5512,6 +5608,7 @@ object Group : TagList {
"group:spicia",
"group:spig at",
"group:spiral brain",
"group:spiritguide",
"group:spock-san",
"group:sponge empire",
"group:sql",
@@ -5559,6 +5656,7 @@ object Group : TagList {
"group:studio c-take",
"group:studio cardamom",
"group:studio ciao",
"group:studio diamond",
"group:studio dna",
"group:studio e.go",
"group:studio erohouse",
@@ -5602,6 +5700,7 @@ object Group : TagList {
"group:studio southpaw",
"group:studio sunadokei",
"group:studio sushi kui-ne",
"group:studio t.r.c.",
"group:studio tapa tapa",
"group:studio wallaby",
"group:studio waltz",
@@ -5615,6 +5714,7 @@ object Group : TagList {
"group:studio30neko",
"group:studiomia",
"group:studios",
"group:stukitora",
"group:stulli-yasan",
"group:sturm no shukuten",
"group:su kanchou koubou",
@@ -5657,6 +5757,7 @@ object Group : TagList {
"group:sumomo hana koushu",
"group:sunadokei to enpitsu",
"group:sunatoka aoi noyama",
"group:sunege6",
"group:sunezumi fauvism",
"group:sunora",
"group:sunsetmoon",
@@ -5766,8 +5867,10 @@ object Group : TagList {
"group:tamago no kimi",
"group:tamanegiya",
"group:tamatamasanmyaku",
"group:tamima-ya",
"group:tamokuteki hall",
"group:tamokuteki kuukan",
"group:tana kara marriage",
"group:tanajou",
"group:tanaka shouten",
"group:tanaura honpo",
@@ -5778,10 +5881,13 @@ object Group : TagList {
"group:tanmatsu ijou",
"group:tanpatsu kikaku",
"group:tansanshonen",
"group:tanu-chan chi",
"group:tanukineiri",
"group:tarai death",
"group:tarako koubou",
"group:tarakospa",
"group:tarantula",
"group:tarareba naraba",
"group:taromarun",
"group:tashikani",
"group:tasogare hakubutukan",
@@ -5799,6 +5905,7 @@ object Group : TagList {
"group:team dai 7 youhei shidan",
"group:team hin ga 9",
"group:team ibm",
"group:team lv",
"group:team okays",
"group:team tanabe",
"group:team z and 3n",
@@ -5854,6 +5961,8 @@ object Group : TagList {
"group:terolin soft",
"group:terra drive",
"group:testa kitchen",
"group:testme1111",
"group:tetora star gumi",
"group:tetorapotto bunsitu",
"group:tetrodotoxin",
"group:tetsubou shounen",
@@ -5866,6 +5975,7 @@ object Group : TagList {
"group:th",
"group:th4",
"group:the block buster destruction",
"group:the fourth sequence",
"group:the fuckin toyzaras",
"group:the jolly roger",
"group:the knight of the pants",
@@ -5898,115 +6008,5 @@ object Group : TagList {
"group:tirol bunko",
"group:tissuhaco",
"group:titanaluminiden",
"group:titancolor brand",
"group:titeki-kairaku",
"group:titillatio",
"group:titokara 2nd branch",
"group:tits",
"group:tiusan kingdom",
"group:tj studio",
"group:tkh soft",
"group:tkspower",
"group:tnc.",
"group:tobihizageri",
"group:todd special",
"group:toei animation",
"group:togari-nozawa",
"group:tohonifun",
"group:tojora-men",
"group:tokaeshina koubou",
"group:tokai oohashi",
"group:tokinoame",
"group:toko-ya",
"group:tokohuyu no bakansu",
"group:tokonatsu tou",
"group:tokoroniyori-tengoku",
"group:tokumori ajillo",
"group:tokutan biyori",
"group:tokyo big eros",
"group:tokyo bungeling bay yokohama",
"group:tokyo corechica",
"group:tokyo gamachannel",
"group:tokyo kumitaisougumi",
"group:tokyo note",
"group:tokyo ponpon dou",
"group:tokyo rox",
"group:tokyo tomodachi kouen",
"group:tokyo tsunamushi land",
"group:tokyo-rozewomond club",
"group:tokyoboogienight",
"group:tokyusen",
"group:toluene ittokan",
"group:tomatogohan.",
"group:tomatohouse-905s room",
"group:tomatta tokei",
"group:tomcat",
"group:tomizofu",
"group:tomoe project",
"group:tomonokai",
"group:tomoshibi-ya",
"group:tomoshibiya koubou",
"group:tonari no dagashiya-san",
"group:tonari no machi no teishokuya",
"group:tonari no yama",
"group:tondemo 8 pun",
"group:tongari gorigori",
"group:tonikakuushi",
"group:tonkotsu fuumi",
"group:tonny club",
"group:tonpuuratei",
"group:tonteki teishoku",
"group:tonton byoushi",
"group:tontoro daiyokujou",
"group:tonyu bokujo",
"group:tonzura douchuu",
"group:top hat studios",
"group:toppuu dooro",
"group:toragoyashiki",
"group:toraisix",
"group:toraiya",
"group:torajirusi",
"group:torano ori",
"group:toratepotto",
"group:toratsugumi",
"group:tori salt",
"group:toriaezu kari",
"group:toridorinori",
"group:toriha dance",
"group:toriihime",
"group:torikaeshi no tsukanai sex",
"group:torino sunakimo",
"group:torinoya",
"group:toriten software studio.",
"group:toro plus drop",
"group:toro toro resistance",
"group:toro-chin teishoku",
"group:toro2 circus",
"group:torochidan",
"group:toruneko chaya",
"group:totencop",
"group:toto max",
"group:totocetera",
"group:totontei",
"group:totoyasu no tsf lab",
"group:totsugasa",
"group:totsugeki tonarino jo-galbi",
"group:tottoko mtarou",
"group:tottori-sabaku kingdom",
"group:tottototomekichi",
"group:touch",
"group:tougall kai",
"group:touge mine",
"group:tougenkyou",
"group:tougesakuraya",
"group:touhou marupondou",
"group:touin",
"group:toukon iwashikusa",
"group:toumei kousoku",
"group:toumei tsuushin",
"group:toushi ryoku kenkyuujo",
"group:toutaku tuyagadou",
"group:touyou bujutsu gakkou",
"group:touyu okiba kari",
)
}
+128
View File
@@ -2,10 +2,124 @@ package exh.eh.tags
object Group2 : TagList {
override fun getTags1(): List<String> = listOf(
"group:titancolor brand",
"group:titeki-kairaku",
"group:titillatio",
"group:titokara 2nd branch",
"group:tits",
"group:tiusan kingdom",
"group:tj studio",
"group:tk jesus",
"group:tkh soft",
"group:tkspower",
"group:tnc.",
"group:tobihizageri",
"group:todd special",
"group:toei animation",
"group:togari-nozawa",
"group:tohonifun",
"group:tojora-men",
"group:tokaeshina koubou",
"group:tokai oohashi",
"group:tokinoame",
"group:toko-ya",
"group:tokohuyu no bakansu",
"group:tokonatsu tou",
"group:tokoroniyori-tengoku",
"group:tokumori ajillo",
"group:tokushuyokujou tondenhei",
"group:tokutan biyori",
"group:tokyo big eros",
"group:tokyo bungeling bay yokohama",
"group:tokyo corechica",
"group:tokyo gamachannel",
"group:tokyo kumitaisougumi",
"group:tokyo manga kenkyuujo",
"group:tokyo note",
"group:tokyo ponpon dou",
"group:tokyo rox",
"group:tokyo tomodachi kouen",
"group:tokyo tsunamushi land",
"group:tokyo-rozewomond club",
"group:tokyoboogienight",
"group:tokyusen",
"group:toluene ittokan",
"group:tomatogohan.",
"group:tomatohouse-905s room",
"group:tomatta tokei",
"group:tomcat",
"group:tomizofu",
"group:tomoe project",
"group:tomonokai",
"group:tomoshibi-ya",
"group:tomoshibiya koubou",
"group:tonari no dagashiya-san",
"group:tonari no machi no teishokuya",
"group:tonari no yama",
"group:tondemo 8 pun",
"group:tongari gorigori",
"group:tonikakuushi",
"group:tonkotsu fuumi",
"group:tonny club",
"group:tonpuuratei",
"group:tonteki teishoku",
"group:tonton byoushi",
"group:tontoro daiyokujou",
"group:tonyu bokujo",
"group:tonzura douchuu",
"group:top hat studios",
"group:toppuu dooro",
"group:toragoyashiki",
"group:toraisix",
"group:toraiya",
"group:torajirusi",
"group:torano ori",
"group:toratepotto",
"group:toratsugumi",
"group:tori salt",
"group:toriaezu kari",
"group:toridorinori",
"group:toriha dance",
"group:toriihime",
"group:torikaeshi no tsukanai sex",
"group:torino sunakimo",
"group:torinoya",
"group:toriten software studio.",
"group:toro plus drop",
"group:toro toro resistance",
"group:toro-chin teishoku",
"group:toro2 circus",
"group:torochidan",
"group:toruneko chaya",
"group:totencop",
"group:toto max",
"group:totocetera",
"group:totontei",
"group:totoyasu no tsf lab",
"group:totsugasa",
"group:totsugeki tonarino jo-galbi",
"group:tottoko mtarou",
"group:tottori-sabaku kingdom",
"group:tottototomekichi",
"group:touch",
"group:tougall kai",
"group:touge mine",
"group:tougenkyou",
"group:tougesakuraya",
"group:touhou marupondou",
"group:touin",
"group:toukon iwashikusa",
"group:toumei kousoku",
"group:toumei tsuushin",
"group:toushi ryoku kenkyuujo",
"group:toutaku tuyagadou",
"group:touyou bujutsu gakkou",
"group:touyu okiba kari",
"group:touyu stand",
"group:touzainanboku",
"group:touzoku tachi no rakuda no mure",
"group:toxic love",
"group:toyamando",
"group:toybox",
"group:tozan bu",
"group:tp",
@@ -55,6 +169,7 @@ object Group2 : TagList {
"group:tsuredure children",
"group:tsurezurezuki",
"group:tsurikichi doumei",
"group:tsurugashima heights",
"group:tsurumiku",
"group:tsurupeta kenkyuusho",
"group:tsurutasousankai",
@@ -70,6 +185,7 @@ object Group2 : TagList {
"group:tukinon bunko",
"group:tukishitahikou",
"group:tumiribbon",
"group:tuna ozawa",
"group:tunacan.",
"group:tunadrive",
"group:turuvege.",
@@ -141,8 +257,10 @@ object Group2 : TagList {
"group:uminouie",
"group:umon paradise",
"group:unagi no nedoko",
"group:unagineco house",
"group:unaginobori",
"group:unceder",
"group:uncertain field",
"group:unconscious",
"group:undamesi",
"group:undead",
@@ -201,6 +319,7 @@ object Group2 : TagList {
"group:uribatakebokujou",
"group:urondou",
"group:urusai kokuen",
"group:uruudoshi",
"group:us",
"group:usa daioh",
"group:usa.k",
@@ -218,6 +337,7 @@ object Group2 : TagList {
"group:usausa",
"group:ushi ushido",
"group:ushidon-ya",
"group:ushikani gassen",
"group:uso seisakusho",
"group:uso293",
"group:usotsuki house",
@@ -282,6 +402,7 @@ object Group2 : TagList {
"group:wankyoku canvas",
"group:wanwandoh",
"group:warabimochi",
"group:warau kado ni wa",
"group:waretama",
"group:warp loop",
"group:wasa wasa",
@@ -364,6 +485,7 @@ object Group2 : TagList {
"group:yakisaketeishoku",
"group:yakisoba pants",
"group:yakisoba rengo",
"group:yakitate jamaica",
"group:yakiubu",
"group:yakousei fan club",
"group:yaku 40 man sarad",
@@ -380,6 +502,7 @@ object Group2 : TagList {
"group:yamakawa denenhuukei",
"group:yamami no yado",
"group:yamamori gohan",
"group:yamamoto keiji",
"group:yamanaka no naka",
"group:yamanashi musume.",
"group:yamano murao",
@@ -463,10 +586,12 @@ object Group2 : TagList {
"group:yoyude ikemasu",
"group:yozorairodrops",
"group:ys company",
"group:yu-chu-bu",
"group:yu-ta.18",
"group:yu-yu-tei",
"group:yuasa rengou",
"group:yubidou",
"group:yubunecity",
"group:yudenakya nama-beer",
"group:yudokuya",
"group:yuhshiki",
@@ -497,6 +622,7 @@ object Group2 : TagList {
"group:yumeoikyounouta",
"group:yumeoukoku",
"group:yunabon",
"group:yuo kurokawa",
"group:yurayuraseyuura",
"group:yurei yashiki",
"group:yureika blade",
@@ -564,6 +690,7 @@ object Group2 : TagList {
"group:zenshuu bougyo",
"group:zensoku zenkai.",
"group:zensun habaku",
"group:zenten ukemi tomonokai",
"group:zenzidou kosyubenjo",
"group:zero byte",
"group:zero equals mono",
@@ -589,6 +716,7 @@ object Group2 : TagList {
"group:zugaikotsu marudashi",
"group:zundoko sperm bank",
"group:zurumuke taro",
"group:zutto mae kara darui.",
"group:zvizva-dan",
"group:zydan",
"group:zyulokuya",
+12
View File
@@ -37,6 +37,7 @@ object Male : TagList {
"male:balljob",
"male:balls expansion",
"male:bandages",
"male:bandaid",
"male:bat boy",
"male:bbm",
"male:bdsm",
@@ -55,7 +56,9 @@ object Male : TagList {
"male:big penis",
"male:bike shorts",
"male:bikini",
"male:bird boy",
"male:bisexual",
"male:bite mark",
"male:blackmail",
"male:blind",
"male:blindfold",
@@ -111,8 +114,10 @@ object Male : TagList {
"male:cockslapping",
"male:collar",
"male:condom",
"male:confinement",
"male:conjoined",
"male:coprophagia",
"male:corpse",
"male:corruption",
"male:corset",
"male:cosplaying",
@@ -157,6 +162,7 @@ object Male : TagList {
"male:drill hair",
"male:drugs",
"male:drunk",
"male:ear fuck",
"male:eel",
"male:eggs",
"male:electric shocks",
@@ -198,6 +204,7 @@ object Male : TagList {
"male:frog",
"male:frog boy",
"male:frottage",
"male:full tour",
"male:fundoshi",
"male:furry",
"male:gag",
@@ -273,6 +280,7 @@ object Male : TagList {
"male:kimono",
"male:kindergarten uniform",
"male:kissing",
"male:kodomo doushi",
"male:kunoichi",
"male:lab coat",
"male:lactation",
@@ -291,6 +299,7 @@ object Male : TagList {
"male:long tongue",
"male:low bestiality",
"male:low guro",
"male:low incest",
"male:low scat",
"male:low shotacon",
"male:low smegma",
@@ -329,6 +338,7 @@ object Male : TagList {
"male:multiple assjob",
"male:multiple footjob",
"male:multiple handjob",
"male:multiple nipples",
"male:multiple orgasms",
"male:multiple penises",
"male:multiple straddling",
@@ -336,6 +346,7 @@ object Male : TagList {
"male:muscle growth",
"male:mute",
"male:nakadashi",
"male:navel birth",
"male:navel fuck",
"male:nazi",
"male:necrophilia",
@@ -496,6 +507,7 @@ object Male : TagList {
"male:tracksuit",
"male:trampling",
"male:transformation",
"male:transparent clothing",
"male:triple anal",
"male:triple penetration",
"male:tube",
+2
View File
@@ -10,6 +10,8 @@ object Mixed : TagList {
"mixed:group",
"mixed:incest",
"mixed:inseki",
"mixed:kodomo doushi",
"mixed:low incest",
"mixed:mmf threesome",
"mixed:mmt threesome",
"mixed:mtf threesome",
+3 -2
View File
@@ -4,7 +4,6 @@ object Other : TagList {
override fun getTags1(): List<String> = listOf(
"other:3d",
"other:3d imageset",
"other:ai generated",
"other:already uploaded",
"other:anaglyph",
"other:animated",
@@ -20,21 +19,23 @@ object Other : TagList {
"other:forbidden content",
"other:full censorship",
"other:full color",
"other:game manual",
"other:game sprite",
"other:goudoushi",
"other:hardcore",
"other:how to",
"other:incomplete",
"other:kodomo only",
"other:missing cover",
"other:mosaic censorship",
"other:multi-work series",
"other:multipanel sequence",
"other:no penetration",
"other:non-h game manual",
"other:non-h imageset",
"other:non-nude",
"other:novel",
"other:nudity only",
"other:object insertion only",
"other:out of order",
"other:paperchild",
"other:realporn",

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