Compare commits

...

112 Commits

Author SHA1 Message Date
Jobobby04 2b890c2057 Minor improvements 2025-01-22 12:58:26 -05:00
Jobobby04 456db52653 Minor cleanup 2025-01-21 18:21:44 -05:00
Weblate (bot) 0a5e9dce24 Translations update from Hosted Weblate (#1340)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ta/
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/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/de/
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/fr/
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/ja/
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/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ta/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/tr/
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: Acelith <joel.jon@moix.me>
Co-authored-by: Andrés sigampa <fixiho3273@bawsny.com>
Co-authored-by: Barrel-Whisky-Fermentation <entomavasilissazeta790@gmail.com>
Co-authored-by: Bokutowo <stephaniejin47@gmail.com>
Co-authored-by: C0LiSii0N <paul.31@hotmail.com>
Co-authored-by: Cauã Oliveira <caua.oli.santos@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@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: Mochammad Nopal Attasya <meleboy22@gmail.com>
Co-authored-by: NormalRandomPeople <normal.scribe833@silomails.com>
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: mirukupc <mirukupc.jp@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 清水汐音 <chenzhongjie19940725@gmail.com>
2025-01-21 17:56:55 -05:00
spicemace 9b6600d31f Mangadex fix alt title being removed by cleanDescription (#1378)
* Update MdUtil.kt

* Update ApiMangaParser.kt

* Update MdUtil.kt
2025-01-21 17:12:11 -05:00
Mend Renovate 5f19859589 Update dependency com.google.firebase:firebase-bom to v33.8.0 (#1652)
(cherry picked from commit 82fd89cee65f6663a6eddd09c73eaff23d3c2947)
2025-01-21 14:48:18 -05:00
Mend Renovate a189780e7f Update dependency androidx.recyclerview:recyclerview to v1.4.0 (#1650)
(cherry picked from commit 643f95f046e98d7403daedf06ff01d0c9708249d)
2025-01-21 14:48:10 -05:00
Mend Renovate 664fcfd787 Update dependency androidx.activity:activity-compose to v1.10.0 (#1649)
(cherry picked from commit 9c81f2486cd8db6dbdb68e6e273cc8587814b21d)
2025-01-21 14:48:02 -05:00
Mend Renovate 64e3e03a02 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.2 (#1647)
(cherry picked from commit e59d2d381d2c105cae41918d30cc215ab3317551)
2025-01-21 14:47:56 -05:00
AntsyLich 3139aa5e51 Remove unnecessary filters for pseudolocales
(cherry picked from commit da90064c948c629ebaf6d6a97ca0b6f52cb570f1)
2025-01-21 14:47:46 -05:00
AntsyLich 2744a8bd96 Address some deprecations
(cherry picked from commit d53a3828b12daead9c898bea12c9a1497d07366f)
2025-01-21 14:47:39 -05:00
sdaqo 0ab7d18ad3 Add option to enable incognito mode per extension (#157)
* add per Extension Incognito Mode

* migrate incognito sources when extension is updated

* remove incognito sources when extension is uninstalled

* remove not used variable

* address change requests

address change requests

* Rebase and cleanup code

---------

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

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
2025-01-21 14:47:32 -05:00
Mend Renovate 853288f71b Update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.1 (#1630)
(cherry picked from commit 5a9367603beb82aac350c47c9ea2a6c343be0c1e)
2025-01-21 14:44:34 -05:00
Mend Renovate 4a2a81df80 Update dependency com.squareup.okio:okio to v3.10.2 (#1631)
(cherry picked from commit c01e9f3e92b9b2aa92aa50af5e3066affed419ad)
2025-01-21 14:44:27 -05:00
Mend Renovate 6827a0899c Update dependency com.android.tools.build:gradle to v8.8.0 (#1634)
(cherry picked from commit ae9753a1ea72b9e8d3271d56ed6cc202b8973ca2)
2025-01-21 14:44:20 -05:00
Mend Renovate dd1cbb07f7 Update dependency io.mockk:mockk to v1.13.16 (#1636)
(cherry picked from commit 1fe4d6cbd41f38676cb3cd858974ac105d272786)
2025-01-21 14:44:13 -05:00
Jobobby04 7f20006622 Use Adoptium distributed Java in workflows 2025-01-21 14:44:04 -05:00
Mend Renovate 8d25074a3e Update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.0 (#1628)
(cherry picked from commit 3a3abc6854c8035e0d489750a04fba8400ef2c84)
2025-01-21 14:42:58 -05:00
Mend Renovate 73a265f5ad Update serialization.version to v1.8.0 (#1627)
(cherry picked from commit d9a550b9350a6fb46bac783833b54c4b199e719b)
2025-01-21 14:42:51 -05:00
Mend Renovate 02da349080 Update aboutlib.version to v11.4.0 (#1621)
(cherry picked from commit 1617f8eb49a210808326bc46b536b87d62095658)
2025-01-21 14:42:44 -05:00
Jobobby04 e218234f91 Tweak build workflow 2025-01-21 14:42:35 -05:00
MajorTanya 47b4be7fcf Fix MAL main_picture nullability breaking search if a result doesn't have a cover set (#1618)
* Fix MAL manga cover nullability

If a manga doesn't have a cover, MAL doesn't provide the
`main_picture` element in the API response at all.

* Add CHANGELOG.md entry

(cherry picked from commit d60802721b78870082af9445201338fbcfa0a780)

# Conflicts:
#	CHANGELOG.md
2025-01-21 14:38:08 -05:00
AntsyLich fea11eaa06 Revert "Revert "Add option to always use SSIV for image decoding""
This reverts commit 1909126921ac78309f7f7c7c2aa85606611531b8

(cherry picked from commit c5655e8803bc32d0931657f0b7bc6afeab70feaf)

# Conflicts:
#	CHANGELOG.md
2025-01-21 14:37:36 -05:00
Mend Renovate 99ef619603 Update dependency gradle to v8.12 (#1605)
(cherry picked from commit d3973f4ad88b2d61e49974b032a118a7f67b9a7b)
2025-01-21 14:37:08 -05:00
Mend Renovate 93a5e70bbe Update dependency androidx.compose:compose-bom to v2024.12.01 (#1564)
(cherry picked from commit bb230fd6a77651ea2b5b1b3f1e42124a98b63016)
2025-01-21 14:36:59 -05:00
Mend Renovate a3c1c63332 Update paging.version to v3.3.5 (#1563)
(cherry picked from commit e526fd44c618ab26fd4860a0b7b147efc89d5bf1)
2025-01-21 14:36:52 -05:00
Mend Renovate 4348862e46 Update dependency androidx.viewpager:viewpager to v1.1.0 (#1571)
(cherry picked from commit f61f039a453cf562fdb10e9eeac64b55b2d9eb31)
2025-01-21 14:36:42 -05:00
Mend Renovate 5a6aaf8dcf Update dependency org.junit.jupiter:junit-jupiter to v5.11.4 (#1580)
(cherry picked from commit 79eb02d8f066e7cd2465938c24a9649a9a61f48a)
2025-01-21 14:36:35 -05:00
Mend Renovate bc28f7a4e9 Update voyager to v1.0.1 (#1595)
(cherry picked from commit 814584d35b4ad79da941b21178f452dc2dd601f3)
2025-01-21 14:36:28 -05:00
Mend Renovate 27f6ed4338 Update dependency com.android.tools:desugar_jdk_libs to v2.1.4 (#1599)
(cherry picked from commit 87513073018d9a0a31c64b898f10a11bddc4772c)
2025-01-21 14:36:18 -05:00
Mend Renovate 4ec0a6d148 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 (#1596)
(cherry picked from commit bcff2262b33004a4dec229c59c43cf27a04e72d3)
2025-01-21 14:36:11 -05:00
Mend Renovate 9e7a3c9e41 Update dependency io.mockk:mockk to v1.13.14 (#1601)
(cherry picked from commit 04454ecdbe49f0690c874b95becc3c164bb66f41)
2025-01-21 14:36:05 -05:00
Mend Renovate 8794b7f5de Update moko-resources to v0.24.4 (#1553)
(cherry picked from commit e86aeee9c417dea66d321fd4cbbad7ffdf41b106)
2025-01-21 14:35:49 -05:00
Mend Renovate 2f02aa07c7 Update dependency com.google.firebase:firebase-bom to v33.7.0 (#1545)
(cherry picked from commit be37f214d87c40f8876bd114cc96f55e0092ca90)
2025-01-21 14:35:40 -05:00
Mend Renovate 62f9c2b187 Update dependency com.android.tools.build:gradle to v8.7.3 (#1535)
(cherry picked from commit 1a833e88b1831fa4c8aacadeedc777adda256f36)
2025-01-21 14:35:33 -05:00
Mend Renovate 900ecfe372 Update dependency com.pinterest.ktlint:ktlint-cli to v1.5.0 (#1540)
(cherry picked from commit 4c84878adc541181ff37ebe23d1f8e7f7521d0d6)
2025-01-21 14:35:26 -05:00
Mend Renovate 72a19fc349 Update dependency org.jsoup:jsoup to v1.18.3 (#1533)
(cherry picked from commit 054198e78f80d6d3c9867a94317deb2d80950db9)
2025-01-21 14:35:13 -05:00
Mend Renovate 9e24276b59 Update kotlin monorepo to v2.1.0 (#1518)
(cherry picked from commit d522d81164d26d405517f7b6ad4d4882c86b54f2)
2025-01-21 14:35:01 -05:00
Tim Schneeberger 46dea6d598 feat: add global search shortcut to SmartSearch (#1377)
* feat: add global search shortcut to SmartSearch

* feat: add global search shortcut to SmartSearch

* feat: add back button to BrowseTabWrapper
2025-01-21 14:30:28 -05:00
Tim Schneeberger d80ad3f145 feat: unify recommendation screens and add more sources (#1376)
* feat(recommendations): add mangaupdates.com support

* feat(recommendations): display all tracker recommendation sources

* refactor(recommendations): apply spotless

* refactor: split RecommendationPagingSources

* feat(recommendations): unify MangaDex & community recommendations

* refactor: remove old screen

* fix: update comment

* style: fix formatting

* refactor: move onClick handlers

* fix: handle external recommendation links correctly

* fix: apply spotless

* feat: add comick recommendation source

* fix: mark recs from comick as not initialized to force fetching missing metadata

* Update app/src/main/java/exh/recs/BrowseRecommendsScreen.kt

---------

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2025-01-21 14:29:32 -05:00
renovate[bot] a7a3e5a2db Update koin to v4.0.2 (#1370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 14:25:27 -05:00
Luca Auer a32c7186e4 Maintain correct source order even when receiving new chapters from a sync service (#1360)
* Maintain correct source order even when receiving new chapters from sync service

* Add comma required by build service
2024-12-17 12:26:11 -05:00
Linguiniotta a25aff7fb0 Fix broken URI scheme (#1342) 2024-12-17 11:49:15 -05:00
renovate[bot] 7721d8b733 Update dependency com.google.oauth-client:google-oauth-client to v1.37.0 (#1369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-17 11:48:41 -05:00
Cuong-Tran 75db0d09e5 Fix spelling (#1368) 2024-12-16 08:58:10 -05:00
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
246 changed files with 8353 additions and 2060 deletions
+5 -5
View File
@@ -53,7 +53,7 @@ body:
label: TachiyomiSY version label: TachiyomiSY version
description: You can find your TachiyomiSY version in **More → About**. description: You can find your TachiyomiSY version in **More → About**.
placeholder: | placeholder: |
Example: "1.10.5" Example: "1.11.0"
validations: validations:
required: true required: true
@@ -63,7 +63,7 @@ body:
label: Android version label: Android version
description: You can find this somewhere in your Android settings. description: You can find this somewhere in your Android settings.
placeholder: | placeholder: |
Example: "Android 11" Example: "Android 14"
validations: validations:
required: true required: true
@@ -73,7 +73,7 @@ body:
label: Device label: Device
description: List your device and model. description: List your device and model.
placeholder: | placeholder: |
Example: "Google Pixel 5" Example: "Google Pixel 8"
validations: validations:
required: true required: true
@@ -94,9 +94,9 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https:/mihon.app/docs/guides/troubleshooting/). - label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
required: true 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 required: true
- label: I have updated all installed extensions. - label: I have updated all installed extensions.
required: true required: true
+1 -1
View File
@@ -31,7 +31,7 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true 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 required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true
+1 -13
View File
@@ -6,20 +6,8 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
build: build:
name: Build app name: Build app
needs: check_wrapper
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -30,7 +18,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: adopt distribution: temurin
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
+15 -16
View File
@@ -17,23 +17,16 @@ jobs:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
- name: Setup Android SDK
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: adopt distribution: temurin
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
# SY <-- # SY -->
- name: Write google-services.json - name: Write google-services.json
uses: DamianReeves/write-file-action@v1.3 uses: DamianReeves/write-file-action@v1.3
with: with:
@@ -47,10 +40,16 @@ jobs:
path: app/src/main/assets/client_secrets.json path: app/src/main/assets/client_secrets.json
contents: ${{ secrets.CLIENT_SECRETS_TEXT }} contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
write-mode: overwrite write-mode: overwrite
# SY --> # SY <--
- name: Build app and run unit tests - name: Check code format
run: ./gradlew spotlessCheck assembleStandardRelease testStandardReleaseUnitTest --stacktrace run: ./gradlew spotlessCheck
- name: Build app
run: ./gradlew assembleStandardRelease
- name: Run unit tests
run: ./gradlew testReleaseUnitTest testStandardReleaseUnitTest
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: r0adkll/sign-android-release@v1
@@ -69,19 +68,19 @@ jobs:
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk mv app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk mv app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk mv app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk mv app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'` sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
+22 -27
View File
@@ -1,7 +1,8 @@
@file:Suppress("ChromeOsAbiSupport")
import mihon.buildlogic.getBuildTime import mihon.buildlogic.getBuildTime
import mihon.buildlogic.getCommitCount import mihon.buildlogic.getCommitCount
import mihon.buildlogic.getGitSha import mihon.buildlogic.getGitSha
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id("mihon.android.application") id("mihon.android.application")
@@ -30,8 +31,8 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 69 versionCode = 71
versionName = "1.10.5" versionName = "1.11.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -98,8 +99,6 @@ android {
dimension = "default" dimension = "default"
} }
create("dev") { create("dev") {
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
dimension = "default" dimension = "default"
} }
} }
@@ -141,6 +140,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 { dependencies {
implementation(projects.i18n) implementation(projects.i18n)
// SY --> // SY -->
@@ -307,28 +324,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 { buildscript {
dependencies { dependencies {
classpath(kotlinx.gradle) classpath(kotlinx.gradle)
@@ -1,5 +1,6 @@
package eu.kanade.core.util package eu.kanade.core.util
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract 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]. * 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) @OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> { inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) } contract { callsInPlace(predicate) }
val destination = ArrayList<T>() return fastFilter { !predicate(it) }
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
} }
/** /**
@@ -131,26 +97,3 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
fastForEach { if (predicate(it)) --count } fastForEach { if (predicate(it)) --count }
return 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
}
@@ -13,9 +13,11 @@ import eu.kanade.domain.manga.interactor.SetExcludedScanlators
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.interactor.GetLanguagesWithSources import eu.kanade.domain.source.interactor.GetLanguagesWithSources
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.interactor.ToggleIncognito
import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin import eu.kanade.domain.source.interactor.ToggleSourcePin
@@ -190,5 +192,7 @@ class DomainModule : InjektModule {
addFactory { DeleteExtensionRepo(get()) } addFactory { DeleteExtensionRepo(get()) }
addFactory { ReplaceExtensionRepo(get()) } addFactory { ReplaceExtensionRepo(get()) }
addFactory { UpdateExtensionRepo(get(), get()) } addFactory { UpdateExtensionRepo(get(), get()) }
addFactory { ToggleIncognito(get()) }
addFactory { GetIncognitoState(get(), get(), get()) }
} }
} }
@@ -2,6 +2,7 @@ package eu.kanade.domain.base
import android.content.Context import android.content.Context
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.util.system.GLUtil
import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -30,4 +31,8 @@ class BasePreferences(
} }
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "") fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false)
} }
@@ -0,0 +1,35 @@
package eu.kanade.domain.source.interactor
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
class GetIncognitoState(
private val basePreferences: BasePreferences,
private val sourcePreferences: SourcePreferences,
private val extensionManager: ExtensionManager,
) {
fun await(sourceId: Long?): Boolean {
if (basePreferences.incognitoMode().get()) return true
if (sourceId == null) return false
val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false
return extensionPackage in sourcePreferences.incognitoExtensions().get()
}
fun subscribe(sourceId: Long?): Flow<Boolean> {
if (sourceId == null) return basePreferences.incognitoMode().changes()
return combine(
basePreferences.incognitoMode().changes(),
sourcePreferences.incognitoExtensions().changes(),
extensionManager.getExtensionPackageAsFlow(sourceId),
) { incognito, incognitoExtensions, extensionPackage ->
incognito || (extensionPackage in incognitoExtensions)
}
.distinctUntilChanged()
}
}
@@ -0,0 +1,14 @@
package eu.kanade.domain.source.interactor
import eu.kanade.domain.source.service.SourcePreferences
import tachiyomi.core.common.preference.getAndSet
class ToggleIncognito(
private val preferences: SourcePreferences,
) {
fun await(extensions: String, enable: Boolean) {
preferences.incognitoExtensions().getAndSet {
if (enable) it.plus(extensions) else it.minus(extensions)
}
}
}
@@ -22,6 +22,8 @@ class SourcePreferences(
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet()) fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet()) fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
fun lastUsedSource() = preferenceStore.getLong( fun lastUsedSource() = preferenceStore.getLong(
@@ -11,7 +11,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
fun BrowseTabWrapper(tab: TabContent) { fun BrowseTabWrapper(tab: TabContent, onBackPressed: (() -> Unit)? = null) {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
@@ -20,6 +20,7 @@ fun BrowseTabWrapper(tab: TabContent) {
actions = { actions = {
AppBarActions(tab.actions) AppBarActions(tab.actions)
}, },
navigateUp = onBackPressed,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
}, },
@@ -35,8 +35,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -48,6 +50,7 @@ import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.WarningBanner import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
@@ -73,6 +76,7 @@ fun ExtensionDetailsScreen(
onClickClearCookies: () -> Unit, onClickClearCookies: () -> Unit,
onClickUninstall: () -> Unit, onClickUninstall: () -> Unit,
onClickSource: (sourceId: Long) -> Unit, onClickSource: (sourceId: Long) -> Unit,
onClickIncognito: (Boolean) -> Unit,
) { ) {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val url = remember(state.extension) { val url = remember(state.extension) {
@@ -141,9 +145,11 @@ fun ExtensionDetailsScreen(
contentPadding = paddingValues, contentPadding = paddingValues,
extension = state.extension, extension = state.extension,
sources = state.sources, sources = state.sources,
incognitoMode = state.isIncognito,
onClickSourcePreferences = onClickSourcePreferences, onClickSourcePreferences = onClickSourcePreferences,
onClickUninstall = onClickUninstall, onClickUninstall = onClickUninstall,
onClickSource = onClickSource, onClickSource = onClickSource,
onClickIncognito = onClickIncognito,
) )
} }
} }
@@ -153,9 +159,11 @@ private fun ExtensionDetails(
contentPadding: PaddingValues, contentPadding: PaddingValues,
extension: Extension.Installed, extension: Extension.Installed,
sources: ImmutableList<ExtensionSourceItem>, sources: ImmutableList<ExtensionSourceItem>,
incognitoMode: Boolean,
onClickSourcePreferences: (sourceId: Long) -> Unit, onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickUninstall: () -> Unit, onClickUninstall: () -> Unit,
onClickSource: (sourceId: Long) -> Unit, onClickSource: (sourceId: Long) -> Unit,
onClickIncognito: (Boolean) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
var showNsfwWarning by remember { mutableStateOf(false) } var showNsfwWarning by remember { mutableStateOf(false) }
@@ -179,6 +187,7 @@ private fun ExtensionDetails(
item { item {
DetailsHeader( DetailsHeader(
extension = extension, extension = extension,
extIncognitoMode = incognitoMode,
onClickUninstall = onClickUninstall, onClickUninstall = onClickUninstall,
onClickAppInfo = { onClickAppInfo = {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
@@ -190,6 +199,7 @@ private fun ExtensionDetails(
onClickAgeRating = { onClickAgeRating = {
showNsfwWarning = true showNsfwWarning = true
}, },
onExtIncognitoChange = onClickIncognito,
) )
} }
@@ -217,9 +227,11 @@ private fun ExtensionDetails(
@Composable @Composable
private fun DetailsHeader( private fun DetailsHeader(
extension: Extension, extension: Extension,
extIncognitoMode: Boolean,
onClickAgeRating: () -> Unit, onClickAgeRating: () -> Unit,
onClickUninstall: () -> Unit, onClickUninstall: () -> Unit,
onClickAppInfo: (() -> Unit)?, onClickAppInfo: (() -> Unit)?,
onExtIncognitoChange: (Boolean) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -227,9 +239,8 @@ private fun DetailsHeader(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = MaterialTheme.padding.medium)
.padding( .padding(
start = MaterialTheme.padding.medium,
end = MaterialTheme.padding.medium,
top = MaterialTheme.padding.medium, top = MaterialTheme.padding.medium,
bottom = MaterialTheme.padding.small, bottom = MaterialTheme.padding.small,
) )
@@ -321,12 +332,9 @@ private fun DetailsHeader(
} }
Row( Row(
modifier = Modifier.padding( modifier = Modifier
start = MaterialTheme.padding.medium, .padding(horizontal = MaterialTheme.padding.medium)
end = MaterialTheme.padding.medium, .padding(top = MaterialTheme.padding.small),
top = MaterialTheme.padding.small,
bottom = MaterialTheme.padding.medium,
),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
) { ) {
OutlinedButton( OutlinedButton(
@@ -349,6 +357,24 @@ private fun DetailsHeader(
} }
} }
TextPreferenceWidget(
modifier = Modifier.padding(horizontal = MaterialTheme.padding.small),
title = stringResource(MR.strings.pref_incognito_mode),
subtitle = stringResource(MR.strings.pref_incognito_mode_extension_summary),
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
widget = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Switch(
checked = extIncognitoMode,
onCheckedChange = onExtIncognitoChange,
modifier = Modifier.padding(start = TrailingWidgetBuffer),
)
}
},
)
HorizontalDivider() HorizontalDivider()
} }
} }
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.PrimaryTabRow
@@ -14,7 +15,6 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -33,20 +33,13 @@ import tachiyomi.presentation.core.i18n.stringResource
fun TabbedScreen( fun TabbedScreen(
titleRes: StringResource, titleRes: StringResource,
tabs: ImmutableList<TabContent>, tabs: ImmutableList<TabContent>,
startIndex: Int? = null, state: PagerState = rememberPagerState { tabs.size },
searchQuery: String? = null, searchQuery: String? = null,
onChangeSearchQuery: (String?) -> Unit = {}, onChangeSearchQuery: (String?) -> Unit = {},
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberPagerState { tabs.size }
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(startIndex) {
if (startIndex != null) {
state.scrollToPage(startIndex)
}
}
Scaffold( Scaffold(
topBar = { topBar = {
val tab = tabs[state.currentPage] val tab = tabs[state.currentPage]
@@ -21,9 +21,7 @@ internal fun LibraryTabs(
getNumberOfMangaForCategory: (Category) -> Int?, getNumberOfMangaForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit, onTabItemClick: (Int) -> Unit,
) { ) {
// SY -->
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex) val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
// SY <--
Column( Column(
modifier = Modifier.zIndex(1f), modifier = Modifier.zIndex(1f),
) { ) {
@@ -15,7 +15,6 @@ import androidx.compose.ui.window.DialogProperties
import exh.favorites.FavoritesSyncStatus import exh.favorites.FavoritesSyncStatus
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -23,7 +22,6 @@ import kotlin.time.Duration.Companion.seconds
data class SyncFavoritesProgressProperties( data class SyncFavoritesProgressProperties(
val title: String, val title: String,
val text: String, val text: String,
val canDismiss: Boolean,
val positiveButtonText: String? = null, val positiveButtonText: String? = null,
val positiveButton: (() -> Unit)? = null, val positiveButton: (() -> Unit)? = null,
val negativeButtonText: String? = null, val negativeButtonText: String? = null,
@@ -34,18 +32,23 @@ data class SyncFavoritesProgressProperties(
fun SyncFavoritesProgressDialog( fun SyncFavoritesProgressDialog(
status: FavoritesSyncStatus, status: FavoritesSyncStatus,
setStatusIdle: () -> Unit, setStatusIdle: () -> Unit,
openManga: (Manga) -> Unit, openManga: (Long) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) { val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
when (status) { when (status) {
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties( is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error), title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message), text = context.stringResource(
canDismiss = false, 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), positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
positiveButton = { positiveButton = {
openManga(status.manga) openManga(status.mangaId)
setStatusIdle() setStatusIdle()
}, },
negativeButtonText = context.stringResource(MR.strings.action_ok), negativeButtonText = context.stringResource(MR.strings.action_ok),
@@ -53,31 +56,122 @@ fun SyncFavoritesProgressDialog(
) )
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties( is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_done_errors), title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message), text = context.stringResource(
canDismiss = false, SYMR.strings.favorites_sync_done_errors_message,
positiveButtonText = context.stringResource(MR.strings.action_ok), status.messages.joinToString(separator = "\n") {
positiveButton = setStatusIdle, when (it) {
) is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties( context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
title = context.stringResource(SYMR.strings.favorites_sync_error), context.stringResource(
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message), SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
canDismiss = false, )
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), positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle, positiveButton = setStatusIdle,
) )
is FavoritesSyncStatus.Idle -> value = null is FavoritesSyncStatus.Idle -> value = null
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> { is FavoritesSyncStatus.Initializing -> {
value = SyncFavoritesProgressProperties( value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing), title = context.stringResource(SYMR.strings.favorites_syncing),
text = status.message, text = context.stringResource(SYMR.strings.favorites_sync_initializing),
canDismiss = false,
) )
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) delay(5.seconds)
value = SyncFavoritesProgressProperties( value = properties.copy(
title = context.stringResource(SYMR.strings.favorites_syncing), text = when (status) {
text = status.delayedMessage ?: status.message, is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
canDismiss = false, 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( properties = DialogProperties(
dismissOnClickOutside = dialog.canDismiss, dismissOnClickOutside = false,
dismissOnBackPress = dialog.canDismiss, dismissOnBackPress = false,
), ),
) )
} }
@@ -165,12 +165,12 @@ sealed class Preference {
data class CustomPreference( data class CustomPreference(
override val title: String, override val title: String,
val content: @Composable (PreferenceItem<String>) -> Unit, val content: @Composable () -> Unit,
) : PreferenceItem<String>() { ) : PreferenceItem<Unit>() {
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = 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) InfoWidget(text = item.title)
} }
is Preference.PreferenceItem.CustomPreference -> { 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.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.storage.DiskUtil 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.isDevFlavor
import eu.kanade.tachiyomi.util.system.isPreviewBuildType import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isShizukuInstalled 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.i18n.stringResource
import tachiyomi.core.common.util.lang.launchNonCancellable import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -369,6 +371,31 @@ object SettingsAdvancedScreen : SearchableSettings {
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader), title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf( 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.SwitchPreference(
pref = basePreferences.alwaysDecodeLongStripWithSSIV(),
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv),
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile), title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(), subtitle = basePreferences.displayProfile().get(),
@@ -537,7 +537,7 @@ object SettingsDataScreen : SearchableSettings {
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle), subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
onClick = { onClick = {
if (!SyncDataJob.isRunning(context)) { if (!SyncDataJob.isRunning(context)) {
SyncDataJob.startNow(context) SyncDataJob.startNow(context, manual = true)
} else { } else {
context.toast(SYMR.strings.sync_in_progress) context.toast(SYMR.strings.sync_in_progress)
} }
@@ -139,7 +139,7 @@ object SettingsMangadexScreen : SearchableSettings {
title = mdex.name + " Login", title = mdex.name + " Login",
content = { content = {
BasePreferenceWidget( BasePreferenceWidget(
title = it.title, title = mdex.name + " Login",
widget = { widget = {
Icon( Icon(
imageVector = Icons.Outlined.PeopleAlt, imageVector = Icons.Outlined.PeopleAlt,
+15 -6
View File
@@ -15,6 +15,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.work.Configuration
import androidx.work.WorkManager
import coil3.ImageLoader import coil3.ImageLoader
import coil3.SingletonImageLoader import coil3.SingletonImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory 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.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.DeviceUtil 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.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.cancelNotification
@@ -78,6 +81,7 @@ import org.conscrypt.Conscrypt
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.storage.service.StorageManager import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -173,6 +177,14 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
.onEach(FirebaseConfig::setCrashlyticsEnabled) .onEach(FirebaseConfig::setCrashlyticsEnabled)
.launchIn(scope) .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()) setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
// Updates widget update // Updates widget update
@@ -182,6 +194,9 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE)) LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
}*/ }*/
if (!WorkManager.isInitialized()) {
WorkManager.initialize(this, Configuration.Builder().build())
}
val syncPreferences: SyncPreferences = Injekt.get() val syncPreferences: SyncPreferences = Injekt.get()
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions() val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) { if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
@@ -283,12 +298,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" } 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 // EXH
@@ -202,6 +202,7 @@ class MangaRestorer(
bookmark = chapter.bookmark || dbChapter.bookmark, bookmark = chapter.bookmark || dbChapter.bookmark,
read = chapter.read, read = chapter.read,
lastPageRead = chapter.lastPageRead, lastPageRead = chapter.lastPageRead,
sourceOrder = chapter.sourceOrder,
) )
} else { } else {
chapter.copyFrom(dbChapter).let { chapter.copyFrom(dbChapter).let {
@@ -252,7 +253,7 @@ class MangaRestorer(
bookmark = chapter.bookmark, bookmark = chapter.bookmark,
lastPageRead = chapter.lastPageRead, lastPageRead = chapter.lastPageRead,
chapterNumber = null, chapterNumber = null,
sourceOrder = null, sourceOrder = if (isSync) chapter.sourceOrder else null,
dateFetch = null, dateFetch = null,
dateUpload = null, dateUpload = null,
chapterId = chapter.id, chapterId = chapter.id,
@@ -15,7 +15,6 @@ import coil3.request.bitmapConfig
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.storage.CbzCrypto import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
import eu.kanade.tachiyomi.util.system.GLUtil
import mihon.core.common.archive.archiveReader import mihon.core.common.archive.archiveReader
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
@@ -71,7 +70,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
if ( if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
options.bitmapConfig == Bitmap.Config.HARDWARE && options.bitmapConfig == Bitmap.Config.HARDWARE &&
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize ImageUtil.canUseHardwareBitmap(bitmap)
) { ) {
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false) val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
if (hwBitmap != null) { if (hwBitmap != null) {
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.library
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build import android.os.Build
import androidx.work.BackoffPolicy import androidx.work.BackoffPolicy
import androidx.work.Constraints import androidx.work.Constraints
@@ -135,10 +137,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if (tags.contains(WORK_NAME_AUTO)) { if (tags.contains(WORK_NAME_AUTO)) {
val preferences = Injekt.get<LibraryPreferences>() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val restrictions = preferences.autoUpdateDeviceRestrictions().get() val preferences = Injekt.get<LibraryPreferences>()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) { val restrictions = preferences.autoUpdateDeviceRestrictions().get()
return Result.retry() if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
} }
// Find a running manual worker. If exists, try again later // Find a running manual worker. If exists, try again later
@@ -768,15 +772,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.autoUpdateInterval().get() val interval = prefInterval ?: preferences.autoUpdateInterval().get()
if (interval > 0) { if (interval > 0) {
val restrictions = preferences.autoUpdateDeviceRestrictions().get() val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val constraints = Constraints( val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED
NetworkType.UNMETERED } else {
} else { NetworkType.CONNECTED
NetworkType.CONNECTED }
}, val networkRequestBuilder = NetworkRequest.Builder()
requiresCharging = DEVICE_CHARGING in restrictions, if (DEVICE_ONLY_ON_WIFI in restrictions) {
requiresBatteryNotLow = true, 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>( val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), interval.toLong(),
@@ -30,6 +30,9 @@ object Notifications {
const val ID_LIBRARY_SIZE_WARNING = -103 const val ID_LIBRARY_SIZE_WARNING = -103
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel" const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
const val ID_LIBRARY_ERROR = -102 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. * 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 CHANNEL_APP_UPDATE = "app_apk_update_channel"
const val ID_APP_UPDATER = 1 const val ID_APP_UPDATER = 1
const val ID_APP_UPDATE_PROMPT = 2 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 CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
const val ID_UPDATES_TO_EXTS = -401 const val ID_UPDATES_TO_EXTS = -401
const val ID_EXTENSION_INSTALLER = -402 const val ID_EXTENSION_INSTALLER = -402
@@ -166,6 +170,13 @@ object Notifications {
setGroup(GROUP_APK_UPDATES) setGroup(GROUP_APK_UPDATES)
setName(context.stringResource(MR.strings.channel_ext_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 { sealed interface Location {
@ConsistentCopyVisibility
data class Pictures private constructor(val relativePath: String) : Location { data class Pictures private constructor(val relativePath: String) : Location {
companion object { companion object {
fun create(relativePath: String = ""): Pictures { fun create(relativePath: String = ""): Pictures {
@@ -15,6 +15,7 @@ import androidx.work.WorkerParameters
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.cancelNotification 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.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager import eu.kanade.tachiyomi.util.system.workManager
@@ -31,6 +32,9 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if (tags.contains(TAG_AUTO)) { if (tags.contains(TAG_AUTO)) {
if (!context.isOnline()) {
return Result.retry()
}
// Find a running manual worker. If exists, try again later // Find a running manual worker. If exists, try again later
if (context.workManager.isRunning(TAG_MANUAL)) { if (context.workManager.isRunning(TAG_MANUAL)) {
return Result.retry() 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 val wm = context.workManager
if (wm.isRunning(TAG_JOB)) { if (wm.isRunning(TAG_JOB)) {
// Already running either as a scheduled or manual job // Already running either as a scheduled or manual job
return return
} }
val tag = if (manual) TAG_MANUAL else TAG_AUTO
val request = OneTimeWorkRequestBuilder<SyncDataJob>() val request = OneTimeWorkRequestBuilder<SyncDataJob>()
.addTag(TAG_JOB) .addTag(TAG_JOB)
.addTag(TAG_MANUAL) .addTag(tag)
.build() .build()
context.workManager.enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request) context.workManager.enqueueUniqueWork(tag, ExistingWorkPolicy.KEEP, request)
} }
fun stop(context: Context) { fun stop(context: Context) {
@@ -233,7 +233,12 @@ abstract class SyncService(
localChapter != null && remoteChapter != null -> { localChapter != null && remoteChapter != null -> {
// Use version number to decide which chapter to keep // Use version number to decide which chapter to keep
val chosenChapter = if (localChapter.version >= remoteChapter.version) { val chosenChapter = if (localChapter.version >= remoteChapter.version) {
localChapter // If there mare more chapter on remote, local sourceOrder will need to be updated to maintain correct source order.
if (localChapters.size < remoteChapters.size) {
localChapter.copy(sourceOrder = remoteChapter.sourceOrder)
} else {
localChapter
}
} else { } else {
remoteChapter remoteChapter
} }
@@ -500,6 +505,7 @@ abstract class SyncService(
logcat(LogPriority.DEBUG, logTag) { "Using remote saved search: ${remoteSearch.name}." } logcat(LogPriority.DEBUG, logTag) { "Using remote saved search: ${remoteSearch.name}." }
remoteSearch remoteSearch
} }
else -> { else -> {
logcat(LogPriority.DEBUG, logTag) { logcat(LogPriority.DEBUG, logTag) {
"No saved search found for composite key: $compositeKey. Skipping." "No saved search found for composite key: $compositeKey. Skipping."
@@ -6,6 +6,7 @@ import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track 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.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -120,6 +121,10 @@ abstract class BaseTracker(
updateRemote(track) updateRemote(track)
} }
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
throw NotImplementedError("Not implemented.")
}
private suspend fun updateRemote(track: Track): Unit = withIOContext { private suspend fun updateRemote(track: Track): Unit = withIOContext {
try { try {
update(track) update(track)
@@ -5,6 +5,7 @@ import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track 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.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -82,4 +83,6 @@ interface Tracker {
suspend fun setRemoteStartDate(track: Track, epochMillis: Long) suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
suspend fun setRemoteFinishDate(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.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@@ -232,6 +233,10 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
interceptor.setAuth(null) interceptor.setAuth(null)
} }
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
fun saveOAuth(alOAuth: ALOAuth?) { fun saveOAuth(alOAuth: ALOAuth?) {
trackPreferences.trackToken(this).set(json.encodeToString(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.database.models.Track
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult 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.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.ALOAuth
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject 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 { private fun createDate(dateValue: Long): JsonObject {
if (dateValue == 0L) { if (dateValue == 0L) {
return buildJsonObject { 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.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -75,6 +76,10 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
return api.search(query) return api.search(query)
} }
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
override suspend fun refresh(track: Track): Track { override suspend fun refresh(track: Track): Track {
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga") val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
track.copyPersonalFrom(remoteStatusTrack) 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.BGMOAuth
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem 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.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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
@@ -21,6 +24,7 @@ import tachiyomi.core.common.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import tachiyomi.domain.track.model.Track as DomainTrack
class BangumiApi( class BangumiApi(
private val trackId: Long, private val trackId: Long,
@@ -71,6 +75,8 @@ class BangumiApi(
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}" val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
.toUri() .toUri()
.buildUpon() .buildUpon()
.appendQueryParameter("type", "1")
.appendQueryParameter("responseGroup", "large")
.appendQueryParameter("max_results", "20") .appendQueryParameter("max_results", "20")
.build() .build()
with(json) { with(json) {
@@ -81,7 +87,6 @@ class BangumiApi(
if (result.code == 404) emptyList<TrackSearch>() if (result.code == 404) emptyList<TrackSearch>()
result.list result.list
?.filter { it.type == 1 }
?.map { it.toTrackSearch(trackId) } ?.map { it.toTrackSearch(trackId) }
.orEmpty() .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 { suspend fun accessToken(code: String): BGMOAuth {
return withIOContext { return withIOContext {
with(json) { with(json) {
@@ -17,6 +17,7 @@ data class BGMSearchItem(
val nameCn: String, val nameCn: String,
val name: String, val name: String,
val type: Int, val type: Int,
val summary: String?,
val images: BGMSearchItemCovers?, val images: BGMSearchItemCovers?,
@SerialName("eps_count") @SerialName("eps_count")
val epsCount: Long?, val epsCount: Long?,
@@ -25,9 +26,13 @@ data class BGMSearchItem(
) { ) {
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply { fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
remote_id = this@BGMSearchItem.id remote_id = this@BGMSearchItem.id
title = nameCn title = nameCn.ifBlank { name }
cover_url = images?.common ?: "" cover_url = images?.common.orEmpty()
summary = this@BGMSearchItem.name summary = if (nameCn.isNotBlank()) {
"作品原名:$name" + this@BGMSearchItem.summary?.let { "\n$it" }.orEmpty()
} else {
this@BGMSearchItem.summary.orEmpty()
}
score = rating?.score ?: -1.0 score = rating?.score ?: -1.0
tracking_url = url tracking_url = url
total_chapters = epsCount ?: 0 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.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -139,6 +140,10 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
interceptor.newAuth(null) interceptor.newAuth(null)
} }
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return api.getMangaMetadata(track)
}
private fun getUserId(): String { private fun getUserId(): String {
return getPassword() 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.KitsuAlgoliaSearchResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult 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.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.KitsuOAuth
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET 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.awaitSuccess
import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put 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 { companion object {
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd" private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151" private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val BASE_URL = "https://kitsu.app/api/edge/" 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 LOGIN_URL = "https://kitsu.app/api/oauth/token"
private const val BASE_MANGA_URL = "https://kitsu.app/manga/" private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/" 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.MURating
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -117,6 +119,20 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
interceptor.newAuth(authenticated.sessionToken) 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? { fun restoreSession(): String? {
return trackPreferences.trackPassword(this).get().ifBlank { null } 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 { companion object {
private const val BASE_URL = "https://api.mangaupdates.com" private const val BASE_URL = "https://api.mangaupdates.com"
@@ -21,6 +21,7 @@ data class MURecord(
val ratingVotes: Int? = null, val ratingVotes: Int? = null,
@SerialName("latest_chapter") @SerialName("latest_chapter")
val latestChapter: Int? = null, val latestChapter: Int? = null,
val authors: List<MUAuthor>? = null,
) )
fun MURecord.toTrackSearch(id: Long): TrackSearch { fun MURecord.toTrackSearch(id: Long): TrackSearch {
@@ -36,3 +37,9 @@ fun MURecord.toTrackSearch(id: Long): TrackSearch {
start_date = this@toTrackSearch.year.toString() 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 android.graphics.Color
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
@@ -168,6 +170,21 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
trackPreferences.trackToken(this).delete() 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 override val isLoggedIn: Boolean
get() = trackPreferences.trackToken(this).get().isNotEmpty() 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,
)
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker 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.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@@ -156,6 +157,10 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
interceptor.setAuth(null) interceptor.setAuth(null)
} }
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
fun getIfAuthExpired(): Boolean { fun getIfAuthExpired(): Boolean {
return trackPreferences.trackAuthExpired(this).get() return trackPreferences.trackAuthExpired(this).get()
} }
@@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track 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.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem 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.MALListItemStatus
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga 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.MALOAuth
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
@@ -111,7 +113,7 @@ class MyAnimeListApi(
summary = it.synopsis summary = it.synopsis
total_chapters = it.numChapters total_chapters = it.numChapters
score = it.mean score = it.mean
cover_url = it.covers.large cover_url = it.covers?.large.orEmpty()
tracking_url = "https://myanimelist.net/manga/$remote_id" tracking_url = "https://myanimelist.net/manga/$remote_id"
publishing_status = it.status.replace("_", " ") publishing_status = it.status.replace("_", " ")
publishing_type = it.mediaType.replace("_", " ") publishing_type = it.mediaType.replace("_", " ")
@@ -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 { private suspend fun getListPage(offset: Int): MALUserSearchResult {
return withIOContext { return withIOContext {
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon() val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
@@ -12,7 +12,7 @@ data class MALManga(
val numChapters: Long, val numChapters: Long,
val mean: Double = -1.0, val mean: Double = -1.0,
@SerialName("main_picture") @SerialName("main_picture")
val covers: MALMangaCovers, val covers: MALMangaCovers?,
val status: String, val status: String,
@SerialName("media_type") @SerialName("media_type")
val mediaType: String, val mediaType: String,
@@ -23,4 +23,29 @@ data class MALManga(
@Serializable @Serializable
data class MALMangaCovers( data class MALMangaCovers(
val large: String = "", 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.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker 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.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@@ -98,6 +99,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
return track return track
} }
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
override fun getLogo() = R.drawable.ic_tracker_shikimori override fun getLogo() = R.drawable.ic_tracker_shikimori
override fun getLogoColor() = Color.rgb(40, 40, 40) override fun getLogoColor() = Color.rgb(40, 40, 40)
@@ -3,9 +3,11 @@ package eu.kanade.tachiyomi.data.track.shikimori
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track 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.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse 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.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.SMOAuth
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry 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 { suspend fun accessToken(code: String): SMOAuth {
return withIOContext { return withIOContext {
with(json) { 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( addAction(
R.drawable.ic_close_24dp, R.drawable.ic_close_24dp,
context.stringResource(MR.strings.action_cancel), 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)
} }
} }
@@ -89,12 +89,25 @@ class ExtensionManager(
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
fun getAppIconForSource(sourceId: Long): Drawable? { fun getExtensionPackage(sourceId: Long): String? {
val pkgName = installedExtensionMapFlow.value.values return installedExtensionsFlow.value.find { extension ->
.find { ext -> extension.sources.any { it.id == sourceId }
ext.sources.any { it.id == sourceId } }
}
?.pkgName ?.pkgName
}
fun getExtensionPackageAsFlow(sourceId: Long): Flow<String?> {
return installedExtensionsFlow.map { extensions ->
extensions.find { extension ->
extension.sources.any { it.id == sourceId }
}
?.pkgName
}
}
fun getAppIconForSource(sourceId: Long): Drawable? {
val pkgName = getExtensionPackage(sourceId)
if (pkgName != null) { if (pkgName != null) {
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!! ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
@@ -25,6 +25,7 @@ class ExtensionInstallActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@Suppress("DEPRECATION")
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
.setDataAndType(intent.data, intent.type) .setDataAndType(intent.data, intent.type)
.putExtra(Intent.EXTRA_RETURN_RESULT, true) .putExtra(Intent.EXTRA_RETURN_RESULT, true)
@@ -313,6 +313,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return similarHandler.getRelated(manga) return similarHandler.getRelated(manga)
} }
suspend fun getMangaMetadata(track: Track): SManga? {
return mangaHandler.getMangaMetadata(track, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc())
}
companion object { companion object {
private const val dataSaverPref = "dataSaverV5" 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.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState 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.browse.source.sourcesTab
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import kotlinx.collections.immutable.persistentListOf 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.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
data class BrowseTab( data object BrowseTab : Tab {
private val toExtensions: Boolean = false,
) : Tab {
override val options: TabOptions override val options: TabOptions
@Composable @Composable
@@ -52,6 +55,12 @@ data class BrowseTab(
navigator.push(GlobalSearchScreen()) navigator.push(GlobalSearchScreen())
} }
private val switchToExtensionTabChannel = Channel<Unit>(1, BufferOverflow.DROP_OLDEST)
fun showExtension() {
switchToExtensionTabChannel.trySend(Unit)
}
@Composable @Composable
override fun Content() { override fun Content() {
val context = LocalContext.current val context = LocalContext.current
@@ -65,35 +74,43 @@ data class BrowseTab(
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() } val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
val extensionsState by extensionsScreenModel.state.collectAsState() 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( TabbedScreen(
titleRes = MR.strings.browse, titleRes = MR.strings.browse,
// SY --> tabs = tabs,
tabs = if (hideFeedTab) { state = state,
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 <--
searchQuery = extensionsState.searchQuery, searchQuery = extensionsState.searchQuery,
onChangeSearchQuery = extensionsScreenModel::search, onChangeSearchQuery = extensionsScreenModel::search,
) )
LaunchedEffect(Unit) {
switchToExtensionTabChannel.receiveAsFlow()
.collectLatest { state.scrollToPage(/* SY --> */2/* SY <-- */) }
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
(context as? MainActivity)?.ready = true (context as? MainActivity)?.ready = true
@@ -39,6 +39,7 @@ data class ExtensionDetailsScreen(
onClickClearCookies = screenModel::clearCookies, onClickClearCookies = screenModel::clearCookies,
onClickUninstall = screenModel::uninstallExtension, onClickUninstall = screenModel::uninstallExtension,
onClickSource = screenModel::toggleSource, onClickSource = screenModel::toggleSource,
onClickIncognito = screenModel::toggleIncognito,
) )
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -6,7 +6,9 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.extension.interactor.ExtensionSourceItem import eu.kanade.domain.extension.interactor.ExtensionSourceItem
import eu.kanade.domain.extension.interactor.GetExtensionSources import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.source.interactor.ToggleIncognito
import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@@ -19,6 +21,7 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@@ -36,6 +39,8 @@ class ExtensionDetailsScreenModel(
private val extensionManager: ExtensionManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(),
private val getExtensionSources: GetExtensionSources = Injekt.get(), private val getExtensionSources: GetExtensionSources = Injekt.get(),
private val toggleSource: ToggleSource = Injekt.get(), private val toggleSource: ToggleSource = Injekt.get(),
private val toggleIncognito: ToggleIncognito = Injekt.get(),
private val preferences: SourcePreferences = Injekt.get(),
) : StateScreenModel<ExtensionDetailsScreenModel.State>(State()) { ) : StateScreenModel<ExtensionDetailsScreenModel.State>(State()) {
private val _events: Channel<ExtensionDetailsEvent> = Channel() private val _events: Channel<ExtensionDetailsEvent> = Channel()
@@ -80,6 +85,15 @@ class ExtensionDetailsScreenModel(
} }
} }
} }
launch {
preferences.incognitoExtensions()
.changes()
.map { pkgName in it }
.distinctUntilChanged()
.collectLatest { isIncognito ->
mutableState.update { it.copy(isIncognito = isIncognito) }
}
}
} }
} }
@@ -118,9 +132,16 @@ class ExtensionDetailsScreenModel(
?.let { toggleSource.await(it, enable) } ?.let { toggleSource.await(it, enable) }
} }
fun toggleIncognito(enable: Boolean) {
state.value.extension?.pkgName?.let { packageName ->
toggleIncognito.await(packageName, enable)
}
}
@Immutable @Immutable
data class State( data class State(
val extension: Extension.Installed? = null, val extension: Extension.Installed? = null,
val isIncognito: Boolean = false,
private val _sources: ImmutableList<ExtensionSourceItem>? = null, private val _sources: ImmutableList<ExtensionSourceItem>? = null,
) { ) {
@@ -23,6 +23,7 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
@@ -77,6 +78,7 @@ open class FeedScreenModel(
getFeedSavedSearchGlobal.subscribe() getFeedSavedSearchGlobal.subscribe()
.distinctUntilChanged() .distinctUntilChanged()
.onEach { .onEach {
sourceManager.isInitialized.first { it }
val items = getSourcesToGetFeed(it).map { (feed, savedSearch) -> val items = getSourcesToGetFeed(it).map { (feed, savedSearch) ->
createCatalogueSearchItem( createCatalogueSearchItem(
feed = feed, feed = feed,
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
@@ -29,8 +27,7 @@ import tachiyomi.i18n.sy.SYMR
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() { class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
@delegate:Transient var newSelectedItem: Pair<Long, Long>? = null
var newSelectedItem by mutableStateOf<Pair<Long, Long>?>(null)
@Composable @Composable
override fun Content() { override fun Content() {
@@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow 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 eu.kanade.tachiyomi.ui.webview.WebViewScreen
import exh.ui.ifSourcesLoaded import exh.ui.ifSourcesLoaded
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@@ -71,7 +71,6 @@ data class SourceSearchScreen(
}, },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues -> ) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
val openMigrateDialog: (Manga) -> Unit = { val openMigrateDialog: (Manga) -> Unit = {
// SY --> // SY -->
navigator.items navigator.items
@@ -83,7 +82,7 @@ data class SourceSearchScreen(
} }
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(), mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode, ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -1,12 +1,15 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources package eu.kanade.tachiyomi.ui.browse.migration.sources
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.BrowseTabWrapper import eu.kanade.presentation.browse.BrowseTabWrapper
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
class MigrationSourcesScreen : Screen() { class MigrationSourcesScreen : Screen() {
@Composable @Composable
override fun Content() { override fun Content() {
BrowseTabWrapper(migrateSourceTab()) val navigator = LocalNavigator.currentOrThrow
BrowseTabWrapper(migrateSourceTab(), onBackPressed = navigator::pop)
} }
} }
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.ui.browse.source package eu.kanade.tachiyomi.ui.browse.source
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.BrowseTabWrapper import eu.kanade.presentation.browse.BrowseTabWrapper
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import java.io.Serializable import java.io.Serializable
@@ -8,7 +10,8 @@ import java.io.Serializable
class SourcesScreen(private val smartSearchConfig: SmartSearchConfig?) : Screen() { class SourcesScreen(private val smartSearchConfig: SmartSearchConfig?) : Screen() {
@Composable @Composable
override fun Content() { override fun Content() {
BrowseTabWrapper(sourcesTab(smartSearchConfig)) val navigator = LocalNavigator.currentOrThrow
BrowseTabWrapper(sourcesTab(smartSearchConfig), onBackPressed = navigator::pop)
} }
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Serializable data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Serializable
@@ -43,21 +43,25 @@ fun Screen.sourcesTab(
true -> MR.strings.label_sources true -> MR.strings.label_sources
false -> SYMR.strings.find_in_another_source false -> SYMR.strings.find_in_another_source
}, },
actions = if (smartSearchConfig == null) { actions = persistentListOf(
persistentListOf( AppBar.Action(
AppBar.Action( title = stringResource(MR.strings.action_global_search),
title = stringResource(MR.strings.action_global_search), icon = Icons.Outlined.TravelExplore,
icon = Icons.Outlined.TravelExplore, onClick = { navigator.push(GlobalSearchScreen(smartSearchConfig?.origTitle ?: "")) },
onClick = { navigator.push(GlobalSearchScreen()) }, ),
), ).let {
AppBar.Action( when (smartSearchConfig) {
title = stringResource(MR.strings.action_filter), null -> {
icon = Icons.Outlined.FilterList, it.add(
onClick = { navigator.push(SourcesFilterScreen()) }, AppBar.Action(
), title = stringResource(MR.strings.action_filter),
) icon = Icons.Outlined.FilterList,
} else { onClick = { navigator.push(SourcesFilterScreen()) },
persistentListOf() ),
)
}
else -> it
}
}, },
// SY <-- // SY <--
content = { contentPadding, snackbarHostState -> content = { contentPadding, snackbarHostState ->
@@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.InputChip import androidx.compose.material3.InputChip
import androidx.compose.material3.InputChipDefaults import androidx.compose.material3.InputChipDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -154,7 +155,7 @@ fun AutoCompleteTextField(
null null
}, },
modifier = Modifier modifier = Modifier
.menuAnchor() .menuAnchor(MenuAnchorType.PrimaryEditable)
.fillMaxWidth() .fillMaxWidth()
.runOnEnterKeyPressed { submit() }, .runOnEnterKeyPressed { submit() },
singleLine = true, singleLine = true,
@@ -32,7 +32,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@@ -61,6 +60,7 @@ import exh.ui.ifSourcesLoaded
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
@@ -75,7 +75,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
data class BrowseSourceScreen( data class BrowseSourceScreen(
private val sourceId: Long, val sourceId: Long,
private val listingQuery: String?, private val listingQuery: String?,
// SY --> // SY -->
private val filtersJson: String? = null, private val filtersJson: String? = null,
@@ -240,11 +240,9 @@ data class BrowseSourceScreen(
}, },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues -> ) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(), mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode, ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -15,10 +15,10 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.interactor.AddTracks import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
@@ -93,7 +93,6 @@ open class BrowseSourceScreenModel(
// SY <-- // SY <--
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
sourcePreferences: SourcePreferences = Injekt.get(), sourcePreferences: SourcePreferences = Injekt.get(),
basePreferences: BasePreferences = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val getRemoteManga: GetRemoteManga = Injekt.get(), private val getRemoteManga: GetRemoteManga = Injekt.get(),
@@ -105,6 +104,7 @@ open class BrowseSourceScreenModel(
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val addTracks: AddTracks = Injekt.get(), private val addTracks: AddTracks = Injekt.get(),
private val getIncognitoState: GetIncognitoState = Injekt.get(),
// SY --> // SY -->
unsortedPreferences: UnsortedPreferences = Injekt.get(), unsortedPreferences: UnsortedPreferences = Injekt.get(),
@@ -149,7 +149,7 @@ open class BrowseSourceScreenModel(
} }
} }
if (!basePreferences.incognitoMode().get()) { if (!getIncognitoState.await(source.id)) {
sourcePreferences.lastUsedSource().set(source.id) sourcePreferences.lastUsedSource().set(source.id)
} }
@@ -38,7 +38,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
object HistoryTab : Tab { data object HistoryTab : Tab {
private val snackbarHostState = SnackbarHostState() private val snackbarHostState = SnackbarHostState()
@@ -30,13 +30,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.navigator.tab.TabNavigator
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.core.util.fastFilter
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
@@ -73,11 +73,11 @@ object HomeScreen : Screen() {
private const val TAB_FADE_DURATION = 200 private const val TAB_FADE_DURATION = 200
private const val TAB_NAVIGATOR_KEY = "HomeTabs" private const val TAB_NAVIGATOR_KEY = "HomeTabs"
private val tabs = listOf( private val TABS = listOf(
LibraryTab, LibraryTab,
UpdatesTab, UpdatesTab,
HistoryTab, HistoryTab,
BrowseTab(), BrowseTab,
MoreTab, MoreTab,
) )
@@ -102,7 +102,7 @@ object HomeScreen : Screen() {
startBar = { startBar = {
if (isTabletUi()) { if (isTabletUi()) {
NavigationRail { NavigationRail {
tabs TABS
// SY --> // SY -->
.fastFilter { it.isEnabled() } .fastFilter { it.isEnabled() }
// SY <-- // SY <--
@@ -123,7 +123,7 @@ object HomeScreen : Screen() {
exit = shrinkVertically(), exit = shrinkVertically(),
) { ) {
NavigationBar { NavigationBar {
tabs TABS
// SY --> // SY -->
.fastFilter { it.isEnabled() } .fastFilter { it.isEnabled() }
// SY <-- // SY <--
@@ -179,7 +179,12 @@ object HomeScreen : Screen() {
is Tab.Library -> LibraryTab is Tab.Library -> LibraryTab
Tab.Updates -> UpdatesTab Tab.Updates -> UpdatesTab
Tab.History -> HistoryTab Tab.History -> HistoryTab
is Tab.Browse -> BrowseTab(it.toExtensions) is Tab.Browse -> {
if (it.toExtensions) {
BrowseTab.showExtension()
}
BrowseTab
}
is Tab.More -> MoreTab is Tab.More -> MoreTab
} }
@@ -7,16 +7,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny 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.fastForEach
import androidx.compose.ui.util.fastMap 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.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.PreferenceMutableState import eu.kanade.core.preference.PreferenceMutableState
import eu.kanade.core.preference.asState 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.fastFilterNot
import eu.kanade.core.util.fastMapNotNull
import eu.kanade.core.util.fastPartition import eu.kanade.core.util.fastPartition
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.interactor.SetReadStatus 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
object LibraryTab : Tab { data object LibraryTab : Tab {
override val options: TabOptions override val options: TabOptions
@Composable @Composable
@@ -165,7 +165,7 @@ object LibraryTab : Tab {
}, },
onClickSyncNow = { onClickSyncNow = {
if (!SyncDataJob.isRunning(context)) { if (!SyncDataJob.isRunning(context)) {
SyncDataJob.startNow(context) SyncDataJob.startNow(context, manual = true)
} else { } else {
context.toast(SYMR.strings.sync_in_progress) context.toast(SYMR.strings.sync_in_progress)
} }
@@ -334,8 +334,8 @@ object LibraryTab : Tab {
// SY --> // SY -->
SyncFavoritesProgressDialog( SyncFavoritesProgressDialog(
status = screenModel.favoritesSync.status.collectAsState().value, status = screenModel.favoritesSync.status.collectAsState().value,
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) }, setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle },
openManga = { navigator.push(MangaScreen(it.id)) }, openManga = { navigator.push(MangaScreen(it)) },
) )
// SY <-- // SY <--
@@ -54,6 +54,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import com.google.firebase.analytics.ktx.analytics import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
@@ -122,6 +123,8 @@ class MainActivity : BaseActivity() {
private val downloadCache: DownloadCache by injectLazy() private val downloadCache: DownloadCache by injectLazy()
private val chapterCache: ChapterCache by injectLazy() private val chapterCache: ChapterCache by injectLazy()
private val getIncognitoState: GetIncognitoState by injectLazy()
// To be checked by splash screen. If true then splash screen will be removed. // To be checked by splash screen. If true then splash screen will be removed.
var ready = false var ready = false
@@ -181,7 +184,7 @@ class MainActivity : BaseActivity() {
setComposeContent { setComposeContent {
val context = LocalContext.current val context = LocalContext.current
val incognito by preferences.incognitoMode().collectAsState() var incognito by remember { mutableStateOf(getIncognitoState.await(null)) }
val downloadOnly by preferences.downloadedOnly().collectAsState() val downloadOnly by preferences.downloadedOnly().collectAsState()
val indexing by downloadCache.isInitializing.collectAsState() val indexing by downloadCache.isInitializing.collectAsState()
@@ -231,6 +234,11 @@ class MainActivity : BaseActivity() {
// SY <-- // SY <--
} }
} }
LaunchedEffect(navigator.lastItem) {
(navigator.lastItem as? BrowseSourceScreen)?.sourceId
.let(getIncognitoState::subscribe)
.collectLatest { incognito = it }
}
val scaffoldInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) val scaffoldInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
Scaffold( Scaffold(
@@ -3,20 +3,26 @@ package eu.kanade.tachiyomi.ui.manga
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth 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.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children 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.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.R 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.databinding.EditMangaDialogBinding
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
import exh.util.dropBlank import exh.util.dropBlank
import exh.util.trimOrNull import exh.util.trimOrNull
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.manga.model.Manga 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.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable @Composable
fun EditMangaDialog( fun EditMangaDialog(
@@ -61,6 +79,10 @@ fun EditMangaDialog(
var binding by remember { var binding by remember {
mutableStateOf<EditMangaDialogBinding?>(null) 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( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
confirmButton = { confirmButton = {
@@ -109,7 +131,7 @@ fun EditMangaDialog(
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext)) EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
.also { binding = it } .also { binding = it }
.apply { .apply {
onViewCreated(manga, factoryContext, this, scope) onViewCreated(manga, factoryContext, this, scope, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
} }
.root .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) loadCover(manga, binding)
val statusAdapter: ArrayAdapter<String> = ArrayAdapter( 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.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
binding.resetInfo.setOnClickListener { resetInfo(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) { private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
@@ -40,7 +40,6 @@ import eu.kanade.presentation.manga.components.SetIntervalDialog
import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.isTabletUi import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.isLocalOrStub import eu.kanade.tachiyomi.source.isLocalOrStub
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@@ -59,13 +58,10 @@ import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.md.similar.MangaDexSimilarScreen
import exh.pagepreview.PagePreviewScreen import exh.pagepreview.PagePreviewScreen
import exh.pref.DelegateSourcePreferences
import exh.recs.RecommendsScreen import exh.recs.RecommendsScreen
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource import exh.source.getMainSource
import exh.source.isMdBasedSource
import exh.ui.ifSourcesLoaded import exh.ui.ifSourcesLoaded
import exh.ui.metadata.MetadataViewScreen import exh.ui.metadata.MetadataViewScreen
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@@ -214,7 +210,7 @@ class MangaScreen(
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) }, onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
onEditInfoClicked = screenModel::showEditMangaInfoDialog, onEditInfoClicked = screenModel::showEditMangaInfoDialog,
onRecommendClicked = { onRecommendClicked = {
openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) openRecommends(navigator, screenModel.source?.getMainSource(), successState.manga)
}, },
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog, onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
onMergeClicked = { openSmartSearch(navigator, successState.manga) }, onMergeClicked = { openSmartSearch(navigator, successState.manga) },
@@ -550,28 +546,9 @@ class MangaScreen(
// EXH <-- // EXH <--
// AZ --> // AZ -->
private fun openRecommends(context: Context, navigator: Navigator, source: Source?, manga: Manga) { private fun openRecommends(navigator: Navigator, source: Source?, manga: Manga) {
source ?: return source ?: return
if (source.isMdBasedSource() && Injekt.get<DelegateSourcePreferences>().delegateSources().get()) { navigator.push(RecommendsScreen(manga.id, source.id))
MaterialAlertDialogBuilder(context)
.setTitle(SYMR.strings.az_recommends.getString(context))
.setSingleChoiceItems(
arrayOf(
context.stringResource(SYMR.strings.mangadex_similar),
context.stringResource(SYMR.strings.community_recommendations),
),
-1,
) { dialog, index ->
dialog.dismiss()
when (index) {
0 -> navigator.push(MangaDexSimilarScreen(manga.id, source.id))
1 -> navigator.push(RecommendsScreen(manga.id, source.id))
}
}
.show()
} else if (source is CatalogueSource) {
navigator.push(RecommendsScreen(manga.id, source.id))
}
} }
// AZ <-- // AZ <--
} }
@@ -1263,16 +1263,14 @@ class MangaScreenModel(
*/ */
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) { fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
toggleAllSelection(false) toggleAllSelection(false)
if (chapters.isEmpty()) return
screenModelScope.launchIO { screenModelScope.launchIO {
setReadStatus.await( setReadStatus.await(
read = read, read = read,
chapters = chapters.toTypedArray(), chapters = chapters.toTypedArray(),
) )
if ( if (!read || successState?.hasLoggedInTrackers == false || autoTrackState == AutoTrackState.NEVER) {
successState?.hasLoggedInTrackers == false ||
!read || autoTrackState == AutoTrackState.NEVER
) {
return@launchIO return@launchIO
} }
@@ -42,7 +42,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
object MoreTab : Tab { data object MoreTab : Tab {
override val options: TabOptions override val options: TabOptions
@Composable @Composable
@@ -663,8 +663,10 @@ class ReaderActivity : BaseActivity() {
SurfaceColors.SURFACE_2.getColor(this), SurfaceColors.SURFACE_2.getColor(this),
if (isNightMode()) 230 else 242, // 90% dark 95% light if (isNightMode()) 230 else 242, // 90% dark 95% light
) )
@Suppress("DEPRECATION")
window.statusBarColor = toolbarColor window.statusBarColor = toolbarColor
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@Suppress("DEPRECATION")
window.navigationBarColor = toolbarColor window.navigationBarColor = toolbarColor
} }
@@ -14,6 +14,7 @@ import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.model.readerOrientation import eu.kanade.domain.manga.model.readerOrientation
import eu.kanade.domain.manga.model.readingMode import eu.kanade.domain.manga.model.readingMode
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.track.interactor.TrackChapter import eu.kanade.domain.track.interactor.TrackChapter
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
@@ -115,7 +116,6 @@ class ReaderViewModel @JvmOverloads constructor(
private val downloadProvider: DownloadProvider = Injekt.get(), private val downloadProvider: DownloadProvider = Injekt.get(),
private val tempFileManager: UniFileTempFileManager = Injekt.get(), private val tempFileManager: UniFileTempFileManager = Injekt.get(),
private val imageSaver: ImageSaver = Injekt.get(), private val imageSaver: ImageSaver = Injekt.get(),
preferences: BasePreferences = Injekt.get(),
val readerPreferences: ReaderPreferences = Injekt.get(), val readerPreferences: ReaderPreferences = Injekt.get(),
private val basePreferences: BasePreferences = Injekt.get(), private val basePreferences: BasePreferences = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(),
@@ -127,8 +127,9 @@ class ReaderViewModel @JvmOverloads constructor(
private val upsertHistory: UpsertHistory = Injekt.get(), private val upsertHistory: UpsertHistory = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(), private val updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(), private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
private val syncPreferences: SyncPreferences = Injekt.get(), private val getIncognitoState: GetIncognitoState = Injekt.get(),
// SY --> // SY -->
private val syncPreferences: SyncPreferences = Injekt.get(),
private val uiPreferences: UiPreferences = Injekt.get(), private val uiPreferences: UiPreferences = Injekt.get(),
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(), private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
private val getMergedMangaById: GetMergedMangaById = Injekt.get(), private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
@@ -264,7 +265,7 @@ class ReaderViewModel @JvmOverloads constructor(
.map(::ReaderChapter) .map(::ReaderChapter)
} }
private val incognitoMode = preferences.incognitoMode().get() private val incognitoMode: Boolean by lazy { getIncognitoState.await(manga?.source) }
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get() private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
init { init {
@@ -33,13 +33,16 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
import com.github.chrisbanes.photoview.PhotoView import com.github.chrisbanes.photoview.PhotoView
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.data.coil.cropBorders import eu.kanade.tachiyomi.data.coil.cropBorders
import eu.kanade.tachiyomi.data.coil.customDecoder import eu.kanade.tachiyomi.data.coil.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView 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.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/** /**
* A wrapper view for showing page image. * A wrapper view for showing page image.
@@ -57,6 +60,10 @@ open class ReaderPageImageView @JvmOverloads constructor(
private val isWebtoon: Boolean = false, private val isWebtoon: Boolean = false,
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) { ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
private val alwaysDecodeLongStripWithSSIV by lazy {
Injekt.get<BasePreferences>().alwaysDecodeLongStripWithSSIV().get()
}
private var pageView: View? = null private var pageView: View? = null
private var config: Config? = null private var config: Config? = null
@@ -116,21 +123,22 @@ open class ReaderPageImageView @JvmOverloads constructor(
} }
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) { private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
val config = config
if (config != null && if (config != null &&
config!!.landscapeZoom && config.landscapeZoom &&
config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE && config.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
sWidth > sHeight && sWidth > sHeight &&
scale == minScale scale == minScale
) { ) {
handler?.postDelayed(500) { 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.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F) ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
ZoomStartPosition.CENTER -> center ZoomStartPosition.CENTER -> center
} }
val targetScale = height.toFloat() / sHeight.toFloat() val targetScale = height.toFloat() / sHeight.toFloat()
animateScaleAndCenter(targetScale, point)!! (animateScaleAndCenter(targetScale, point) ?: return@postDelayed)
.withDuration(500) .withDuration(500)
.withEasing(EASE_IN_OUT_QUAD) .withEasing(EASE_IN_OUT_QUAD)
.withInterruptible(true) .withInterruptible(true)
@@ -232,7 +240,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} else { } else {
SubsamplingScaleImageView(context) SubsamplingScaleImageView(context)
}.apply { }.apply {
setMaxTileSize(GLUtil.maxTextureSize) setMaxTileSize(ImageUtil.hardwareBitmapThreshold)
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER) setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
setMinimumTileDpi(180) setMinimumTileDpi(180)
@@ -287,35 +295,44 @@ open class ReaderPageImageView @JvmOverloads constructor(
}, },
) )
if (isWebtoon) { when (data) {
val request = ImageRequest.Builder(context) is BitmapDrawable -> {
.data(data) setImage(ImageSource.bitmap(data.bitmap))
.memoryCachePolicy(CachePolicy.DISABLED) isVisible = true
.diskCachePolicy(CachePolicy.DISABLED) }
.target( is BufferedSource -> {
onSuccess = { result -> if (!isWebtoon || alwaysDecodeLongStripWithSSIV) {
val image = result as BitmapImage setHardwareConfig(ImageUtil.canUseHardwareBitmap(data))
setImage(ImageSource.bitmap(image.bitmap)) setImage(ImageSource.inputStream(data.inputStream()))
isVisible = true isVisible = true
}, return@apply
onError = { }
this@ReaderPageImageView.onImageLoadError()
}, ImageRequest.Builder(context)
) .data(data)
.size(ViewSizeResolver(this@ReaderPageImageView)) .memoryCachePolicy(CachePolicy.DISABLED)
.precision(Precision.INEXACT) .diskCachePolicy(CachePolicy.DISABLED)
.cropBorders(config.cropBorders) .target(
.customDecoder(true) onSuccess = { result ->
.crossfade(false) val image = result as BitmapImage
.build() setImage(ImageSource.bitmap(image.bitmap))
context.imageLoader.enqueue(request) isVisible = true
} else { },
when (data) { onError = {
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap)) onImageLoadError()
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream())) },
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}") )
.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 else -> oldCurrent?.first ?: return
} }
val index = when (newPage) { val index = when {
is ChapterTransition -> { newPage is ChapterTransition && joinedItems.none { it.first == newPage || it.second == newPage } -> {
val filteredPages = joinedItems.filter { val filteredPages = joinedItems.filter {
it.first is ReaderPage && it.first is ReaderPage &&
(it.first as ReaderPage).chapter == newPage.to (it.first as ReaderPage).chapter == newPage.to
@@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.ui.stats 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.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.fastCountNot 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.fastFilterNot
import eu.kanade.core.util.fastMapNotNull
import eu.kanade.presentation.more.stats.StatsScreenState import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.presentation.more.stats.data.StatsData import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.tachiyomi.data.download.DownloadManager 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
object UpdatesTab : Tab { data object UpdatesTab : Tab {
override val options: TabOptions override val options: TabOptions
@Composable @Composable
@@ -15,6 +15,7 @@ import androidx.core.content.getSystemService
import androidx.core.net.toUri import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
@@ -107,9 +108,13 @@ fun Context.createFileInCacheDir(name: String): File {
fun Context.createReaderThemeContext(): Context { fun Context.createReaderThemeContext(): Context {
val preferences = Injekt.get<UiPreferences>() val preferences = Injekt.get<UiPreferences>()
val readerPreferences = Injekt.get<ReaderPreferences>() val readerPreferences = Injekt.get<ReaderPreferences>()
val themeMode = preferences.themeMode().get()
val isDarkBackground = when (readerPreferences.readerTheme().get()) { val isDarkBackground = when (readerPreferences.readerTheme().get()) {
1, 2 -> true // Black, Gray 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 else -> false // White
} }
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO 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, track: eu.kanade.tachiyomi.data.database.models.Track,
epochMillis: Long, epochMillis: Long,
) = Unit ) = 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( suspend fun findAcceptedRootAndDiscardOthers(
sourceId: Long, sourceId: Long,
chapters: List<Chapter>, chapters: List<Chapter>,
): Triple<ChapterChain, List<ChapterChain>, Boolean> { ): Triple<ChapterChain, List<ChapterChain>, List<Chapter>> {
// Find other chains // Find other chains
val chains = chapters val chains = chapters
.flatMap { chapter -> .flatMap { chapter ->
@@ -149,7 +149,7 @@ class EHentaiUpdateHelper(context: Context) {
setMangaCategories.await(it.manga.id, newCategories) setMangaCategories.await(it.manga.id, newCategories)
} }
Triple(newAccepted, toDiscard, new) Triple(newAccepted, toDiscard, newChapters)
} else { } else {
/*val notNeeded = chains.filter { it.manga.id != accepted.manga.id } /*val notNeeded = chains.filter { it.manga.id != accepted.manga.id }
val (newChapters, new) = getChapterList(accepted, notNeeded, chainsAsChapters) val (newChapters, new) = getChapterList(accepted, notNeeded, chainsAsChapters)
@@ -158,7 +158,7 @@ class EHentaiUpdateHelper(context: Context) {
// Insert new chapters for accepted manga // Insert new chapters for accepted manga
db.insertChapters(newAccepted.chapters).await()*/ 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 package exh.eh
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.NetworkType import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.elvishew.xlog.Logger 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier 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.source.online.all.EHentai
import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isConnectedToWifi
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager import eu.kanade.tachiyomi.util.system.workManager
import exh.debug.DebugToggles import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
@@ -27,9 +33,11 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter 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_CHARGING
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
@@ -46,9 +54,10 @@ import kotlin.time.Duration.Companion.days
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) : class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
private val preferences: UnsortedPreferences by injectLazy() private val preferences: UnsortedPreferences by injectLazy()
private val libraryPreferences: LibraryPreferences by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val updateHelper: EHentaiUpdateHelper 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 updateManga: UpdateManga by injectLazy()
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy() private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
private val getChaptersByMangaId: GetChaptersByMangaId 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 insertFlatMetadata: InsertFlatMetadata by injectLazy()
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata 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 { override suspend fun doWork(): Result {
return try { return try {
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) { if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
Result.success() // retry again later Result.success() // retry again later
} else { } else {
setForegroundSafely()
startUpdating() startUpdating()
logger.d("Update job completed!") logger.d("Update job completed!")
Result.success() Result.success()
} }
} catch (e: Exception) { } catch (e: Exception) {
Result.success() // retry again later 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() { private suspend fun startUpdating() {
logger.d("Update job started!") logger.d("Update job started!")
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
@@ -138,6 +163,11 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
} }
val (new, chapters) = try { val (new, chapters) = try {
updateNotifier.showProgressNotification(
manga,
updatedThisIteration + failuresThisIteration,
mangaMetaToUpdateThisIter.size,
)
updateEntryAndGetChapters(manga) updateEntryAndGetChapters(manga)
} catch (e: GalleryNotUpdatedException) { } catch (e: GalleryNotUpdatedException) {
if (e.network) { if (e.network) {
@@ -169,13 +199,15 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
} }
// Find accepted root and discard others // Find accepted root and discard others
val (acceptedRoot, discardedRoots, hasNew) = val (acceptedRoot, discardedRoots, exhNew) =
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters) updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) || if (new.isNotEmpty() && manga.id == acceptedRoot.manga.id) {
(hasNew && updatedManga.none { it.first.id == acceptedRoot.manga.id }) libraryPreferences.newUpdatesCount().getAndSet { it + new.size }
) {
updatedManga += acceptedRoot.manga to new.toTypedArray() 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 modifiedThisIteration += acceptedRoot.manga.id
@@ -193,8 +225,9 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
), ),
) )
updateNotifier.cancelProgressNotification()
if (updatedManga.isNotEmpty()) { 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") } private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
fun launchBackgroundTest(context: Context) { 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) { fun scheduleBackground(context: Context, prefInterval: Int? = null, prefRestrictions: Set<String>? = null) {
@@ -92,7 +92,7 @@ class MemAutoFlushingLookupTable<T>(
val size = bb.getInt(4) val size = bb.getInt(4)
val strBArr = ByteArray(size) val strBArr = ByteArray(size)
if (!input.requireBytes(strBArr, size)) break 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) { } catch (e: FileNotFoundException) {
@@ -131,7 +131,7 @@ class MemAutoFlushingLookupTable<T>(
try { try {
val out = fos.sink().buffer() val out = fos.sink().buffer()
table.forEach { key, value -> 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(0, key)
bb.putInt(4, v.size) bb.putInt(4, v.size)
out.write(bb.array()) 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:abby dark star",
"cosplayer:adamae-dono", "cosplayer:adamae-dono",
"cosplayer:ai lei jiang", "cosplayer:ai lei jiang",
"cosplayer:ai xi",
"cosplayer:aiga mizuki", "cosplayer:aiga mizuki",
"cosplayer:aimy",
"cosplayer:aizawa ren", "cosplayer:aizawa ren",
"cosplayer:ajo cosplay", "cosplayer:ajo cosplay",
"cosplayer:akane araragi", "cosplayer:akane araragi",
"cosplayer:akari yamazaki",
"cosplayer:akemi101xoxo", "cosplayer:akemi101xoxo",
"cosplayer:akiba cute star", "cosplayer:akiba cute star",
"cosplayer:akitsu honoka", "cosplayer:akitsu honoka",
"cosplayer:aleksandra bodler", "cosplayer:aleksandra bodler",
"cosplayer:aleksandra lerman",
"cosplayer:aleksis hitc", "cosplayer:aleksis hitc",
"cosplayer:alexis lust", "cosplayer:alexis lust",
"cosplayer:alice bong", "cosplayer:alice bong",
"cosplayer:alice cosplay", "cosplayer:alice cosplay",
"cosplayer:alice wonder",
"cosplayer:aliceholic",
"cosplayer:alicekyo", "cosplayer:alicekyo",
"cosplayer:alin ma", "cosplayer:alin ma",
"cosplayer:alisa arkhangelskaya",
"cosplayer:alisa kiss", "cosplayer:alisa kiss",
"cosplayer:alisa valeeva",
"cosplayer:alodia gosiengfiao", "cosplayer:alodia gosiengfiao",
"cosplayer:alva velasco",
"cosplayer:alycia elvie", "cosplayer:alycia elvie",
"cosplayer:amanda welp", "cosplayer:amanda welp",
"cosplayer:amber hallibell", "cosplayer:amber hallibell",
@@ -31,16 +40,20 @@ object Cosplayer : TagList {
"cosplayer:anabelle joy", "cosplayer:anabelle joy",
"cosplayer:anastasia komori", "cosplayer:anastasia komori",
"cosplayer:angelina preobrazhenskaya", "cosplayer:angelina preobrazhenskaya",
"cosplayer:aniela verbin",
"cosplayer:aninnyan", "cosplayer:aninnyan",
"cosplayer:anizu chie", "cosplayer:anizu chie",
"cosplayer:anna kuramoto", "cosplayer:anna kuramoto",
"cosplayer:annie seixas", "cosplayer:annie seixas",
"cosplayer:anxi",
"cosplayer:aokotan", "cosplayer:aokotan",
"cosplayer:arai yomi", "cosplayer:arai yomi",
"cosplayer:araki mai", "cosplayer:araki mai",
"cosplayer:ari.anna",
"cosplayer:arisa mizuhara", "cosplayer:arisa mizuhara",
"cosplayer:arty huang", "cosplayer:arty huang",
"cosplayer:asakawa ran", "cosplayer:asakawa ran",
"cosplayer:asakura kotomi",
"cosplayer:asakura naho", "cosplayer:asakura naho",
"cosplayer:ashiya noriko", "cosplayer:ashiya noriko",
"cosplayer:astasiadream", "cosplayer:astasiadream",
@@ -48,28 +61,39 @@ object Cosplayer : TagList {
"cosplayer:atina", "cosplayer:atina",
"cosplayer:atsuki", "cosplayer:atsuki",
"cosplayer:audalove", "cosplayer:audalove",
"cosplayer:aveline tetsuya",
"cosplayer:ayaka matsunaga", "cosplayer:ayaka matsunaga",
"cosplayer:bailey jay", "cosplayer:bailey jay",
"cosplayer:banbanko", "cosplayer:banbanko",
"cosplayer:beibei kappu",
"cosplayer:bella bunbun",
"cosplayer:bili bili", "cosplayer:bili bili",
"cosplayer:bishoujomom", "cosplayer:bishoujomom",
"cosplayer:blacqkl",
"cosplayer:bloodraven", "cosplayer:bloodraven",
"cosplayer:bobbi starr", "cosplayer:bobbi starr",
"cosplayer:bonn1ethebunny", "cosplayer:bonn1ethebunny",
"cosplayer:bonnie bonkers", "cosplayer:bonnie bonkers",
"cosplayer:boople snoot", "cosplayer:boople snoot",
"cosplayer:breesknees",
"cosplayer:bukkitbrown",
"cosplayer:bunny ayumi", "cosplayer:bunny ayumi",
"cosplayer:carry key", "cosplayer:carry key",
"cosplayer:chadkasa", "cosplayer:chadkasa",
"cosplayer:charess",
"cosplayer:charles dera", "cosplayer:charles dera",
"cosplayer:chisai cosplay",
"cosplayer:chokoboll mukakoi.", "cosplayer:chokoboll mukakoi.",
"cosplayer:christina volkova", "cosplayer:christina volkova",
"cosplayer:chunmomo", "cosplayer:chunmomo",
"cosplayer:cocopie", "cosplayer:cocopie",
"cosplayer:comonun", "cosplayer:comonun",
"cosplayer:cristina luise",
"cosplayer:dakko ja rrs",
"cosplayer:dani doe", "cosplayer:dani doe",
"cosplayer:danielle beaulieu", "cosplayer:danielle beaulieu",
"cosplayer:danielle vedovelli", "cosplayer:danielle vedovelli",
"cosplayer:daria kravets",
"cosplayer:darling cute", "cosplayer:darling cute",
"cosplayer:dessyy", "cosplayer:dessyy",
"cosplayer:dillion harper", "cosplayer:dillion harper",
@@ -78,39 +102,56 @@ object Cosplayer : TagList {
"cosplayer:donnaloli", "cosplayer:donnaloli",
"cosplayer:eira wang", "cosplayer:eira wang",
"cosplayer:electricbum", "cosplayer:electricbum",
"cosplayer:elena lenina",
"cosplayer:elena yuna",
"cosplayer:elisa cattabriga",
"cosplayer:elyhria",
"cosplayer:erin eevee", "cosplayer:erin eevee",
"cosplayer:erotic doki",
"cosplayer:eroticneko", "cosplayer:eroticneko",
"cosplayer:esther rutkovskaya-tudor",
"cosplayer:eunji pyo", "cosplayer:eunji pyo",
"cosplayer:evawxsh", "cosplayer:evawxsh",
"cosplayer:evenink", "cosplayer:evenink",
"cosplayer:evie evangelion", "cosplayer:evie evangelion",
"cosplayer:ezy summers", "cosplayer:ezy summers",
"cosplayer:fatiaoliii",
"cosplayer:fay sg",
"cosplayer:fe galvao", "cosplayer:fe galvao",
"cosplayer:felvelial", "cosplayer:felvelial",
"cosplayer:feng jiang jiang",
"cosplayer:ferin feirn", "cosplayer:ferin feirn",
"cosplayer:feywilde", "cosplayer:feywilde",
"cosplayer:firtsbornunicorn",
"cosplayer:flora daria", "cosplayer:flora daria",
"cosplayer:fluffy nemu", "cosplayer:fluffy nemu",
"cosplayer:franxcos", "cosplayer:franxcos",
"cosplayer:frauleinmilk", "cosplayer:frauleinmilk",
"cosplayer:fubuki ami",
"cosplayer:fuji serika", "cosplayer:fuji serika",
"cosplayer:futaba emiru",
"cosplayer:g44 wa kizutsukanai", "cosplayer:g44 wa kizutsukanai",
"cosplayer:garo dazay", "cosplayer:garo dazay",
"cosplayer:generic egirl",
"cosplayer:genthehobbit", "cosplayer:genthehobbit",
"cosplayer:ghostly cosplay", "cosplayer:ghostly cosplay",
"cosplayer:giorgia vecchini",
"cosplayer:giulia valeriani", "cosplayer:giulia valeriani",
"cosplayer:goth egg", "cosplayer:goth egg",
"cosplayer:gumiho hannya", "cosplayer:gumiho hannya",
"cosplayer:guo chengzi", "cosplayer:guo chengzi",
"cosplayer:hakuhi kaede", "cosplayer:hakuhi kaede",
"cosplayer:hamasaki rio",
"cosplayer:han yeri", "cosplayer:han yeri",
"cosplayer:hanamura misaki", "cosplayer:hanamura misaki",
"cosplayer:hane ame", "cosplayer:hane ame",
"cosplayer:harukaism",
"cosplayer:helly von valentine", "cosplayer:helly von valentine",
"cosplayer:hessakai", "cosplayer:hessakai",
"cosplayer:hey shika", "cosplayer:hey shika",
"cosplayer:higurashi rin", "cosplayer:higurashi rin",
"cosplayer:himeecosplay", "cosplayer:himeecosplay",
"cosplayer:hinatasama",
"cosplayer:hinaughtya", "cosplayer:hinaughtya",
"cosplayer:hiyo nishizuku", "cosplayer:hiyo nishizuku",
"cosplayer:holly ava", "cosplayer:holly ava",
@@ -121,18 +162,30 @@ object Cosplayer : TagList {
"cosplayer:imokawa naoko", "cosplayer:imokawa naoko",
"cosplayer:ino cosplay", "cosplayer:ino cosplay",
"cosplayer:iori moe", "cosplayer:iori moe",
"cosplayer:iri",
"cosplayer:ishikawa asami", "cosplayer:ishikawa asami",
"cosplayer:jannet kat",
"cosplayer:jannet vinogradova",
"cosplayer:jasming chea",
"cosplayer:jaycee", "cosplayer:jaycee",
"cosplayer:jenezial",
"cosplayer:jenna lynn meowri", "cosplayer:jenna lynn meowri",
"cosplayer:jenni kaellberg",
"cosplayer:jessica nigri", "cosplayer:jessica nigri",
"cosplayer:jessika jinx",
"cosplayer:jill", "cosplayer:jill",
"cosplayer:jinxie", "cosplayer:jinxie",
"cosplayer:jiuqujean", "cosplayer:jiuqujean",
"cosplayer:jiuyan",
"cosplayer:jiyun choi", "cosplayer:jiyun choi",
"cosplayer:joanna muller",
"cosplayer:julia larangeiras",
"cosplayer:julia shuenkova", "cosplayer:julia shuenkova",
"cosplayer:jun ye tako", "cosplayer:jun ye tako",
"cosplayer:kae kaieda", "cosplayer:kae kaieda",
"cosplayer:kalinka fox", "cosplayer:kalinka fox",
"cosplayer:kamelya chan",
"cosplayer:kamijiri ichigo",
"cosplayer:kamui alice", "cosplayer:kamui alice",
"cosplayer:kanda likitsangjaroen", "cosplayer:kanda likitsangjaroen",
"cosplayer:kanda midori", "cosplayer:kanda midori",
@@ -141,6 +194,7 @@ object Cosplayer : TagList {
"cosplayer:katiecakey", "cosplayer:katiecakey",
"cosplayer:kay bear", "cosplayer:kay bear",
"cosplayer:kaya huang", "cosplayer:kaya huang",
"cosplayer:kei shino",
"cosplayer:khainsaw", "cosplayer:khainsaw",
"cosplayer:kibashi", "cosplayer:kibashi",
"cosplayer:kiera marie", "cosplayer:kiera marie",
@@ -159,19 +213,27 @@ object Cosplayer : TagList {
"cosplayer:kotea dali", "cosplayer:kotea dali",
"cosplayer:koyama rikako", "cosplayer:koyama rikako",
"cosplayer:kqueentsun", "cosplayer:kqueentsun",
"cosplayer:kurasaka kururu",
"cosplayer:kurumi.", "cosplayer:kurumi.",
"cosplayer:kururugi aoi", "cosplayer:kururugi aoi",
"cosplayer:kuuko w", "cosplayer:kuuko w",
"cosplayer:kyonatix", "cosplayer:kyonatix",
"cosplayer:lagertha",
"cosplayer:lea martinez",
"cosplayer:lelewu", "cosplayer:lelewu",
"cosplayer:lenfried", "cosplayer:lenfried",
"cosplayer:lewdoart", "cosplayer:lewdoart",
"cosplayer:lex kuma",
"cosplayer:lightz", "cosplayer:lightz",
"cosplayer:lili erlih",
"cosplayer:lilly rose", "cosplayer:lilly rose",
"cosplayer:lilya victorovna", "cosplayer:lilya victorovna",
"cosplayer:lilylit", "cosplayer:lilylit",
"cosplayer:lina erdel",
"cosplayer:ling li",
"cosplayer:linneas life", "cosplayer:linneas life",
"cosplayer:linzi jiang", "cosplayer:linzi jiang",
"cosplayer:little blue girl",
"cosplayer:lizyhsan", "cosplayer:lizyhsan",
"cosplayer:lmusicl", "cosplayer:lmusicl",
"cosplayer:lovelyspacekitten", "cosplayer:lovelyspacekitten",
@@ -180,11 +242,16 @@ object Cosplayer : TagList {
"cosplayer:mags.irl", "cosplayer:mags.irl",
"cosplayer:mais conheyo", "cosplayer:mais conheyo",
"cosplayer:manyu hanausagi", "cosplayer:manyu hanausagi",
"cosplayer:mappy sanchez",
"cosplayer:marie-claude bourbonnais", "cosplayer:marie-claude bourbonnais",
"cosplayer:mariigabii", "cosplayer:mariigabii",
"cosplayer:masako yume",
"cosplayer:meagan vanburkleo",
"cosplayer:mei succubus",
"cosplayer:meikoui", "cosplayer:meikoui",
"cosplayer:meriol-chan", "cosplayer:meriol-chan",
"cosplayer:mianbing xianer", "cosplayer:mianbing xianer",
"cosplayer:micro kitty",
"cosplayer:miih cosplay", "cosplayer:miih cosplay",
"cosplayer:miiya", "cosplayer:miiya",
"cosplayer:mik allen", "cosplayer:mik allen",
@@ -192,18 +259,27 @@ object Cosplayer : TagList {
"cosplayer:milena hime", "cosplayer:milena hime",
"cosplayer:milky", "cosplayer:milky",
"cosplayer:mimi-chan", "cosplayer:mimi-chan",
"cosplayer:mimmi",
"cosplayer:mimo ningyo", "cosplayer:mimo ningyo",
"cosplayer:minaduki miri", "cosplayer:minaduki miri",
"cosplayer:minato riku",
"cosplayer:minematsu rie", "cosplayer:minematsu rie",
"cosplayer:mingchudesu",
"cosplayer:mingming kizami", "cosplayer:mingming kizami",
"cosplayer:mingtao", "cosplayer:mingtao",
"cosplayer:minzy tea", "cosplayer:minzy tea",
"cosplayer:miorin",
"cosplayer:misaco", "cosplayer:misaco",
"cosplayer:mishka bear",
"cosplayer:missbrisolo", "cosplayer:missbrisolo",
"cosplayer:misty silver",
"cosplayer:mitsuki riyu",
"cosplayer:miura aika", "cosplayer:miura aika",
"cosplayer:miyoki", "cosplayer:miyoki",
"cosplayer:mizhimaoqiu", "cosplayer:mizhimaoqiu",
"cosplayer:mizuki akira",
"cosplayer:mochichuu", "cosplayer:mochichuu",
"cosplayer:mochimochi-nn",
"cosplayer:mochizuki eiko", "cosplayer:mochizuki eiko",
"cosplayer:mochizuki kanade", "cosplayer:mochizuki kanade",
"cosplayer:moiicos", "cosplayer:moiicos",
@@ -211,8 +287,11 @@ object Cosplayer : TagList {
"cosplayer:momoiro reku", "cosplayer:momoiro reku",
"cosplayer:momoko", "cosplayer:momoko",
"cosplayer:momokun", "cosplayer:momokun",
"cosplayer:momousagi mao",
"cosplayer:moody feet", "cosplayer:moody feet",
"cosplayer:morgana cosplay", "cosplayer:morgana cosplay",
"cosplayer:mowky",
"cosplayer:moyu mommy",
"cosplayer:mozuku kimura", "cosplayer:mozuku kimura",
"cosplayer:mu zhi ben lan", "cosplayer:mu zhi ben lan",
"cosplayer:murasaki", "cosplayer:murasaki",
@@ -222,7 +301,11 @@ object Cosplayer : TagList {
"cosplayer:nadyasonika", "cosplayer:nadyasonika",
"cosplayer:nagisa", "cosplayer:nagisa",
"cosplayer:nan tao momoko", "cosplayer:nan tao momoko",
"cosplayer:natalya ditrikh",
"cosplayer:natasha roik", "cosplayer:natasha roik",
"cosplayer:nateephan thammasilbanyad",
"cosplayer:nawo019",
"cosplayer:nayfi bardales",
"cosplayer:nekob0icarti", "cosplayer:nekob0icarti",
"cosplayer:neroko kaigan", "cosplayer:neroko kaigan",
"cosplayer:niannian d", "cosplayer:niannian d",
@@ -235,58 +318,81 @@ object Cosplayer : TagList {
"cosplayer:nonbinate", "cosplayer:nonbinate",
"cosplayer:nora fawn", "cosplayer:nora fawn",
"cosplayer:nuko meguro", "cosplayer:nuko meguro",
"cosplayer:nuria gonzalez",
"cosplayer:nyako", "cosplayer:nyako",
"cosplayer:nymph-princess",
"cosplayer:octokuro", "cosplayer:octokuro",
"cosplayer:odoru neko ningen", "cosplayer:odoru neko ningen",
"cosplayer:oharucosplay", "cosplayer:oharucosplay",
"cosplayer:oichi", "cosplayer:oichi",
"cosplayer:okada yui", "cosplayer:okada yui",
"cosplayer:oki-cospi", "cosplayer:oki-cospi",
"cosplayer:olivia metric",
"cosplayer:olyashaa saxon", "cosplayer:olyashaa saxon",
"cosplayer:pattie cosplay", "cosplayer:pattie cosplay",
"cosplayer:pattycake", "cosplayer:pattycake",
"cosplayer:peachtot", "cosplayer:peachtot",
"cosplayer:penkarui", "cosplayer:penkarui",
"cosplayer:penny walsh",
"cosplayer:pichapu",
"cosplayer:pokket", "cosplayer:pokket",
"cosplayer:poon warunya", "cosplayer:poon warunya",
"cosplayer:punk macarroni", "cosplayer:punk macarroni",
"cosplayer:purrblind",
"cosplayer:pushiku", "cosplayer:pushiku",
"cosplayer:qiqi nanazi", "cosplayer:qiqi nanazi",
"cosplayer:qiqi xiaojie", "cosplayer:qiqi xiaojie",
"cosplayer:qiuhe keji", "cosplayer:qiuhe keji",
"cosplayer:queenie", "cosplayer:queenie",
"cosplayer:quist",
"cosplayer:rachel ravaged",
"cosplayer:rakuraku",
"cosplayer:ravvcoser", "cosplayer:ravvcoser",
"cosplayer:raynearts", "cosplayer:raynearts",
"cosplayer:rea kami", "cosplayer:rea kami",
"cosplayer:renee storm",
"cosplayer:rhylee passfield", "cosplayer:rhylee passfield",
"cosplayer:ri care", "cosplayer:ri care",
"cosplayer:riani haratina",
"cosplayer:rinami", "cosplayer:rinami",
"cosplayer:ringo mitsuki",
"cosplayer:rinoa",
"cosplayer:rio-chan", "cosplayer:rio-chan",
"cosplayer:rioko", "cosplayer:rioko",
"cosplayer:rissoft",
"cosplayer:rocksy light", "cosplayer:rocksy light",
"cosplayer:rolyatistaylor", "cosplayer:rolyatistaylor",
"cosplayer:rongrongzi", "cosplayer:rongrongzi",
"cosplayer:rusuwu",
"cosplayer:sachi budou", "cosplayer:sachi budou",
"cosplayer:saiwari ph", "cosplayer:saiwari ph",
"cosplayer:saki kawanami",
"cosplayer:saku", "cosplayer:saku",
"cosplayer:sakura ema", "cosplayer:sakura ema",
"cosplayer:sakurai", "cosplayer:sakurai",
"cosplayer:sakurai hinoki", "cosplayer:sakurai hinoki",
"cosplayer:samantha boon",
"cosplayer:sandykuroneko", "cosplayer:sandykuroneko",
"cosplayer:saotome love", "cosplayer:saotome love",
"cosplayer:sara underwood", "cosplayer:sara underwood",
"cosplayer:sarah quillian", "cosplayer:sarah quillian",
"cosplayer:sarawrcosplay",
"cosplayer:sasaki remi", "cosplayer:sasaki remi",
"cosplayer:sato yuri",
"cosplayer:savannah sixx", "cosplayer:savannah sixx",
"cosplayer:sawaka", "cosplayer:sawaka",
"cosplayer:scarlett afterdark", "cosplayer:scarlett afterdark",
"cosplayer:sean lawless", "cosplayer:sean lawless",
"cosplayer:sei",
"cosplayer:seltin sweet", "cosplayer:seltin sweet",
"cosplayer:sena",
"cosplayer:sexy toys",
"cosplayer:sexyflowerwater", "cosplayer:sexyflowerwater",
"cosplayer:sharkparty", "cosplayer:sharkparty",
"cosplayer:shermie", "cosplayer:shermie",
"cosplayer:shibuya kaho", "cosplayer:shibuya kaho",
"cosplayer:shimizu yuno", "cosplayer:shimizu yuno",
"cosplayer:shinen",
"cosplayer:shiro kitsune", "cosplayer:shiro kitsune",
"cosplayer:shiroluxx", "cosplayer:shiroluxx",
"cosplayer:siao ding", "cosplayer:siao ding",
@@ -295,14 +401,24 @@ object Cosplayer : TagList {
"cosplayer:soa lianna", "cosplayer:soa lianna",
"cosplayer:son yeeun", "cosplayer:son yeeun",
"cosplayer:sophie snomster", "cosplayer:sophie snomster",
"cosplayer:stanislava anushkina",
"cosplayer:starfmodel",
"cosplayer:stelar hoshi", "cosplayer:stelar hoshi",
"cosplayer:sunny lin",
"cosplayer:sunny ray", "cosplayer:sunny ray",
"cosplayer:sunnyvier", "cosplayer:sunnyvier",
"cosplayer:sunohara miki", "cosplayer:sunohara miki",
"cosplayer:sushiflavoredmilk",
"cosplayer:suspira grey",
"cosplayer:tachibana remika",
"cosplayer:tanaka hitomi", "cosplayer:tanaka hitomi",
"cosplayer:tanaka mana",
"cosplayer:tangtang", "cosplayer:tangtang",
"cosplayer:tanja kensinger", "cosplayer:tanja kensinger",
"cosplayer:tao liang azhai",
"cosplayer:tara nicole azarian",
"cosplayer:tasha leigh", "cosplayer:tasha leigh",
"cosplayer:tatiana neva",
"cosplayer:tenleid", "cosplayer:tenleid",
"cosplayer:tenryu-0", "cosplayer:tenryu-0",
"cosplayer:tenshi myu.", "cosplayer:tenshi myu.",
@@ -310,8 +426,10 @@ object Cosplayer : TagList {
"cosplayer:tiffany gordon", "cosplayer:tiffany gordon",
"cosplayer:tiny asa", "cosplayer:tiny asa",
"cosplayer:todopokie", "cosplayer:todopokie",
"cosplayer:tristan valdez",
"cosplayer:tsubaki zakuro", "cosplayer:tsubaki zakuro",
"cosplayer:tsuki desu", "cosplayer:tsuki desu",
"cosplayer:tsuki miko",
"cosplayer:tsukimiya madoka", "cosplayer:tsukimiya madoka",
"cosplayer:tsuyato", "cosplayer:tsuyato",
"cosplayer:turkish chi-chi", "cosplayer:turkish chi-chi",
@@ -320,34 +438,49 @@ object Cosplayer : TagList {
"cosplayer:ukyuu nako", "cosplayer:ukyuu nako",
"cosplayer:una cosplayer", "cosplayer:una cosplayer",
"cosplayer:uno megumi", "cosplayer:uno megumi",
"cosplayer:ur senpai june",
"cosplayer:uru uruu", "cosplayer:uru uruu",
"cosplayer:uta kohaku",
"cosplayer:valery himera", "cosplayer:valery himera",
"cosplayer:velvet", "cosplayer:velvet",
"cosplayer:veroodle", "cosplayer:veroodle",
"cosplayer:vlada lutsak",
"cosplayer:wanco chan", "cosplayer:wanco chan",
"cosplayer:whimpercat",
"cosplayer:wifey", "cosplayer:wifey",
"cosplayer:wildhoney423", "cosplayer:wildhoney423",
"cosplayer:xansoon", "cosplayer:xansoon",
"cosplayer:xia xia zi", "cosplayer:xia xia zi",
"cosplayer:xiaoyao yaoyao",
"cosplayer:xiaoying shi zhi xiaomulong",
"cosplayer:xidaidai", "cosplayer:xidaidai",
"cosplayer:xue qi-sama", "cosplayer:xue qi-sama",
"cosplayer:yaki", "cosplayer:yaki",
"cosplayer:yaokoututu", "cosplayer:yaokoututu",
"cosplayer:yaoyaoqwq", "cosplayer:yaoyaoqwq",
"cosplayer:ying lili",
"cosplayer:yoko inui",
"cosplayer:yor succubus", "cosplayer:yor succubus",
"cosplayer:yorkie w", "cosplayer:yorkie w",
"cosplayer:youyou",
"cosplayer:yuki astra", "cosplayer:yuki astra",
"cosplayer:yuki lefay",
"cosplayer:yuki teyi", "cosplayer:yuki teyi",
"cosplayer:yume", "cosplayer:yume",
"cosplayer:yummykimmy",
"cosplayer:yunie lannister", "cosplayer:yunie lannister",
"cosplayer:yunocos69", "cosplayer:yunocos69",
"cosplayer:yurihime", "cosplayer:yurihime",
"cosplayer:yuumeilyn", "cosplayer:yuumeilyn",
"cosplayer:yuyunte", "cosplayer:yuyunte",
"cosplayer:yuzu chan",
"cosplayer:yuzuki", "cosplayer:yuzuki",
"cosplayer:yuzukimiiu",
"cosplayer:yuzupyon", "cosplayer:yuzupyon",
"cosplayer:zara durose", "cosplayer:zara durose",
"cosplayer:zeico", "cosplayer:zeico",
"cosplayer:zhenya zhuk",
"cosplayer:zhuimingyou",
"cosplayer:zyunka mukhina", "cosplayer:zyunka mukhina",
) )
} }
+8
View File
@@ -63,7 +63,9 @@ object Female : TagList {
"female:big vagina", "female:big vagina",
"female:bike shorts", "female:bike shorts",
"female:bikini", "female:bikini",
"female:bird girl",
"female:bisexual", "female:bisexual",
"female:bite mark",
"female:blackmail", "female:blackmail",
"female:blind", "female:blind",
"female:blindfold", "female:blindfold",
@@ -120,8 +122,10 @@ object Female : TagList {
"female:cockslapping", "female:cockslapping",
"female:collar", "female:collar",
"female:condom", "female:condom",
"female:confinement",
"female:conjoined", "female:conjoined",
"female:coprophagia", "female:coprophagia",
"female:corpse",
"female:corruption", "female:corruption",
"female:corset", "female:corset",
"female:cosplaying", "female:cosplaying",
@@ -218,6 +222,7 @@ object Female : TagList {
"female:frog", "female:frog",
"female:frog girl", "female:frog girl",
"female:frottage", "female:frottage",
"female:full tour",
"female:full-packaged futanari", "female:full-packaged futanari",
"female:fundoshi", "female:fundoshi",
"female:furry", "female:furry",
@@ -299,6 +304,7 @@ object Female : TagList {
"female:kindergarten uniform", "female:kindergarten uniform",
"female:kissing", "female:kissing",
"female:kneepit sex", "female:kneepit sex",
"female:kodomo doushi",
"female:kunoichi", "female:kunoichi",
"female:lab coat", "female:lab coat",
"female:lactation", "female:lactation",
@@ -319,6 +325,7 @@ object Female : TagList {
"female:long tongue", "female:long tongue",
"female:low bestiality", "female:low bestiality",
"female:low guro", "female:low guro",
"female:low incest",
"female:low lolicon", "female:low lolicon",
"female:low scat", "female:low scat",
"female:low smegma", "female:low smegma",
@@ -369,6 +376,7 @@ object Female : TagList {
"female:muscle growth", "female:muscle growth",
"female:mute", "female:mute",
"female:nakadashi", "female:nakadashi",
"female:navel birth",
"female:navel fuck", "female:navel fuck",
"female:nazi", "female:nazi",
"female:necrophilia", "female:necrophilia",
+119 -119
View File
@@ -15,6 +15,7 @@ object Group : TagList {
"group:008", "group:008",
"group:0123456789", "group:0123456789",
"group:0909", "group:0909",
"group:1 equals 1ziz",
"group:1 slash 0 kansokujo", "group:1 slash 0 kansokujo",
"group:1-up", "group:1-up",
"group:10 slash 19", "group:10 slash 19",
@@ -336,7 +337,6 @@ object Group : TagList {
"group:alice-do", "group:alice-do",
"group:alice.blood", "group:alice.blood",
"group:alicegarden", "group:alicegarden",
"group:aliceholic",
"group:alicemirror", "group:alicemirror",
"group:alices house", "group:alices house",
"group:alicesoft", "group:alicesoft",
@@ -359,6 +359,7 @@ object Group : TagList {
"group:alz-hammer", "group:alz-hammer",
"group:am colon tiger", "group:am colon tiger",
"group:am400", "group:am400",
"group:am644",
"group:amaama-tei", "group:amaama-tei",
"group:amaembo", "group:amaembo",
"group:amagasa cycle", "group:amagasa cycle",
@@ -380,6 +381,7 @@ object Group : TagList {
"group:amanogawa tsuushin", "group:amanogawa tsuushin",
"group:amapoteya", "group:amapoteya",
"group:amarini senpaku", "group:amarini senpaku",
"group:amaterasu tsukikage",
"group:amatoro bow", "group:amatoro bow",
"group:amatosui", "group:amatosui",
"group:amatouenpitsukezuri", "group:amatouenpitsukezuri",
@@ -539,6 +541,7 @@ object Group : TagList {
"group:arkham products team ankoku baitai", "group:arkham products team ankoku baitai",
"group:armadillo", "group:armadillo",
"group:armanium", "group:armanium",
"group:arnest room",
"group:aroimark", "group:aroimark",
"group:aroma gaeru", "group:aroma gaeru",
"group:arsenothelus", "group:arsenothelus",
@@ -557,6 +560,7 @@ object Group : TagList {
"group:arukomu", "group:arukomu",
"group:aruku denpatou no kai", "group:aruku denpatou no kai",
"group:arumike", "group:arumike",
"group:arumirua",
"group:aruto-ya", "group:aruto-ya",
"group:arysuivery", "group:arysuivery",
"group:asa made go-ya", "group:asa made go-ya",
@@ -612,6 +616,7 @@ object Group : TagList {
"group:at mztm", "group:at mztm",
"group:at no 464", "group:at no 464",
"group:at oz", "group:at oz",
"group:at szkn",
"group:atara shindou", "group:atara shindou",
"group:ataraxia", "group:ataraxia",
"group:atelier d", "group:atelier d",
@@ -709,6 +714,7 @@ object Group : TagList {
"group:bakuhatsu brs.", "group:bakuhatsu brs.",
"group:bakunyu fullnerson", "group:bakunyu fullnerson",
"group:bakuretsu fusen", "group:bakuretsu fusen",
"group:bakushu koujou",
"group:bakusou special", "group:bakusou special",
"group:balgus rec", "group:balgus rec",
"group:balklash.", "group:balklash.",
@@ -746,6 +752,7 @@ object Group : TagList {
"group:bbb-extra", "group:bbb-extra",
"group:bbuttondash", "group:bbuttondash",
"group:beaf emotion", "group:beaf emotion",
"group:bear valley",
"group:beart", "group:beart",
"group:beat-pop", "group:beat-pop",
"group:beauty salon b and s", "group:beauty salon b and s",
@@ -764,6 +771,7 @@ object Group : TagList {
"group:beni namazu dan", "group:beni namazu dan",
"group:benichigaya", "group:benichigaya",
"group:beniiro kaitenkikou", "group:beniiro kaitenkikou",
"group:benimaru suisan",
"group:benimomo dou", "group:benimomo dou",
"group:benisuzumedo", "group:benisuzumedo",
"group:beniya", "group:beniya",
@@ -804,6 +812,7 @@ object Group : TagList {
"group:biroon jr.", "group:biroon jr.",
"group:biruban", "group:biruban",
"group:bisaid label", "group:bisaid label",
"group:bishamon.",
"group:bishou neko", "group:bishou neko",
"group:bishoujo production", "group:bishoujo production",
"group:bisketty", "group:bisketty",
@@ -1083,6 +1092,7 @@ object Group : TagList {
"group:chimamire yashiki", "group:chimamire yashiki",
"group:chimatsuriya honpo", "group:chimatsuriya honpo",
"group:chimchimteam", "group:chimchimteam",
"group:chimeishou",
"group:chimere marie", "group:chimere marie",
"group:chin soft", "group:chin soft",
"group:chinasanchi", "group:chinasanchi",
@@ -1243,6 +1253,7 @@ object Group : TagList {
"group:coonelius", "group:coonelius",
"group:copo deluxe", "group:copo deluxe",
"group:coppo-otome", "group:coppo-otome",
"group:coscoteikoku",
"group:cosmic-3d-angels", "group:cosmic-3d-angels",
"group:cosplay kissa nyan nyan", "group:cosplay kissa nyan nyan",
"group:cosplaydeviants", "group:cosplaydeviants",
@@ -1301,6 +1312,7 @@ object Group : TagList {
"group:d-ten", "group:d-ten",
"group:d.d.d.b.", "group:d.d.d.b.",
"group:d.n.a.lab.", "group:d.n.a.lab.",
"group:d.o.",
"group:d2", "group:d2",
"group:da hootch", "group:da hootch",
"group:da pomb no tokoro", "group:da pomb no tokoro",
@@ -1413,6 +1425,7 @@ object Group : TagList {
"group:deucesworld", "group:deucesworld",
"group:dewdrop", "group:dewdrop",
"group:dex plus", "group:dex plus",
"group:dez climax",
"group:dhr-ken", "group:dhr-ken",
"group:diablo", "group:diablo",
"group:dicpic studio", "group:dicpic studio",
@@ -1424,6 +1437,7 @@ object Group : TagList {
"group:digital graffiti", "group:digital graffiti",
"group:digital lover", "group:digital lover",
"group:digital tambourine", "group:digital tambourine",
"group:dingiruutoushi",
"group:diogenes club", "group:diogenes club",
"group:dioxin", "group:dioxin",
"group:dirty", "group:dirty",
@@ -1445,12 +1459,14 @@ object Group : TagList {
"group:doing crew", "group:doing crew",
"group:doisakaken", "group:doisakaken",
"group:dojin otome", "group:dojin otome",
"group:dojiro books",
"group:dokkoi-tori gomoku", "group:dokkoi-tori gomoku",
"group:doku alice", "group:doku alice",
"group:doku doku kinoko", "group:doku doku kinoko",
"group:doku pepper", "group:doku pepper",
"group:doku usagi tai", "group:doku usagi tai",
"group:dokudenpa jushintei", "group:dokudenpa jushintei",
"group:dokudoku ryouki garou",
"group:dokugiri", "group:dokugiri",
"group:dokukinokosha", "group:dokukinokosha",
"group:dokupan koubou", "group:dokupan koubou",
@@ -1461,11 +1477,13 @@ object Group : TagList {
"group:donaora", "group:donaora",
"group:donburi beya", "group:donburi beya",
"group:dondondon", "group:dondondon",
"group:dongurineko",
"group:dont understand", "group:dont understand",
"group:doomcomic", "group:doomcomic",
"group:dopyunger oukoku", "group:dopyunger oukoku",
"group:dorepooru", "group:dorepooru",
"group:doro-coppelia", "group:doro-coppelia",
"group:dorokuma kumaya",
"group:doronuma bunshitsu", "group:doronuma bunshitsu",
"group:doronuma kyoudai", "group:doronuma kyoudai",
"group:doropanda tours", "group:doropanda tours",
@@ -1489,8 +1507,10 @@ object Group : TagList {
"group:doujin mukashibanashi", "group:doujin mukashibanashi",
"group:douke romance", "group:douke romance",
"group:doumo sumimasen", "group:doumo sumimasen",
"group:dounimo naranai nou",
"group:dourakuya honpo", "group:dourakuya honpo",
"group:doushin chaya", "group:doushin chaya",
"group:doushoku",
"group:douwa-kensetsu", "group:douwa-kensetsu",
"group:doyondo.", "group:doyondo.",
"group:dr.vermilion", "group:dr.vermilion",
@@ -1533,6 +1553,7 @@ object Group : TagList {
"group:earthlyparadise", "group:earthlyparadise",
"group:easymode", "group:easymode",
"group:ebimayo", "group:ebimayo",
"group:ebiten kaido",
"group:ecchi na taikendan kokuhaku toukou otoko jyuku", "group:ecchi na taikendan kokuhaku toukou otoko jyuku",
"group:ecchuu douga honpo", "group:ecchuu douga honpo",
"group:eclair ringo tea", "group:eclair ringo tea",
@@ -1622,6 +1643,7 @@ object Group : TagList {
"group:etopi kan", "group:etopi kan",
"group:eucalyptus house", "group:eucalyptus house",
"group:euereuphorie", "group:euereuphorie",
"group:euglena factory",
"group:eunospress", "group:eunospress",
"group:everyday milk challenge", "group:everyday milk challenge",
"group:evil aratame baroque store", "group:evil aratame baroque store",
@@ -1647,6 +1669,7 @@ object Group : TagList {
"group:fakereal", "group:fakereal",
"group:fakers manual", "group:fakers manual",
"group:fakestar", "group:fakestar",
"group:falcon115",
"group:fallinmoon", "group:fallinmoon",
"group:famous comics", "group:famous comics",
"group:famous toons facial", "group:famous toons facial",
@@ -1730,6 +1753,7 @@ object Group : TagList {
"group:frill frill", "group:frill frill",
"group:frontwing", "group:frontwing",
"group:fruitsjam", "group:fruitsjam",
"group:fu rairyuu",
"group:fuantei", "group:fuantei",
"group:fudeoki seisakujo", "group:fudeoki seisakujo",
"group:fuegerstef", "group:fuegerstef",
@@ -1778,6 +1802,7 @@ object Group : TagList {
"group:fururi.", "group:fururi.",
"group:furuya", "group:furuya",
"group:fuseimyaku", "group:fuseimyaku",
"group:fusha fusha kingdom",
"group:fushichou no yoake", "group:fushichou no yoake",
"group:fushinsya guilty", "group:fushinsya guilty",
"group:fushizen doubutsu hogodantai", "group:fushizen doubutsu hogodantai",
@@ -1829,10 +1854,12 @@ object Group : TagList {
"group:gamera 8th army", "group:gamera 8th army",
"group:gamma menia", "group:gamma menia",
"group:gammaedge", "group:gammaedge",
"group:ganbaru dou",
"group:gang bang comix", "group:gang bang comix",
"group:ganmo-no-oyatsu", "group:ganmo-no-oyatsu",
"group:ganryuu island", "group:ganryuu island",
"group:ganso sonodaya", "group:ganso sonodaya",
"group:gaoookyouryu",
"group:gara ayuri nisshi", "group:gara ayuri nisshi",
"group:garage-talk", "group:garage-talk",
"group:garahadoh", "group:garahadoh",
@@ -1849,6 +1876,7 @@ object Group : TagList {
"group:gas ketsu jinsei", "group:gas ketsu jinsei",
"group:gasshuukoku netamekoru", "group:gasshuukoku netamekoru",
"group:gate of xiii", "group:gate of xiii",
"group:gatekeeper",
"group:gaton.", "group:gaton.",
"group:gavial no sumika", "group:gavial no sumika",
"group:gd-mechano", "group:gd-mechano",
@@ -1870,12 +1898,14 @@ object Group : TagList {
"group:general bacchus", "group:general bacchus",
"group:genesys", "group:genesys",
"group:genki no mizu no wakutokoro", "group:genki no mizu no wakutokoro",
"group:genkin-dou souhonpo",
"group:genmaiya", "group:genmaiya",
"group:genocidekiss", "group:genocidekiss",
"group:gensancha", "group:gensancha",
"group:gensou graphics", "group:gensou graphics",
"group:gensou kuukan", "group:gensou kuukan",
"group:gensou stlavus", "group:gensou stlavus",
"group:gensou yakai",
"group:gensyokuhakoniwa", "group:gensyokuhakoniwa",
"group:genwakukinema", "group:genwakukinema",
"group:geregere negro", "group:geregere negro",
@@ -1897,12 +1927,14 @@ object Group : TagList {
"group:giantessfan", "group:giantessfan",
"group:giftbell", "group:giftbell",
"group:giftkuchen", "group:giftkuchen",
"group:giga omaru",
"group:gigameka", "group:gigameka",
"group:gikogakodo", "group:gikogakodo",
"group:gin eiji", "group:gin eiji",
"group:gin no hoshitei", "group:gin no hoshitei",
"group:gin penguin", "group:gin penguin",
"group:gin-ion", "group:gin-ion",
"group:ginga no arakuremon",
"group:ginga no himitu kichi", "group:ginga no himitu kichi",
"group:ginga-ryusei", "group:ginga-ryusei",
"group:giniro noel", "group:giniro noel",
@@ -1960,6 +1992,7 @@ object Group : TagList {
"group:gouhouwakan", "group:gouhouwakan",
"group:gouriki hyakkaten", "group:gouriki hyakkaten",
"group:gouten doujou", "group:gouten doujou",
"group:gozen 4-ji one call",
"group:gozen niji no ushigaeru", "group:gozen niji no ushigaeru",
"group:gpen", "group:gpen",
"group:gpx", "group:gpx",
@@ -1969,6 +2002,9 @@ object Group : TagList {
"group:gravidan", "group:gravidan",
"group:great acta", "group:great acta",
"group:great canyon", "group:great canyon",
)
override fun getTags2(): List<String> = listOf(
"group:great dadan", "group:great dadan",
"group:greatest18club", "group:greatest18club",
"group:greatmanjuu", "group:greatmanjuu",
@@ -2002,9 +2038,6 @@ object Group : TagList {
"group:gyara cter", "group:gyara cter",
"group:gyaran rose", "group:gyaran rose",
"group:gyarandou", "group:gyarandou",
)
override fun getTags2(): List<String> = listOf(
"group:gyogyou rengou", "group:gyogyou rengou",
"group:gyokasuisin", "group:gyokasuisin",
"group:gyokotsu kouzou", "group:gyokotsu kouzou",
@@ -2018,6 +2051,7 @@ object Group : TagList {
"group:h na hon. ya san.", "group:h na hon. ya san.",
"group:h plus", "group:h plus",
"group:h senshokutai", "group:h senshokutai",
"group:h sparkle",
"group:h-m", "group:h-m",
"group:h-na-ojisan", "group:h-na-ojisan",
"group:h-sys.", "group:h-sys.",
@@ -2208,6 +2242,7 @@ object Group : TagList {
"group:hellfragrance", "group:hellfragrance",
"group:hellter skelter", "group:hellter skelter",
"group:helmet ga naosemasen", "group:helmet ga naosemasen",
"group:henachoko-domei",
"group:hengen monogatari", "group:hengen monogatari",
"group:henntai-shinshi", "group:henntai-shinshi",
"group:henreikai", "group:henreikai",
@@ -2223,6 +2258,7 @@ object Group : TagList {
"group:hero hero tei", "group:hero hero tei",
"group:hero oukoku", "group:hero oukoku",
"group:heroes factory", "group:heroes factory",
"group:herunian zokusei",
"group:heshi factory", "group:heshi factory",
"group:heta no yoko zuki", "group:heta no yoko zuki",
"group:hetalearts", "group:hetalearts",
@@ -2250,6 +2286,7 @@ object Group : TagList {
"group:high heel syndrome", "group:high heel syndrome",
"group:high risk revolution", "group:high risk revolution",
"group:high-octane", "group:high-octane",
"group:high-rised fossil garden",
"group:high-soft", "group:high-soft",
"group:high-spirit", "group:high-spirit",
"group:highway-senmu", "group:highway-senmu",
@@ -2315,7 +2352,7 @@ object Group : TagList {
"group:hitsuji-1ban-shibori", "group:hitsuji-1ban-shibori",
"group:hitsujin toko", "group:hitsujin toko",
"group:hitujinoki", "group:hitujinoki",
"group:hiyashi chuuka hajimemashita", "group:hiyashi chuuka owarimashita",
"group:hiyoko no gekijoh", "group:hiyoko no gekijoh",
"group:hiyosanchi", "group:hiyosanchi",
"group:hizadati zekkouchou", "group:hizadati zekkouchou",
@@ -2331,6 +2368,7 @@ object Group : TagList {
"group:hokoushayou shingou", "group:hokoushayou shingou",
"group:hokuroza", "group:hokuroza",
"group:holiday school", "group:holiday school",
"group:holy up",
"group:home not found", "group:home not found",
"group:homepie koubou", "group:homepie koubou",
"group:homerun chaya", "group:homerun chaya",
@@ -2348,6 +2386,7 @@ object Group : TagList {
"group:honeypie", "group:honeypie",
"group:hong kong dou", "group:hong kong dou",
"group:honnokimochiya", "group:honnokimochiya",
"group:honpo kes",
"group:hontoinu", "group:hontoinu",
"group:hook", "group:hook",
"group:hooliganism", "group:hooliganism",
@@ -2379,6 +2418,7 @@ object Group : TagList {
"group:hotori bocchi", "group:hotori bocchi",
"group:hotpink", "group:hotpink",
"group:hougakuya", "group:hougakuya",
"group:houjou-kun mania",
"group:houkago inokorigumi", "group:houkago inokorigumi",
"group:houkago paradise", "group:houkago paradise",
"group:houkaiseki.", "group:houkaiseki.",
@@ -2424,6 +2464,7 @@ object Group : TagList {
"group:ice to choco", "group:ice to choco",
"group:ice-place", "group:ice-place",
"group:ichachi", "group:ichachi",
"group:ichi dollar kouka",
"group:ichi-kan", "group:ichi-kan",
"group:ichigiteishi", "group:ichigiteishi",
"group:ichigo maririn", "group:ichigo maririn",
@@ -2507,6 +2548,7 @@ object Group : TagList {
"group:intermikan", "group:intermikan",
"group:interracial-comics", "group:interracial-comics",
"group:intoku.info", "group:intoku.info",
"group:inuchan equals land",
"group:inudrill.", "group:inudrill.",
"group:inukamedou", "group:inukamedou",
"group:inukichi club", "group:inukichi club",
@@ -2587,12 +2629,15 @@ object Group : TagList {
"group:jet-black baselarde", "group:jet-black baselarde",
"group:jewel box", "group:jewel box",
"group:jhk", "group:jhk",
"group:jibaku mecha",
"group:jibaku-system", "group:jibaku-system",
"group:jidaraku risutorante", "group:jidaraku risutorante",
"group:jido-hikki", "group:jido-hikki",
"group:jigen no wataridori", "group:jigen no wataridori",
"group:jiggly girls", "group:jiggly girls",
"group:jigizagi", "group:jigizagi",
"group:jigoku no cakeya-san",
"group:jigoku no monban",
"group:jigoku potion", "group:jigoku potion",
"group:jigyaku jihen", "group:jigyaku jihen",
"group:jikan-ya", "group:jikan-ya",
@@ -2697,6 +2742,7 @@ object Group : TagList {
"group:kairoudou", "group:kairoudou",
"group:kairyuu", "group:kairyuu",
"group:kaisanbou", "group:kaisanbou",
"group:kaitaiya",
"group:kaitatuku", "group:kaitatuku",
"group:kaiteisinden", "group:kaiteisinden",
"group:kaiten sommelier", "group:kaiten sommelier",
@@ -2824,6 +2870,7 @@ object Group : TagList {
"group:keiyou tsudanuma juku", "group:keiyou tsudanuma juku",
"group:kemao coopercent", "group:kemao coopercent",
"group:kemokomoya", "group:kemokomoya",
"group:kemomimi-chan ya",
"group:kemominnosuke", "group:kemominnosuke",
"group:kemono ekaki no kousoku 2", "group:kemono ekaki no kousoku 2",
"group:kemono masshigura.", "group:kemono masshigura.",
@@ -2849,6 +2896,7 @@ object Group : TagList {
"group:khaos distance", "group:khaos distance",
"group:khaos wind", "group:khaos wind",
"group:kharisma jati", "group:kharisma jati",
"group:khpn style",
"group:kibawomuku", "group:kibawomuku",
"group:kichiku bansankai", "group:kichiku bansankai",
"group:kichiku koubou", "group:kichiku koubou",
@@ -2959,6 +3007,7 @@ object Group : TagList {
"group:koge croquette", "group:koge croquette",
"group:kogemaru tsuushinkyoku", "group:kogemaru tsuushinkyoku",
"group:kogitune", "group:kogitune",
"group:koh no atelier",
"group:kohagura.", "group:kohagura.",
"group:kohau no heya", "group:kohau no heya",
"group:kohitsujitei", "group:kohitsujitei",
@@ -3010,6 +3059,7 @@ object Group : TagList {
"group:komorikiri.", "group:komorikiri.",
"group:komugiko 100 percent", "group:komugiko 100 percent",
"group:kon no pencase", "group:kon no pencase",
"group:konekoconnection",
"group:konekopan", "group:konekopan",
"group:konekopunch", "group:konekopunch",
"group:kongou rikisi", "group:kongou rikisi",
@@ -3057,6 +3107,7 @@ object Group : TagList {
"group:kousaien", "group:kousaien",
"group:kousoku gurihari-tei", "group:kousoku gurihari-tei",
"group:kousoku purin", "group:kousoku purin",
"group:koutatsu dennou koushi",
"group:kouzaka-san to makino jimusho", "group:kouzaka-san to makino jimusho",
"group:kouzu shoukai", "group:kouzu shoukai",
"group:kouzuya", "group:kouzuya",
@@ -3087,12 +3138,14 @@ object Group : TagList {
"group:kumo to koumori", "group:kumo to koumori",
"group:kumohitode of world", "group:kumohitode of world",
"group:kumonosu", "group:kumonosu",
"group:kuni gamma",
"group:kunkakunka teikoku", "group:kunkakunka teikoku",
"group:kunon", "group:kunon",
"group:kurage kyoudai", "group:kurage kyoudai",
"group:kurahashi shoin", "group:kurahashi shoin",
"group:kurai mori no soko de", "group:kurai mori no soko de",
"group:kurakura-honey", "group:kurakura-honey",
"group:kurasan",
"group:kuraudo.", "group:kuraudo.",
"group:kureboti ufo", "group:kureboti ufo",
"group:kurige wagyuu", "group:kurige wagyuu",
@@ -3162,6 +3215,7 @@ object Group : TagList {
"group:kuukiisu", "group:kuukiisu",
"group:kuuronziyou", "group:kuuronziyou",
"group:kuusou idol labo bellberry", "group:kuusou idol labo bellberry",
"group:kuusou kouko gakkai",
"group:kuusou switch", "group:kuusou switch",
"group:kuzuryuu", "group:kuzuryuu",
"group:kyakuniku kanzume", "group:kyakuniku kanzume",
@@ -3178,6 +3232,7 @@ object Group : TagList {
"group:kyouken diners", "group:kyouken diners",
"group:kyouki na shiunten", "group:kyouki na shiunten",
"group:kyoumo spaghe", "group:kyoumo spaghe",
"group:kyourakuen",
"group:kyoushuugata", "group:kyoushuugata",
"group:kyuu no mon", "group:kyuu no mon",
"group:kyuu tekki jidai", "group:kyuu tekki jidai",
@@ -3246,6 +3301,7 @@ object Group : TagList {
"group:lily heart", "group:lily heart",
"group:lily lily rose", "group:lily lily rose",
"group:lily-put", "group:lily-put",
"group:lime green",
"group:limit break", "group:limit break",
"group:limit max", "group:limit max",
"group:limit over", "group:limit over",
@@ -3260,6 +3316,7 @@ object Group : TagList {
"group:little mantis", "group:little mantis",
"group:little princess", "group:little princess",
"group:littlehopper", "group:littlehopper",
"group:littlepool.",
"group:littletail", "group:littletail",
"group:live house", "group:live house",
"group:lo likyo new", "group:lo likyo new",
@@ -3274,6 +3331,7 @@ object Group : TagList {
"group:lolipop complete", "group:lolipop complete",
"group:lolitachannel", "group:lolitachannel",
"group:longhorntrain", "group:longhorntrain",
"group:longlong de cangku",
"group:looptheloop", "group:looptheloop",
"group:lopet dan", "group:lopet dan",
"group:lostscript", "group:lostscript",
@@ -3284,6 +3342,7 @@ object Group : TagList {
"group:love kitten", "group:love kitten",
"group:love kyun maiden", "group:love kyun maiden",
"group:love lily", "group:love lily",
"group:love love craft",
"group:love nyanko", "group:love nyanko",
"group:love scythe", "group:love scythe",
"group:love shine", "group:love shine",
@@ -3325,6 +3384,7 @@ object Group : TagList {
"group:m kichibeya", "group:m kichibeya",
"group:m plus dilore", "group:m plus dilore",
"group:m slash k club", "group:m slash k club",
"group:m-family",
"group:m-i-p", "group:m-i-p",
"group:m-keifu", "group:m-keifu",
"group:m-koujou", "group:m-koujou",
@@ -3364,6 +3424,7 @@ object Group : TagList {
"group:magono-tei", "group:magono-tei",
"group:magudara kaihou doumei", "group:magudara kaihou doumei",
"group:maguro bokujo", "group:maguro bokujo",
"group:maguro fiction",
"group:maguro koubou", "group:maguro koubou",
"group:mahiru no tsuki", "group:mahiru no tsuki",
"group:mahiru nosora", "group:mahiru nosora",
@@ -3417,9 +3478,11 @@ object Group : TagList {
"group:manjuu x", "group:manjuu x",
"group:manles laboratory", "group:manles laboratory",
"group:manshin soui", "group:manshin soui",
"group:mantohihi atoz",
"group:many menu", "group:many menu",
"group:maokonzu", "group:maokonzu",
"group:maple-go", "group:maple-go",
"group:mappa namatta",
"group:mara apocalypse", "group:mara apocalypse",
"group:marashion", "group:marashion",
"group:marble kid", "group:marble kid",
@@ -3431,6 +3494,7 @@ object Group : TagList {
"group:marge-loop", "group:marge-loop",
"group:maria system00", "group:maria system00",
"group:mariana kaikou kikaku", "group:mariana kaikou kikaku",
"group:marimo-ya",
"group:marinesapphire", "group:marinesapphire",
"group:marionette soukou ryouhei", "group:marionette soukou ryouhei",
"group:marireimari inochi", "group:marireimari inochi",
@@ -3651,6 +3715,7 @@ object Group : TagList {
"group:misaki shoujokei.", "group:misaki shoujokei.",
"group:misakix megamix", "group:misakix megamix",
"group:misin koujou", "group:misin koujou",
"group:misobolo dou",
"group:misonodenpatou", "group:misonodenpatou",
"group:misoyahonpo", "group:misoyahonpo",
"group:misssail", "group:misssail",
@@ -3697,6 +3762,7 @@ object Group : TagList {
"group:mocha plus ccc", "group:mocha plus ccc",
"group:mocha2popcorn", "group:mocha2popcorn",
"group:mochi hasamiuchi da", "group:mochi hasamiuchi da",
"group:mochi mochi bomb",
"group:mochi usagi", "group:mochi usagi",
"group:mochi-ya", "group:mochi-ya",
"group:mochimoonya", "group:mochimoonya",
@@ -3716,8 +3782,10 @@ object Group : TagList {
"group:mogura-dou", "group:mogura-dou",
"group:mogyutto cheesecake", "group:mogyutto cheesecake",
"group:mojibone", "group:mojibone",
"group:mokkindo",
"group:mokkorihan", "group:mokkorihan",
"group:mokkoubondobu", "group:mokkoubondobu",
"group:mokkuafunfun",
"group:mokomaru suisan", "group:mokomaru suisan",
"group:mokugyuutan", "group:mokugyuutan",
"group:mokusa", "group:mokusa",
@@ -3738,6 +3806,7 @@ object Group : TagList {
"group:momoiro funenmono", "group:momoiro funenmono",
"group:momoiro mimic", "group:momoiro mimic",
"group:momoiro tanzaku", "group:momoiro tanzaku",
"group:momoiro zundoko",
"group:momoiro-gekijyou", "group:momoiro-gekijyou",
"group:momoiro-rip", "group:momoiro-rip",
"group:momokamasu", "group:momokamasu",
@@ -3787,8 +3856,8 @@ object Group : TagList {
"group:morning star", "group:morning star",
"group:morning tea.", "group:morning tea.",
"group:morningmoon merchandising products", "group:morningmoon merchandising products",
"group:morohei-ya",
"group:moroheiya break", "group:moroheiya break",
"group:moroheiya no agata",
"group:morokochiffon cake", "group:morokochiffon cake",
"group:morokosheet", "group:morokosheet",
"group:moromi-ya", "group:moromi-ya",
@@ -3802,6 +3871,7 @@ object Group : TagList {
"group:mosoya", "group:mosoya",
"group:mosquitone.", "group:mosquitone.",
"group:motchie kingdom", "group:motchie kingdom",
"group:motemote life",
"group:mothman", "group:mothman",
"group:mou sukoshi hidari e", "group:mou sukoshi hidari e",
"group:mouko mouretsu hasai dan", "group:mouko mouretsu hasai dan",
@@ -3864,6 +3934,7 @@ object Group : TagList {
"group:mukokoro no kumo", "group:mukokoro no kumo",
"group:mukousharan", "group:mukousharan",
"group:mukuchi na hakoniwa", "group:mukuchi na hakoniwa",
"group:mukyou no utopia",
"group:mukyuu dynamic", "group:mukyuu dynamic",
"group:mulberry", "group:mulberry",
"group:multi media studio l.o.e.r.", "group:multi media studio l.o.e.r.",
@@ -3934,6 +4005,9 @@ object Group : TagList {
"group:nagomi-chaya", "group:nagomi-chaya",
"group:nagomisui", "group:nagomisui",
"group:nagomiyasan", "group:nagomiyasan",
)
override fun getTags3(): List<String> = listOf(
"group:nagucha.", "group:nagucha.",
"group:nagumo curry-bu", "group:nagumo curry-bu",
"group:nagumoya", "group:nagumoya",
@@ -3957,9 +4031,11 @@ object Group : TagList {
"group:namahage-dou", "group:namahage-dou",
"group:namahamu sando", "group:namahamu sando",
"group:namaikichibi", "group:namaikichibi",
"group:namaitati teishoku",
"group:namakemono sou", "group:namakemono sou",
"group:namakura dou", "group:namakura dou",
"group:namanecotei", "group:namanecotei",
"group:namaniku aikoukai",
"group:namasute koubou", "group:namasute koubou",
"group:namazuchaya", "group:namazuchaya",
"group:nameco-soup", "group:nameco-soup",
@@ -4005,9 +4081,6 @@ object Group : TagList {
"group:nanto wachou ken", "group:nanto wachou ken",
"group:nantoka ikitemasu", "group:nantoka ikitemasu",
"group:nantoka suroun", "group:nantoka suroun",
)
override fun getTags3(): List<String> = listOf(
"group:nappy", "group:nappy",
"group:naraduke biyori", "group:naraduke biyori",
"group:naragyogyo kumiai", "group:naragyogyo kumiai",
@@ -4018,6 +4091,7 @@ object Group : TagList {
"group:nasi-pasuya", "group:nasi-pasuya",
"group:nasu no mono", "group:nasu no mono",
"group:nasuan", "group:nasuan",
"group:natadecoco company",
"group:natakuga-yuku", "group:natakuga-yuku",
"group:natorina dou", "group:natorina dou",
"group:natrinium", "group:natrinium",
@@ -4120,8 +4194,10 @@ object Group : TagList {
"group:nendo ningyo", "group:nendo ningyo",
"group:nengaranenjuu", "group:nengaranenjuu",
"group:neo maiden", "group:neo maiden",
"group:neoniro",
"group:neruneru", "group:neruneru",
"group:netemo sametemo", "group:netemo sametemo",
"group:netorareru tamashigi no hitoshizuku",
"group:netsuzukeru ishiryoku", "group:netsuzukeru ishiryoku",
"group:nettaigyo club", "group:nettaigyo club",
"group:neutron city", "group:neutron city",
@@ -4152,6 +4228,7 @@ object Group : TagList {
"group:nighthawk", "group:nighthawk",
"group:nightmare software", "group:nightmare software",
"group:nigimitama no ya", "group:nigimitama no ya",
"group:nihao series",
"group:nihatsu shika ataranai", "group:nihatsu shika ataranai",
"group:nihon dandy", "group:nihon dandy",
"group:nihon denga senmon gakkou", "group:nihon denga senmon gakkou",
@@ -4168,6 +4245,7 @@ object Group : TagList {
"group:nijiiro zakura", "group:nijiiro zakura",
"group:nijinoren", "group:nijinoren",
"group:nijiyome", "group:nijiyome",
"group:niko-chan planning",
"group:nikomark", "group:nikomark",
"group:nikomi omurice", "group:nikomi omurice",
"group:nikoniko company", "group:nikoniko company",
@@ -4323,6 +4401,7 @@ object Group : TagList {
"group:obsession.", "group:obsession.",
"group:ochanomizu mokujinkai", "group:ochanomizu mokujinkai",
"group:ochawan", "group:ochawan",
"group:ochi mono kanzume",
"group:ochikochitei", "group:ochikochitei",
"group:ochikonium", "group:ochikonium",
"group:ochimusha.", "group:ochimusha.",
@@ -4339,6 +4418,7 @@ object Group : TagList {
"group:office amagasa", "group:office amagasa",
"group:ofuton de suyaa", "group:ofuton de suyaa",
"group:ogeretsu-dan", "group:ogeretsu-dan",
"group:ogon shinshi club",
"group:ogre no heya", "group:ogre no heya",
"group:oh-banzai studio", "group:oh-banzai studio",
"group:ohagi.", "group:ohagi.",
@@ -4413,6 +4493,7 @@ object Group : TagList {
"group:onsen", "group:onsen",
"group:oobari doujou", "group:oobari doujou",
"group:ookami no o", "group:ookami no o",
"group:ookina gomibako",
"group:ookina kodomo no omocha bako", "group:ookina kodomo no omocha bako",
"group:operating room", "group:operating room",
"group:oppawi shitei", "group:oppawi shitei",
@@ -4489,8 +4570,10 @@ object Group : TagList {
"group:owlpop", "group:owlpop",
"group:oxide.lab", "group:oxide.lab",
"group:oyako donburi tei", "group:oyako donburi tei",
"group:oyasumi kobe gyuu",
"group:ozawa kobo", "group:ozawa kobo",
"group:p kikaku", "group:p kikaku",
"group:p shoukai",
"group:p-collection", "group:p-collection",
"group:p-forest", "group:p-forest",
"group:p-pooh", "group:p-pooh",
@@ -4632,6 +4715,7 @@ object Group : TagList {
"group:pink-noise", "group:pink-noise",
"group:pinkbell software", "group:pinkbell software",
"group:pinkharlem", "group:pinkharlem",
"group:pinki wana",
"group:pinktips.info", "group:pinktips.info",
"group:pinkysoft", "group:pinkysoft",
"group:pinpoint", "group:pinpoint",
@@ -4689,6 +4773,7 @@ object Group : TagList {
"group:ponchees kari", "group:ponchees kari",
"group:ponkotuna potunoya", "group:ponkotuna potunoya",
"group:ponpon-black", "group:ponpon-black",
"group:ponponponpo",
"group:pons lab", "group:pons lab",
"group:pony farm", "group:pony farm",
"group:ponyfarm", "group:ponyfarm",
@@ -4864,6 +4949,7 @@ object Group : TagList {
"group:remora works", "group:remora works",
"group:ren-kon-an", "group:ren-kon-an",
"group:renai mangaka", "group:renai mangaka",
"group:rengaworks",
"group:renge-dou", "group:renge-dou",
"group:rengeza", "group:rengeza",
"group:renglet", "group:renglet",
@@ -4967,6 +5053,7 @@ object Group : TagList {
"group:ruku-pusyu", "group:ruku-pusyu",
"group:running girl", "group:running girl",
"group:runrun soft", "group:runrun soft",
"group:ruri-iro special room",
"group:ruriiro honpo", "group:ruriiro honpo",
"group:rurirara star", "group:rurirara star",
"group:ruruna and nimunimu", "group:ruruna and nimunimu",
@@ -5053,6 +5140,7 @@ object Group : TagList {
"group:sakuraproject", "group:sakuraproject",
"group:sakurasaku", "group:sakurasaku",
"group:sakurasaku koubou", "group:sakurasaku koubou",
"group:sakurayakan no hanare",
"group:sakuryu", "group:sakuryu",
"group:sakusaku kangen noushuku", "group:sakusaku kangen noushuku",
"group:sakusakusakuchan", "group:sakusakusakuchan",
@@ -5241,6 +5329,7 @@ object Group : TagList {
"group:shiina club", "group:shiina club",
"group:shiinotomoshibitake", "group:shiinotomoshibitake",
"group:shiitake nouen", "group:shiitake nouen",
"group:shijimi wari ningyou",
"group:shijou misaki", "group:shijou misaki",
"group:shikakui tori", "group:shikakui tori",
"group:shiki be careful", "group:shiki be careful",
@@ -5274,6 +5363,7 @@ object Group : TagList {
"group:shingeki no nameko", "group:shingeki no nameko",
"group:shining star", "group:shining star",
"group:shining star lilys", "group:shining star lilys",
"group:shinise ikedaya",
"group:shinkirou akatsuki", "group:shinkirou akatsuki",
"group:shinkuraiku", "group:shinkuraiku",
"group:shinnihon pepsitou", "group:shinnihon pepsitou",
@@ -5338,8 +5428,11 @@ object Group : TagList {
"group:short kami", "group:short kami",
"group:shortcut koubou", "group:shortcut koubou",
"group:shosekido", "group:shosekido",
"group:shota mangaya-san",
"group:shotacon-do",
"group:shouchuu mac", "group:shouchuu mac",
"group:shoudansha", "group:shoudansha",
"group:shougusha",
"group:shoujo 2-jou", "group:shoujo 2-jou",
"group:shoujo gesshoku", "group:shoujo gesshoku",
"group:shoujo kousaku", "group:shoujo kousaku",
@@ -5356,6 +5449,7 @@ object Group : TagList {
"group:showa saishuu sensen", "group:showa saishuu sensen",
"group:showa shojo", "group:showa shojo",
"group:showano", "group:showano",
"group:shubi-ryoku 4man",
"group:shudoushiki denki jidousha", "group:shudoushiki denki jidousha",
"group:shumi eshi", "group:shumi eshi",
"group:shumisen jiru", "group:shumisen jiru",
@@ -5384,6 +5478,7 @@ object Group : TagList {
"group:silky to yukai na nakama-tachi", "group:silky to yukai na nakama-tachi",
"group:silkys plus wasabi", "group:silkys plus wasabi",
"group:silmaril", "group:silmaril",
"group:silver rice",
"group:silver ring", "group:silver ring",
"group:silver-kingdom", "group:silver-kingdom",
"group:silver-rx", "group:silver-rx",
@@ -5407,6 +5502,7 @@ object Group : TagList {
"group:sirouto plan", "group:sirouto plan",
"group:sirubedou", "group:sirubedou",
"group:sisinabeya", "group:sisinabeya",
"group:sistny and anasis",
"group:sittori oblaat", "group:sittori oblaat",
"group:sketch-book", "group:sketch-book",
"group:skid-mark", "group:skid-mark",
@@ -5512,6 +5608,7 @@ object Group : TagList {
"group:spicia", "group:spicia",
"group:spig at", "group:spig at",
"group:spiral brain", "group:spiral brain",
"group:spiritguide",
"group:spock-san", "group:spock-san",
"group:sponge empire", "group:sponge empire",
"group:sql", "group:sql",
@@ -5559,6 +5656,7 @@ object Group : TagList {
"group:studio c-take", "group:studio c-take",
"group:studio cardamom", "group:studio cardamom",
"group:studio ciao", "group:studio ciao",
"group:studio diamond",
"group:studio dna", "group:studio dna",
"group:studio e.go", "group:studio e.go",
"group:studio erohouse", "group:studio erohouse",
@@ -5602,6 +5700,7 @@ object Group : TagList {
"group:studio southpaw", "group:studio southpaw",
"group:studio sunadokei", "group:studio sunadokei",
"group:studio sushi kui-ne", "group:studio sushi kui-ne",
"group:studio t.r.c.",
"group:studio tapa tapa", "group:studio tapa tapa",
"group:studio wallaby", "group:studio wallaby",
"group:studio waltz", "group:studio waltz",
@@ -5615,6 +5714,7 @@ object Group : TagList {
"group:studio30neko", "group:studio30neko",
"group:studiomia", "group:studiomia",
"group:studios", "group:studios",
"group:stukitora",
"group:stulli-yasan", "group:stulli-yasan",
"group:sturm no shukuten", "group:sturm no shukuten",
"group:su kanchou koubou", "group:su kanchou koubou",
@@ -5657,6 +5757,7 @@ object Group : TagList {
"group:sumomo hana koushu", "group:sumomo hana koushu",
"group:sunadokei to enpitsu", "group:sunadokei to enpitsu",
"group:sunatoka aoi noyama", "group:sunatoka aoi noyama",
"group:sunege6",
"group:sunezumi fauvism", "group:sunezumi fauvism",
"group:sunora", "group:sunora",
"group:sunsetmoon", "group:sunsetmoon",
@@ -5766,8 +5867,10 @@ object Group : TagList {
"group:tamago no kimi", "group:tamago no kimi",
"group:tamanegiya", "group:tamanegiya",
"group:tamatamasanmyaku", "group:tamatamasanmyaku",
"group:tamima-ya",
"group:tamokuteki hall", "group:tamokuteki hall",
"group:tamokuteki kuukan", "group:tamokuteki kuukan",
"group:tana kara marriage",
"group:tanajou", "group:tanajou",
"group:tanaka shouten", "group:tanaka shouten",
"group:tanaura honpo", "group:tanaura honpo",
@@ -5778,10 +5881,13 @@ object Group : TagList {
"group:tanmatsu ijou", "group:tanmatsu ijou",
"group:tanpatsu kikaku", "group:tanpatsu kikaku",
"group:tansanshonen", "group:tansanshonen",
"group:tanu-chan chi",
"group:tanukineiri",
"group:tarai death", "group:tarai death",
"group:tarako koubou", "group:tarako koubou",
"group:tarakospa", "group:tarakospa",
"group:tarantula", "group:tarantula",
"group:tarareba naraba",
"group:taromarun", "group:taromarun",
"group:tashikani", "group:tashikani",
"group:tasogare hakubutukan", "group:tasogare hakubutukan",
@@ -5799,6 +5905,7 @@ object Group : TagList {
"group:team dai 7 youhei shidan", "group:team dai 7 youhei shidan",
"group:team hin ga 9", "group:team hin ga 9",
"group:team ibm", "group:team ibm",
"group:team lv",
"group:team okays", "group:team okays",
"group:team tanabe", "group:team tanabe",
"group:team z and 3n", "group:team z and 3n",
@@ -5854,6 +5961,8 @@ object Group : TagList {
"group:terolin soft", "group:terolin soft",
"group:terra drive", "group:terra drive",
"group:testa kitchen", "group:testa kitchen",
"group:testme1111",
"group:tetora star gumi",
"group:tetorapotto bunsitu", "group:tetorapotto bunsitu",
"group:tetrodotoxin", "group:tetrodotoxin",
"group:tetsubou shounen", "group:tetsubou shounen",
@@ -5866,6 +5975,7 @@ object Group : TagList {
"group:th", "group:th",
"group:th4", "group:th4",
"group:the block buster destruction", "group:the block buster destruction",
"group:the fourth sequence",
"group:the fuckin toyzaras", "group:the fuckin toyzaras",
"group:the jolly roger", "group:the jolly roger",
"group:the knight of the pants", "group:the knight of the pants",
@@ -5898,115 +6008,5 @@ object Group : TagList {
"group:tirol bunko", "group:tirol bunko",
"group:tissuhaco", "group:tissuhaco",
"group:titanaluminiden", "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 { object Group2 : TagList {
override fun getTags1(): List<String> = listOf( 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:touyu stand",
"group:touzainanboku", "group:touzainanboku",
"group:touzoku tachi no rakuda no mure", "group:touzoku tachi no rakuda no mure",
"group:toxic love", "group:toxic love",
"group:toyamando",
"group:toybox", "group:toybox",
"group:tozan bu", "group:tozan bu",
"group:tp", "group:tp",
@@ -55,6 +169,7 @@ object Group2 : TagList {
"group:tsuredure children", "group:tsuredure children",
"group:tsurezurezuki", "group:tsurezurezuki",
"group:tsurikichi doumei", "group:tsurikichi doumei",
"group:tsurugashima heights",
"group:tsurumiku", "group:tsurumiku",
"group:tsurupeta kenkyuusho", "group:tsurupeta kenkyuusho",
"group:tsurutasousankai", "group:tsurutasousankai",
@@ -70,6 +185,7 @@ object Group2 : TagList {
"group:tukinon bunko", "group:tukinon bunko",
"group:tukishitahikou", "group:tukishitahikou",
"group:tumiribbon", "group:tumiribbon",
"group:tuna ozawa",
"group:tunacan.", "group:tunacan.",
"group:tunadrive", "group:tunadrive",
"group:turuvege.", "group:turuvege.",
@@ -141,8 +257,10 @@ object Group2 : TagList {
"group:uminouie", "group:uminouie",
"group:umon paradise", "group:umon paradise",
"group:unagi no nedoko", "group:unagi no nedoko",
"group:unagineco house",
"group:unaginobori", "group:unaginobori",
"group:unceder", "group:unceder",
"group:uncertain field",
"group:unconscious", "group:unconscious",
"group:undamesi", "group:undamesi",
"group:undead", "group:undead",
@@ -201,6 +319,7 @@ object Group2 : TagList {
"group:uribatakebokujou", "group:uribatakebokujou",
"group:urondou", "group:urondou",
"group:urusai kokuen", "group:urusai kokuen",
"group:uruudoshi",
"group:us", "group:us",
"group:usa daioh", "group:usa daioh",
"group:usa.k", "group:usa.k",
@@ -218,6 +337,7 @@ object Group2 : TagList {
"group:usausa", "group:usausa",
"group:ushi ushido", "group:ushi ushido",
"group:ushidon-ya", "group:ushidon-ya",
"group:ushikani gassen",
"group:uso seisakusho", "group:uso seisakusho",
"group:uso293", "group:uso293",
"group:usotsuki house", "group:usotsuki house",
@@ -282,6 +402,7 @@ object Group2 : TagList {
"group:wankyoku canvas", "group:wankyoku canvas",
"group:wanwandoh", "group:wanwandoh",
"group:warabimochi", "group:warabimochi",
"group:warau kado ni wa",
"group:waretama", "group:waretama",
"group:warp loop", "group:warp loop",
"group:wasa wasa", "group:wasa wasa",
@@ -364,6 +485,7 @@ object Group2 : TagList {
"group:yakisaketeishoku", "group:yakisaketeishoku",
"group:yakisoba pants", "group:yakisoba pants",
"group:yakisoba rengo", "group:yakisoba rengo",
"group:yakitate jamaica",
"group:yakiubu", "group:yakiubu",
"group:yakousei fan club", "group:yakousei fan club",
"group:yaku 40 man sarad", "group:yaku 40 man sarad",
@@ -380,6 +502,7 @@ object Group2 : TagList {
"group:yamakawa denenhuukei", "group:yamakawa denenhuukei",
"group:yamami no yado", "group:yamami no yado",
"group:yamamori gohan", "group:yamamori gohan",
"group:yamamoto keiji",
"group:yamanaka no naka", "group:yamanaka no naka",
"group:yamanashi musume.", "group:yamanashi musume.",
"group:yamano murao", "group:yamano murao",
@@ -463,10 +586,12 @@ object Group2 : TagList {
"group:yoyude ikemasu", "group:yoyude ikemasu",
"group:yozorairodrops", "group:yozorairodrops",
"group:ys company", "group:ys company",
"group:yu-chu-bu",
"group:yu-ta.18", "group:yu-ta.18",
"group:yu-yu-tei", "group:yu-yu-tei",
"group:yuasa rengou", "group:yuasa rengou",
"group:yubidou", "group:yubidou",
"group:yubunecity",
"group:yudenakya nama-beer", "group:yudenakya nama-beer",
"group:yudokuya", "group:yudokuya",
"group:yuhshiki", "group:yuhshiki",
@@ -497,6 +622,7 @@ object Group2 : TagList {
"group:yumeoikyounouta", "group:yumeoikyounouta",
"group:yumeoukoku", "group:yumeoukoku",
"group:yunabon", "group:yunabon",
"group:yuo kurokawa",
"group:yurayuraseyuura", "group:yurayuraseyuura",
"group:yurei yashiki", "group:yurei yashiki",
"group:yureika blade", "group:yureika blade",
@@ -564,6 +690,7 @@ object Group2 : TagList {
"group:zenshuu bougyo", "group:zenshuu bougyo",
"group:zensoku zenkai.", "group:zensoku zenkai.",
"group:zensun habaku", "group:zensun habaku",
"group:zenten ukemi tomonokai",
"group:zenzidou kosyubenjo", "group:zenzidou kosyubenjo",
"group:zero byte", "group:zero byte",
"group:zero equals mono", "group:zero equals mono",
@@ -589,6 +716,7 @@ object Group2 : TagList {
"group:zugaikotsu marudashi", "group:zugaikotsu marudashi",
"group:zundoko sperm bank", "group:zundoko sperm bank",
"group:zurumuke taro", "group:zurumuke taro",
"group:zutto mae kara darui.",
"group:zvizva-dan", "group:zvizva-dan",
"group:zydan", "group:zydan",
"group:zyulokuya", "group:zyulokuya",
+12
View File
@@ -37,6 +37,7 @@ object Male : TagList {
"male:balljob", "male:balljob",
"male:balls expansion", "male:balls expansion",
"male:bandages", "male:bandages",
"male:bandaid",
"male:bat boy", "male:bat boy",
"male:bbm", "male:bbm",
"male:bdsm", "male:bdsm",
@@ -55,7 +56,9 @@ object Male : TagList {
"male:big penis", "male:big penis",
"male:bike shorts", "male:bike shorts",
"male:bikini", "male:bikini",
"male:bird boy",
"male:bisexual", "male:bisexual",
"male:bite mark",
"male:blackmail", "male:blackmail",
"male:blind", "male:blind",
"male:blindfold", "male:blindfold",
@@ -111,8 +114,10 @@ object Male : TagList {
"male:cockslapping", "male:cockslapping",
"male:collar", "male:collar",
"male:condom", "male:condom",
"male:confinement",
"male:conjoined", "male:conjoined",
"male:coprophagia", "male:coprophagia",
"male:corpse",
"male:corruption", "male:corruption",
"male:corset", "male:corset",
"male:cosplaying", "male:cosplaying",
@@ -157,6 +162,7 @@ object Male : TagList {
"male:drill hair", "male:drill hair",
"male:drugs", "male:drugs",
"male:drunk", "male:drunk",
"male:ear fuck",
"male:eel", "male:eel",
"male:eggs", "male:eggs",
"male:electric shocks", "male:electric shocks",
@@ -198,6 +204,7 @@ object Male : TagList {
"male:frog", "male:frog",
"male:frog boy", "male:frog boy",
"male:frottage", "male:frottage",
"male:full tour",
"male:fundoshi", "male:fundoshi",
"male:furry", "male:furry",
"male:gag", "male:gag",
@@ -273,6 +280,7 @@ object Male : TagList {
"male:kimono", "male:kimono",
"male:kindergarten uniform", "male:kindergarten uniform",
"male:kissing", "male:kissing",
"male:kodomo doushi",
"male:kunoichi", "male:kunoichi",
"male:lab coat", "male:lab coat",
"male:lactation", "male:lactation",
@@ -291,6 +299,7 @@ object Male : TagList {
"male:long tongue", "male:long tongue",
"male:low bestiality", "male:low bestiality",
"male:low guro", "male:low guro",
"male:low incest",
"male:low scat", "male:low scat",
"male:low shotacon", "male:low shotacon",
"male:low smegma", "male:low smegma",
@@ -329,6 +338,7 @@ object Male : TagList {
"male:multiple assjob", "male:multiple assjob",
"male:multiple footjob", "male:multiple footjob",
"male:multiple handjob", "male:multiple handjob",
"male:multiple nipples",
"male:multiple orgasms", "male:multiple orgasms",
"male:multiple penises", "male:multiple penises",
"male:multiple straddling", "male:multiple straddling",
@@ -336,6 +346,7 @@ object Male : TagList {
"male:muscle growth", "male:muscle growth",
"male:mute", "male:mute",
"male:nakadashi", "male:nakadashi",
"male:navel birth",
"male:navel fuck", "male:navel fuck",
"male:nazi", "male:nazi",
"male:necrophilia", "male:necrophilia",
@@ -496,6 +507,7 @@ object Male : TagList {
"male:tracksuit", "male:tracksuit",
"male:trampling", "male:trampling",
"male:transformation", "male:transformation",
"male:transparent clothing",
"male:triple anal", "male:triple anal",
"male:triple penetration", "male:triple penetration",
"male:tube", "male:tube",
+2
View File
@@ -10,6 +10,8 @@ object Mixed : TagList {
"mixed:group", "mixed:group",
"mixed:incest", "mixed:incest",
"mixed:inseki", "mixed:inseki",
"mixed:kodomo doushi",
"mixed:low incest",
"mixed:mmf threesome", "mixed:mmf threesome",
"mixed:mmt threesome", "mixed:mmt threesome",
"mixed:mtf threesome", "mixed:mtf threesome",
+3 -2
View File
@@ -4,7 +4,6 @@ object Other : TagList {
override fun getTags1(): List<String> = listOf( override fun getTags1(): List<String> = listOf(
"other:3d", "other:3d",
"other:3d imageset", "other:3d imageset",
"other:ai generated",
"other:already uploaded", "other:already uploaded",
"other:anaglyph", "other:anaglyph",
"other:animated", "other:animated",
@@ -20,21 +19,23 @@ object Other : TagList {
"other:forbidden content", "other:forbidden content",
"other:full censorship", "other:full censorship",
"other:full color", "other:full color",
"other:game manual",
"other:game sprite", "other:game sprite",
"other:goudoushi", "other:goudoushi",
"other:hardcore", "other:hardcore",
"other:how to", "other:how to",
"other:incomplete", "other:incomplete",
"other:kodomo only",
"other:missing cover", "other:missing cover",
"other:mosaic censorship", "other:mosaic censorship",
"other:multi-work series", "other:multi-work series",
"other:multipanel sequence", "other:multipanel sequence",
"other:no penetration", "other:no penetration",
"other:non-h game manual",
"other:non-h imageset", "other:non-h imageset",
"other:non-nude", "other:non-nude",
"other:novel", "other:novel",
"other:nudity only", "other:nudity only",
"other:object insertion only",
"other:out of order", "other:out of order",
"other:paperchild", "other:paperchild",
"other:realporn", "other:realporn",
+64 -9
View File
@@ -36,6 +36,7 @@ object Parody : TagList {
"parody:a vampyre story", "parody:a vampyre story",
"parody:a.d.police", "parody:a.d.police",
"parody:a.i. ga tomaranai", "parody:a.i. ga tomaranai",
"parody:abashiri ikka",
"parody:abenobashi mahou shoutengai", "parody:abenobashi mahou shoutengai",
"parody:acca 13-ku kansatsu-ka", "parody:acca 13-ku kansatsu-ka",
"parody:accel world", "parody:accel world",
@@ -243,7 +244,6 @@ object Parody : TagList {
"parody:battle royale", "parody:battle royale",
"parody:battle spirits", "parody:battle spirits",
"parody:beast wars", "parody:beast wars",
"parody:beat angel escalayer",
"parody:beat blades haruka", "parody:beat blades haruka",
"parody:beatmania", "parody:beatmania",
"parody:beauty and the beast", "parody:beauty and the beast",
@@ -348,6 +348,7 @@ object Parody : TagList {
"parody:burst angel", "parody:burst angel",
"parody:busou renkin", "parody:busou renkin",
"parody:busou shoujo machiavellianism", "parody:busou shoujo machiavellianism",
"parody:buta no gotoki",
"parody:buzz lightyear of star command", "parody:buzz lightyear of star command",
"parody:c cube", "parody:c cube",
"parody:c the money of soul and possibility control", "parody:c the money of soul and possibility control",
@@ -382,18 +383,21 @@ object Parody : TagList {
"parody:childs play", "parody:childs play",
"parody:chio-chan no tsuugakuro", "parody:chio-chan no tsuugakuro",
"parody:chip n dale rescue rangers", "parody:chip n dale rescue rangers",
"parody:chiyu mahou no machigatta tsukaikata",
"parody:cho aniki", "parody:cho aniki",
"parody:chobits", "parody:chobits",
"parody:chogattai majutsu robot ginguiser", "parody:chogattai majutsu robot ginguiser",
"parody:chokotto sister", "parody:chokotto sister",
"parody:chou dokyuu shoujo 4946", "parody:chou dokyuu shoujo 4946",
"parody:chou kuse ni narisou", "parody:chou kuse ni narisou",
"parody:chou-tantei jikenbo rain code",
"parody:choudenshi bioman", "parody:choudenshi bioman",
"parody:chouja raideen", "parody:chouja raideen",
"parody:choujikuu kidan southern cross", "parody:choujikuu kidan southern cross",
"parody:choujin koukousei-tachi wa isekai demo yoyuu de ikinuku you desu", "parody:choujin koukousei-tachi wa isekai demo yoyuu de ikinuku you desu",
"parody:choujuu kishin dancougar", "parody:choujuu kishin dancougar",
"parody:choukou shinki ixseal", "parody:choukou shinki ixseal",
"parody:choukou tenshi escalayer",
"parody:chronicles of the going home club", "parody:chronicles of the going home club",
"parody:chrono cross", "parody:chrono cross",
"parody:chrono crusade", "parody:chrono crusade",
@@ -554,6 +558,7 @@ object Parody : TagList {
"parody:dororon enma-kun", "parody:dororon enma-kun",
"parody:dosanko gal wa namaramenkoi", "parody:dosanko gal wa namaramenkoi",
"parody:doubutsu banchou", "parody:doubutsu banchou",
"parody:doubutsu nee-chan",
"parody:doubutsu no oishasan", "parody:doubutsu no oishasan",
"parody:doukyuusei 2", "parody:doukyuusei 2",
"parody:douluo continent", "parody:douluo continent",
@@ -565,6 +570,7 @@ object Parody : TagList {
"parody:dragon age", "parody:dragon age",
"parody:dragon ball", "parody:dragon ball",
"parody:dragon ball gt", "parody:dragon ball gt",
"parody:dragon ball heroes",
"parody:dragon ball super", "parody:dragon ball super",
"parody:dragon ball z", "parody:dragon ball z",
"parody:dragon half", "parody:dragon half",
@@ -628,6 +634,7 @@ object Parody : TagList {
"parody:elemental gelade", "parody:elemental gelade",
"parody:elf-san wa yaserarenai.", "parody:elf-san wa yaserarenai.",
"parody:elfen lied", "parody:elfen lied",
"parody:elfquest",
"parody:emma a victorian romance", "parody:emma a victorian romance",
"parody:endless frontier", "parody:endless frontier",
"parody:enen no shouboutai", "parody:enen no shouboutai",
@@ -705,12 +712,14 @@ object Parody : TagList {
"parody:final romance", "parody:final romance",
"parody:fire emblem", "parody:fire emblem",
"parody:fire emblem awakening", "parody:fire emblem awakening",
"parody:fire emblem fates",
"parody:fire emblem gaiden", "parody:fire emblem gaiden",
"parody:fire emblem if", "parody:fire emblem genealogy of the holy war",
"parody:fire emblem mystery of the emblem", "parody:fire emblem mystery of the emblem",
"parody:fire emblem path of radiance", "parody:fire emblem path of radiance",
"parody:fire emblem radiant dawn", "parody:fire emblem radiant dawn",
"parody:fire emblem rekka no ken", "parody:fire emblem the binding blade",
"parody:fire emblem the blazing blade",
"parody:fire emblem the sacred stones", "parody:fire emblem the sacred stones",
"parody:fire emblem three houses", "parody:fire emblem three houses",
"parody:fist of the north star", "parody:fist of the north star",
@@ -773,6 +782,7 @@ object Parody : TagList {
"parody:gakkou gurashi", "parody:gakkou gurashi",
"parody:gakkou no kaidan", "parody:gakkou no kaidan",
"parody:gakuen alice", "parody:gakuen alice",
"parody:gakuen babysitters",
"parody:gakuen heaven", "parody:gakuen heaven",
"parody:gakusen toshi asterisk", "parody:gakusen toshi asterisk",
"parody:galactic drifter vifam", "parody:galactic drifter vifam",
@@ -807,6 +817,7 @@ object Parody : TagList {
"parody:genroh", "parody:genroh",
"parody:genshiken", "parody:genshiken",
"parody:genshin impact", "parody:genshin impact",
"parody:gensou suikoden",
"parody:getbackers", "parody:getbackers",
"parody:getsumen to heiki mina", "parody:getsumen to heiki mina",
"parody:getter robo", "parody:getter robo",
@@ -830,6 +841,7 @@ object Parody : TagList {
"parody:gochuumon wa usagi desu ka", "parody:gochuumon wa usagi desu ka",
"parody:god eater", "parody:god eater",
"parody:god of war", "parody:god of war",
"parody:goddess of victory nikke",
"parody:gogo sentai boukenger", "parody:gogo sentai boukenger",
"parody:gokudou-kun manyuuki", "parody:gokudou-kun manyuuki",
"parody:gokujou seitokai", "parody:gokujou seitokai",
@@ -839,6 +851,7 @@ object Parody : TagList {
"parody:golden sun", "parody:golden sun",
"parody:goldfish warning", "parody:goldfish warning",
"parody:goof troop", "parody:goof troop",
"parody:gormiti",
"parody:gosenzo san-e", "parody:gosenzo san-e",
"parody:goshujin-sama to kemonomimi no shoujo mel", "parody:goshujin-sama to kemonomimi no shoujo mel",
"parody:goshuushou-sama ninomiya-kun", "parody:goshuushou-sama ninomiya-kun",
@@ -911,12 +924,14 @@ object Parody : TagList {
"parody:hakushon daimaou", "parody:hakushon daimaou",
"parody:half-life", "parody:half-life",
"parody:halo", "parody:halo",
"parody:hametsu no oukoku",
"parody:hamtaro", "parody:hamtaro",
"parody:hana no joshi announcer newscaster etsuko", "parody:hana no joshi announcer newscaster etsuko",
"parody:hanamaru youchien", "parody:hanamaru youchien",
"parody:hanasaku iroha", "parody:hanasaku iroha",
"parody:hanaukyo maid tai", "parody:hanaukyo maid tai",
"parody:hand maid may", "parody:hand maid may",
"parody:hantsu x trash",
"parody:hanzasky", "parody:hanzasky",
"parody:happiness", "parody:happiness",
"parody:happinesscharge precure", "parody:happinesscharge precure",
@@ -932,11 +947,13 @@ object Parody : TagList {
"parody:hataage kemono michi", "parody:hataage kemono michi",
"parody:hataraku onii-san", "parody:hataraku onii-san",
"parody:hataraku saibou", "parody:hataraku saibou",
"parody:hatena no tou",
"parody:hateshinaku aoi kono sora no shita de...", "parody:hateshinaku aoi kono sora no shita de...",
"parody:hatsukoi limited", "parody:hatsukoi limited",
"parody:hayate no gotoku", "parody:hayate no gotoku",
"parody:hayate x blade", "parody:hayate x blade",
"parody:hazun de catch", "parody:hazun de catch",
"parody:hazure waku",
"parody:he is my master", "parody:he is my master",
"parody:he-man and the masters of the universe", "parody:he-man and the masters of the universe",
"parody:heartcatch precure", "parody:heartcatch precure",
@@ -969,6 +986,7 @@ object Parody : TagList {
"parody:hime kishi lilia", "parody:hime kishi lilia",
"parody:hime-chans ribbon", "parody:hime-chans ribbon",
"parody:himegoto", "parody:himegoto",
"parody:himiko-den",
"parody:himitsu no akko-chan", "parody:himitsu no akko-chan",
"parody:himitsu sentai metamor v", "parody:himitsu sentai metamor v",
"parody:hinabita", "parody:hinabita",
@@ -1004,6 +1022,7 @@ object Parody : TagList {
"parody:how the grinch stole christmas", "parody:how the grinch stole christmas",
"parody:how to train your dragon", "parody:how to train your dragon",
"parody:howls moving castle", "parody:howls moving castle",
"parody:hp himepara",
"parody:hugtto precure", "parody:hugtto precure",
"parody:hulu xiongdi", "parody:hulu xiongdi",
"parody:hunter x hunter", "parody:hunter x hunter",
@@ -1092,6 +1111,7 @@ object Parody : TagList {
"parody:jewelpet tinkle", "parody:jewelpet tinkle",
"parody:jibaku shounen hanako-kun", "parody:jibaku shounen hanako-kun",
"parody:jigoku shoujo", "parody:jigoku shoujo",
"parody:jiisan baasan wakagaeru",
"parody:jijou wo shiranai tenkousei ga guigui kuru.", "parody:jijou wo shiranai tenkousei ga guigui kuru.",
"parody:jikkyou powerful pro yakyuu", "parody:jikkyou powerful pro yakyuu",
"parody:jikuu senshi spielban", "parody:jikuu senshi spielban",
@@ -1142,6 +1162,7 @@ object Parody : TagList {
"parody:kageki shojo", "parody:kageki shojo",
"parody:kagihime monogatari eikyuu alice rondo", "parody:kagihime monogatari eikyuu alice rondo",
"parody:kagura reimeiki", "parody:kagura reimeiki",
"parody:kagurabachi",
"parody:kaguya-sama wa kokurasetai", "parody:kaguya-sama wa kokurasetai",
"parody:kaichou wa maid-sama", "parody:kaichou wa maid-sama",
"parody:kaifuku jutsushi no yarinaoshi", "parody:kaifuku jutsushi no yarinaoshi",
@@ -1187,12 +1208,15 @@ object Parody : TagList {
"parody:kappa no kaikata", "parody:kappa no kaikata",
"parody:kara no kyoukai", "parody:kara no kyoukai",
"parody:kara no naka no kotori", "parody:kara no naka no kotori",
"parody:karakai jouzu no takagi-san",
"parody:karakuri kiden", "parody:karakuri kiden",
"parody:kare kano", "parody:kare kano",
"parody:kashimashi", "parody:kashimashi",
"parody:kasumin", "parody:kasumin",
"parody:katawa shoujo", "parody:katawa shoujo",
"parody:katekyo hitman reborn", "parody:katekyo hitman reborn",
"parody:katri girl of the meadows",
"parody:katsute kami datta kemono-tachi e",
"parody:katsute mahou shoujo to aku wa tekitai shite ita.", "parody:katsute mahou shoujo to aku wa tekitai shite ita.",
"parody:katte ni kaizou", "parody:katte ni kaizou",
"parody:kawaii dake ja nai shikimori-san", "parody:kawaii dake ja nai shikimori-san",
@@ -1293,6 +1317,7 @@ object Parody : TagList {
"parody:kozure ookami", "parody:kozure ookami",
"parody:kubo-san wa mob o yurusanai", "parody:kubo-san wa mob o yurusanai",
"parody:kumo desu ga nani ka", "parody:kumo desu ga nani ka",
"parody:kung fu cooking girls",
"parody:kung fu panda", "parody:kung fu panda",
"parody:kunoichi", "parody:kunoichi",
"parody:kuon no kizuna", "parody:kuon no kizuna",
@@ -1394,6 +1419,8 @@ object Parody : TagList {
"parody:lupin iii", "parody:lupin iii",
"parody:lux-pain", "parody:lux-pain",
"parody:lv1 maou to one room yuusha", "parody:lv1 maou to one room yuusha",
"parody:lv2 kara cheat datta motoyuusha kouho no mattari isekai life",
"parody:lydie and suelle no atelier",
"parody:mabinogi", "parody:mabinogi",
"parody:macademi wasshoi", "parody:macademi wasshoi",
"parody:machikado mazoku", "parody:machikado mazoku",
@@ -1458,10 +1485,12 @@ object Parody : TagList {
"parody:makai kishi ingrid", "parody:makai kishi ingrid",
"parody:makai tenshi jibril", "parody:makai tenshi jibril",
"parody:makai toushi saga", "parody:makai toushi saga",
"parody:make heroine ga oosugiru",
"parody:maken-ki", "parody:maken-ki",
"parody:makyou gaiden le deus", "parody:makyou gaiden le deus",
"parody:mama is a 4th grader", "parody:mama is a 4th grader",
"parody:mama wa poyopoyo saurus ga osuki", "parody:mama wa poyopoyo saurus ga osuki",
"parody:mamahaha no tsurego ga motokano datta",
"parody:mamono musume zukan", "parody:mamono musume zukan",
"parody:mamoru-kun", "parody:mamoru-kun",
"parody:mamoru-kun ni megami no shukufuku wo", "parody:mamoru-kun ni megami no shukufuku wo",
@@ -1471,6 +1500,7 @@ object Parody : TagList {
"parody:mangaka-san to assistant-san to", "parody:mangaka-san to assistant-san to",
"parody:manyuu hikenchou", "parody:manyuu hikenchou",
"parody:maou gakuin no futekigousha", "parody:maou gakuin no futekigousha",
"parody:maou no ore ga dorei elf o yome ni shitanda ga dou medereba ii",
"parody:maou to ore no hangyakuki", "parody:maou to ore no hangyakuki",
"parody:maoujou de oyasumi", "parody:maoujou de oyasumi",
"parody:maoyuu maou yuusha", "parody:maoyuu maou yuusha",
@@ -1520,6 +1550,7 @@ object Parody : TagList {
"parody:megami paradise", "parody:megami paradise",
"parody:megami-ryou no ryoubo-kun.", "parody:megami-ryou no ryoubo-kun.",
"parody:megamind", "parody:megamind",
"parody:mehime no toriko",
"parody:meiken lassie", "parody:meiken lassie",
"parody:meili xinshijie i", "parody:meili xinshijie i",
"parody:melon-chan no seichouki", "parody:melon-chan no seichouki",
@@ -1611,6 +1642,7 @@ object Parody : TagList {
"parody:my life as a teenage robot", "parody:my life as a teenage robot",
"parody:my little pony friendship is magic", "parody:my little pony friendship is magic",
"parody:my neighbor totoro", "parody:my neighbor totoro",
"parody:my wife is a demon queen",
"parody:myriad colors phantom world", "parody:myriad colors phantom world",
"parody:myst", "parody:myst",
"parody:na lesnoy trope", "parody:na lesnoy trope",
@@ -1679,6 +1711,7 @@ object Parody : TagList {
"parody:nogizaka haruka no himitsu", "parody:nogizaka haruka no himitsu",
"parody:non anonymous instruction", "parody:non anonymous instruction",
"parody:non non biyori", "parody:non non biyori",
"parody:nora to toki no koubou kiri no mori no majo",
"parody:nozoki ana", "parody:nozoki ana",
"parody:nurarihyon no mago", "parody:nurarihyon no mago",
"parody:nurse angel ririka sos", "parody:nurse angel ririka sos",
@@ -1733,7 +1766,9 @@ object Parody : TagList {
"parody:ookiku furikabutte", "parody:ookiku furikabutte",
"parody:oounabara to wadanohara", "parody:oounabara to wadanohara",
"parody:ooyasan wa shishunki", "parody:ooyasan wa shishunki",
"parody:operators side",
"parody:ore dake haireru kakushi dungeon", "parody:ore dake haireru kakushi dungeon",
"parody:ore ga suki nano wa imouto dakedo imouto ja nai",
"parody:ore monogatari", "parody:ore monogatari",
"parody:ore no imouto ga konna ni kawaii wake ga nai", "parody:ore no imouto ga konna ni kawaii wake ga nai",
"parody:ore no kanojo to osananajimi ga shuraba sugiru", "parody:ore no kanojo to osananajimi ga shuraba sugiru",
@@ -1894,6 +1929,9 @@ object Parody : TagList {
"parody:resident evil", "parody:resident evil",
"parody:resonance of fate", "parody:resonance of fate",
"parody:ressha sentai toqger", "parody:ressha sentai toqger",
"parody:return to shironagasu island",
"parody:revelation online",
"parody:reverse 1999",
"parody:revevolution", "parody:revevolution",
"parody:revolutionary girl utena", "parody:revolutionary girl utena",
"parody:rewrite", "parody:rewrite",
@@ -1941,6 +1979,7 @@ object Parody : TagList {
"parody:saenai heroine no sodatekata", "parody:saenai heroine no sodatekata",
"parody:saga frontier", "parody:saga frontier",
"parody:saijaku muhai no bahamut", "parody:saijaku muhai no bahamut",
"parody:saijaku tamer wa gomi hiroi no tabi o hajimemashita.",
"parody:saijou no meii", "parody:saijou no meii",
"parody:saikano", "parody:saikano",
"parody:saikin yatotta maid ga ayashii", "parody:saikin yatotta maid ga ayashii",
@@ -1963,6 +2002,9 @@ object Parody : TagList {
"parody:samurai 7", "parody:samurai 7",
"parody:samurai champloo", "parody:samurai champloo",
"parody:samurai pizza cats", "parody:samurai pizza cats",
)
override fun getTags2(): List<String> = listOf(
"parody:samurai sentai shinkenger", "parody:samurai sentai shinkenger",
"parody:samurai spirits", "parody:samurai spirits",
"parody:samurai warriors", "parody:samurai warriors",
@@ -1989,6 +2031,7 @@ object Parody : TagList {
"parody:scott pilgrim", "parody:scott pilgrim",
"parody:scp foundation", "parody:scp foundation",
"parody:sd gundam sangokuden", "parody:sd gundam sangokuden",
"parody:seer",
"parody:sei juushi bismark", "parody:sei juushi bismark",
"parody:sei senshi yariman 12", "parody:sei senshi yariman 12",
"parody:seijo no maryoku wa bannou desu", "parody:seijo no maryoku wa bannou desu",
@@ -2002,9 +2045,6 @@ object Parody : TagList {
"parody:seirei no moribito", "parody:seirei no moribito",
"parody:seishoujo sentai lakers", "parody:seishoujo sentai lakers",
"parody:seishun buta yarou wa bunny girl senpai no yume o minai", "parody:seishun buta yarou wa bunny girl senpai no yume o minai",
)
override fun getTags2(): List<String> = listOf(
"parody:seito kaichou hikaru", "parody:seito kaichou hikaru",
"parody:seitokai no ichizon", "parody:seitokai no ichizon",
"parody:seitokai yakuindomo", "parody:seitokai yakuindomo",
@@ -2185,6 +2225,7 @@ object Parody : TagList {
"parody:star wars", "parody:star wars",
"parody:star-myu", "parody:star-myu",
"parody:starcraft", "parody:starcraft",
"parody:stargate",
"parody:starry sky", "parody:starry sky",
"parody:station memories", "parody:station memories",
"parody:steam detectives", "parody:steam detectives",
@@ -2199,7 +2240,6 @@ object Parody : TagList {
"parody:strike witches", "parody:strike witches",
"parody:sucker punch", "parody:sucker punch",
"parody:suigetsu", "parody:suigetsu",
"parody:suikoden",
"parody:suikoden v", "parody:suikoden v",
"parody:suisei no gargantia", "parody:suisei no gargantia",
"parody:suite precure", "parody:suite precure",
@@ -2297,7 +2337,9 @@ object Parody : TagList {
"parody:tenkuu senki shurato", "parody:tenkuu senki shurato",
"parody:tenkuu shinpan", "parody:tenkuu shinpan",
"parody:tensai ryouri shounen ajinosuke", "parody:tensai ryouri shounen ajinosuke",
"parody:tensei kizoku kantei skill de nariagaru",
"parody:tensei oujo to tensai reijou no mahou kakumei", "parody:tensei oujo to tensai reijou no mahou kakumei",
"parody:tensei shitara dainana ouji datta node kimama ni majutsu o kiwamemasu",
"parody:tensei shitara slime datta ken", "parody:tensei shitara slime datta ken",
"parody:tenshi na konamaiki", "parody:tenshi na konamaiki",
"parody:tenshi ni narumon", "parody:tenshi ni narumon",
@@ -2360,6 +2402,7 @@ object Parody : TagList {
"parody:the legend of heroes", "parody:the legend of heroes",
"parody:the legend of korra", "parody:the legend of korra",
"parody:the legend of luo xiaohei", "parody:the legend of luo xiaohei",
"parody:the legend of the condor heroes",
"parody:the legend of the legendary heroes", "parody:the legend of the legendary heroes",
"parody:the legend of zelda", "parody:the legend of zelda",
"parody:the life and times of juniper lee", "parody:the life and times of juniper lee",
@@ -2386,6 +2429,7 @@ object Parody : TagList {
"parody:the princess and the frog", "parody:the princess and the frog",
"parody:the proud family", "parody:the proud family",
"parody:the queen of duellist", "parody:the queen of duellist",
"parody:the queens gambit",
"parody:the ren and stimpy show", "parody:the ren and stimpy show",
"parody:the replacements", "parody:the replacements",
"parody:the rescuers", "parody:the rescuers",
@@ -2435,7 +2479,7 @@ object Parody : TagList {
"parody:togainu no chi", "parody:togainu no chi",
"parody:toheart2", "parody:toheart2",
"parody:toji no miko", "parody:toji no miko",
"parody:tokidoki bosotto russia-go de dereru tonari no aalya-san", "parody:tokidoki bosotto russia-go de dereru tonari no alya-san",
"parody:tokimeki memorial", "parody:tokimeki memorial",
"parody:tokusatsu gagaga", "parody:tokusatsu gagaga",
"parody:tokusou sentai dekaranger", "parody:tokusou sentai dekaranger",
@@ -2453,6 +2497,7 @@ object Parody : TagList {
"parody:tonagura", "parody:tonagura",
"parody:tonari no kaibutsu-kun", "parody:tonari no kaibutsu-kun",
"parody:tonari no kyuuketsuki-san", "parody:tonari no kyuuketsuki-san",
"parody:tongari boushi",
"parody:tongari boushi no atelier", "parody:tongari boushi no atelier",
"parody:tonikaku kawaii", "parody:tonikaku kawaii",
"parody:toradora", "parody:toradora",
@@ -2503,6 +2548,7 @@ object Parody : TagList {
"parody:uchuu no kishi tekkaman", "parody:uchuu no kishi tekkaman",
"parody:uchuu no stellvia", "parody:uchuu no stellvia",
"parody:uchuu senshi baldios", "parody:uchuu senshi baldios",
"parody:uchuu show e youkoso",
"parody:uchuujin tanaka tarou", "parody:uchuujin tanaka tarou",
"parody:ufo princess valkyrie", "parody:ufo princess valkyrie",
"parody:ukagaka", "parody:ukagaka",
@@ -2512,6 +2558,7 @@ object Parody : TagList {
"parody:uma musume pretty derby", "parody:uma musume pretty derby",
"parody:umi ga kikoeru", "parody:umi ga kikoeru",
"parody:umi monogatari", "parody:umi monogatari",
"parody:umibe no etranger",
"parody:umineko no naku koro ni", "parody:umineko no naku koro ni",
"parody:un-go", "parody:un-go",
"parody:unbalance x unbalance", "parody:unbalance x unbalance",
@@ -2541,6 +2588,7 @@ object Parody : TagList {
"parody:vandread", "parody:vandread",
"parody:vanitas no carte", "parody:vanitas no carte",
"parody:variable geo", "parody:variable geo",
"parody:various",
"parody:vatican kiseki chousakan", "parody:vatican kiseki chousakan",
"parody:venus and braves", "parody:venus and braves",
"parody:venus blood -ragnarok-", "parody:venus blood -ragnarok-",
@@ -2550,7 +2598,9 @@ object Parody : TagList {
"parody:video girl ai", "parody:video girl ai",
"parody:viewtiful joe", "parody:viewtiful joe",
"parody:vindictus", "parody:vindictus",
"parody:violated heroine",
"parody:violinist of hameln", "parody:violinist of hameln",
"parody:viorate no atelier",
"parody:viper", "parody:viper",
"parody:viper ctr", "parody:viper ctr",
"parody:viper f40", "parody:viper f40",
@@ -2565,6 +2615,7 @@ object Parody : TagList {
"parody:vocaloid", "parody:vocaloid",
"parody:voiceroid", "parody:voiceroid",
"parody:voltage fighter gowcaizer", "parody:voltage fighter gowcaizer",
"parody:vshojo",
"parody:w.i.t.c.h.", "parody:w.i.t.c.h.",
"parody:wagaya no oinari-sama", "parody:wagaya no oinari-sama",
"parody:waka okami wa shougakusei", "parody:waka okami wa shougakusei",
@@ -2583,6 +2634,7 @@ object Parody : TagList {
"parody:warship girls", "parody:warship girls",
"parody:warzard", "parody:warzard",
"parody:washio sumi wa yuusha de aru", "parody:washio sumi wa yuusha de aru",
"parody:watashi ga koibito ni nareru wake nai jan muri muri muri ja nakatta",
"parody:watashi ga motete dousunda", "parody:watashi ga motete dousunda",
"parody:watashi ni tenshi ga maiorita", "parody:watashi ni tenshi ga maiorita",
"parody:watashi no ashinaga ojisan", "parody:watashi no ashinaga ojisan",
@@ -2599,6 +2651,7 @@ object Parody : TagList {
"parody:white album", "parody:white album",
"parody:wild arms", "parody:wild arms",
"parody:wild arms 2", "parody:wild arms 2",
"parody:windbreaker",
"parody:wingman", "parody:wingman",
"parody:wings of honneamise", "parody:wings of honneamise",
"parody:winnie the pooh", "parody:winnie the pooh",
@@ -2657,15 +2710,18 @@ object Parody : TagList {
"parody:yoake mae yori ruriiro na", "parody:yoake mae yori ruriiro na",
"parody:yofukashi no uta", "parody:yofukashi no uta",
"parody:yokohama kaidashi kikou", "parody:yokohama kaidashi kikou",
"parody:yoku wakaru gendai mahou",
"parody:yomawari", "parody:yomawari",
"parody:yondemasuyo azazel-san", "parody:yondemasuyo azazel-san",
"parody:yongbi bulpae", "parody:yongbi bulpae",
"parody:yoroiden samurai troopers", "parody:yoroiden samurai troopers",
"parody:yoru ga kuru", "parody:yoru ga kuru",
"parody:yoru no kurage wa oyogenai",
"parody:yoru no yatterman", "parody:yoru no yatterman",
"parody:yoshinaga-san chi no gargoyle", "parody:yoshinaga-san chi no gargoyle",
"parody:yosuga no sora", "parody:yosuga no sora",
"parody:yotsubato", "parody:yotsubato",
"parody:you shou yan",
"parody:youjo senki", "parody:youjo senki",
"parody:youjuu senki a.d. 2048", "parody:youjuu senki a.d. 2048",
"parody:youkai hyakkitan", "parody:youkai hyakkitan",
@@ -2714,7 +2770,6 @@ object Parody : TagList {
"parody:zettai muteki raijin-oh", "parody:zettai muteki raijin-oh",
"parody:zettai shougeki platonic heart", "parody:zettai shougeki platonic heart",
"parody:zettai zetsumei toshi 3", "parody:zettai zetsumei toshi 3",
"parody:zhongfan weilai 1999",
"parody:zoids", "parody:zoids",
"parody:zoids genesis", "parody:zoids genesis",
"parody:zoids new century", "parody:zoids new century",

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