Compare commits

..

45 Commits

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

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

* MangaUpdates implementation

* Add logging and toast on error.

* MyAnimeList implementation

* Kitsu implementation

* Fix MAL authors and artists

* Decode AL description

* Throw NotImplementedError instead of returning null

* Use logcat from LogcatExtensions

* Replace strings with MR strings

* Missed a string

* Delete unused Author class.

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

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

* Exclude enhanced trackers from tracker select dialog

* MdList implementation

* Remember getTracks and trackerManager

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

---------

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

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

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

Declares all listed devices as unable to use hardware bitmaps.

Might fix #1541.

* Hide Hardware Bitmap Threshold setting if unusable

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

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

* Add missing negation

* Update CHANGELOG.md

* Update CHANGELOG.md

* Needs to be and not or

Also fix typo in CHANGELOG.md

---------

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

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

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

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

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

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

(cherry picked from commit 9aef08c333397caa4b897514cf76966592d3849c)

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

(cherry picked from commit dcddac5daaff3ec89c8507c35dc13d345ffdb6d7)

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

(cherry picked from commit 1909126921ac78309f7f7c7c2aa85606611531b8)

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

(cherry picked from commit e6d96bd348ea5d18a005d6465222ad5f5123103e)

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

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

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
2024-12-08 13:58:32 -05:00
Jobobby04 b9fffc45cc Fix idle status set 2024-12-08 13:47:54 -05:00
Jobobby04 de6cd169d0 Return newpage joineditems check 2024-12-08 13:37:29 -05:00
Jobobby04 95e8a02e33 Forgot import 2024-12-08 13:36:19 -05:00
Jobobby04 c720f0ac5c Increase new updates count when updates found 2024-12-08 13:35:58 -05:00
Jobobby04 76af3b59f0 Improve favorites sync statuses 2024-12-08 13:35:36 -05:00
Jobobby04 3f8cce8a32 Update tag lists 2024-12-08 13:31:38 -05:00
Jobobby04 26cfb4811f Fix a possible crash with auto-zoom 2024-11-07 22:21:39 -05:00
Jobobby04 e5a6d1b456 Fix a crash with migration list screen 2024-11-07 22:21:18 -05:00
Jobobby04 f0b621dfe5 Fix multiple issues with the E-Hentai updater 2024-11-07 22:21:02 -05:00
141 changed files with 3964 additions and 971 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 70
versionCode = 71
versionName = "1.11.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
@@ -1,5 +1,6 @@
package eu.kanade.core.util
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -45,21 +46,6 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
}
}
/**
* Returns a list containing only elements matching the given [predicate].
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) }
val destination = ArrayList<T>()
fastForEach { if (predicate(it)) destination.add(it) }
return destination
}
/**
* Returns a list containing all elements not matching the given [predicate].
*
@@ -70,27 +56,7 @@ inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
@OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
contract { callsInPlace(predicate) }
val destination = ArrayList<T>()
fastForEach { if (!predicate(it)) destination.add(it) }
return destination
}
/**
* Returns a list containing only the non-null results of applying the
* given [transform] function to each element in the original collection.
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
contract { callsInPlace(transform) }
val destination = ArrayList<R>()
fastForEach { element ->
transform(element)?.let(destination::add)
}
return destination
return fastFilter { !predicate(it) }
}
/**
@@ -131,26 +97,3 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
fastForEach { if (predicate(it)) --count }
return count
}
/**
* Returns a list containing only elements from the given collection
* having distinct keys returned by the given [selector] function.
*
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
* The elements in the resulting list are in the same order as they were in the source collection.
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@OptIn(ExperimentalContracts::class)
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
contract { callsInPlace(selector) }
val set = HashSet<K>()
val list = ArrayList<T>()
fastForEach {
val key = selector(it)
if (set.add(key)) list.add(it)
}
return list
}
@@ -2,6 +2,7 @@ package eu.kanade.domain.base
import android.content.Context
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.util.system.GLUtil
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.i18n.MR
@@ -31,5 +32,5 @@ class BasePreferences(
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
fun alwaysUseSSIVToDecode() = preferenceStore.getBoolean("pref_always_use_ssiv_to_decode", false)
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
}
@@ -21,9 +21,7 @@ internal fun LibraryTabs(
getNumberOfMangaForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit,
) {
// SY -->
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
// SY <--
Column(
modifier = Modifier.zIndex(1f),
) {
@@ -15,7 +15,6 @@ import androidx.compose.ui.window.DialogProperties
import exh.favorites.FavoritesSyncStatus
import kotlinx.coroutines.delay
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import kotlin.time.Duration.Companion.seconds
@@ -23,7 +22,6 @@ import kotlin.time.Duration.Companion.seconds
data class SyncFavoritesProgressProperties(
val title: String,
val text: String,
val canDismiss: Boolean,
val positiveButtonText: String? = null,
val positiveButton: (() -> Unit)? = null,
val negativeButtonText: String? = null,
@@ -34,18 +32,23 @@ data class SyncFavoritesProgressProperties(
fun SyncFavoritesProgressDialog(
status: FavoritesSyncStatus,
setStatusIdle: () -> Unit,
openManga: (Manga) -> Unit,
openManga: (Long) -> Unit,
) {
val context = LocalContext.current
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
when (status) {
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message),
canDismiss = false,
text = context.stringResource(
SYMR.strings.favorites_sync_bad_library_state,
context.stringResource(
SYMR.strings.favorites_sync_gallery_in_multiple_categories, status.mangaTitle,
status.categories.joinToString(),
),
),
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
positiveButton = {
openManga(status.manga)
openManga(status.mangaId)
setStatusIdle()
},
negativeButtonText = context.stringResource(MR.strings.action_ok),
@@ -53,31 +56,122 @@ fun SyncFavoritesProgressDialog(
)
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message),
canDismiss = false,
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message),
canDismiss = false,
text = context.stringResource(
SYMR.strings.favorites_sync_done_errors_message,
status.messages.joinToString(separator = "\n") {
when (it) {
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, it.url,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, it.title, it.gid)
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
}
},
),
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Idle -> value = null
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> {
is FavoritesSyncStatus.Initializing -> {
value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = status.message,
canDismiss = false,
text = context.stringResource(SYMR.strings.favorites_sync_initializing),
)
if (status is FavoritesSyncStatus.Processing && status.title != null) {
}
is FavoritesSyncStatus.SyncError -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(
SYMR.strings.favorites_sync_error_string,
when (status) {
FavoritesSyncStatus.SyncError.NotLoggedInSyncError -> context.stringResource(SYMR.strings.please_login)
FavoritesSyncStatus.SyncError.FailedToFetchFavorites ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_featch)
is FavoritesSyncStatus.SyncError.UnknownSyncError ->
context.stringResource(SYMR.strings.favorites_sync_unknown_error, status.message)
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_error, status.title, status.reason,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, status.title, status.url,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, status.title, status.gid)
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
},
),
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Processing -> {
val properties = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = when (status) {
FavoritesSyncStatus.Processing.VerifyingLibrary ->
context.stringResource(SYMR.strings.favorites_sync_verifying_library)
FavoritesSyncStatus.Processing.DownloadingFavorites ->
context.stringResource(SYMR.strings.favorites_sync_downloading)
FavoritesSyncStatus.Processing.CalculatingRemoteChanges ->
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes)
FavoritesSyncStatus.Processing.CalculatingLocalChanges ->
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes)
FavoritesSyncStatus.Processing.SyncingCategoryNames ->
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names)
is FavoritesSyncStatus.Processing.RemovingRemoteGalleries ->
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, status.galleryCount)
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
if (status.isThrottling) {
context.stringResource(
SYMR.strings.favorites_sync_processing_throttle,
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total),
)
} else {
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
}
is FavoritesSyncStatus.Processing.RemovingGalleryFromLocal ->
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, status.index, status.total)
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
if (status.isThrottling) {
context.stringResource(
SYMR.strings.favorites_sync_processing_throttle,
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total),
)
} else {
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
}
FavoritesSyncStatus.Processing.CleaningUp ->
context.stringResource(SYMR.strings.favorites_sync_cleaning_up)
},
)
value = properties
if (
status is FavoritesSyncStatus.Processing.AddingGalleryToRemote ||
status is FavoritesSyncStatus.Processing.AddingGalleryToLocal
) {
delay(5.seconds)
value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = status.delayedMessage ?: status.message,
canDismiss = false,
value = properties.copy(
text = when (status) {
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
properties.text + "\n\n" + status.title
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
properties.text + "\n\n" + status.title
else -> properties.text
},
)
}
}
@@ -112,8 +206,8 @@ fun SyncFavoritesProgressDialog(
}
},
properties = DialogProperties(
dismissOnClickOutside = dialog.canDismiss,
dismissOnBackPress = dialog.canDismiss,
dismissOnClickOutside = false,
dismissOnBackPress = false,
),
)
}
@@ -165,12 +165,12 @@ sealed class Preference {
data class CustomPreference(
override val title: String,
val content: @Composable (PreferenceItem<String>) -> Unit,
) : PreferenceItem<String>() {
val content: @Composable () -> Unit,
) : PreferenceItem<Unit>() {
override val enabled: Boolean = true
override val subtitle: String? = null
override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
override val onValueChanged: suspend (newValue: Unit) -> Boolean = { true }
}
}
@@ -167,7 +167,7 @@ internal fun PreferenceItem(
InfoWidget(text = item.title)
}
is Preference.PreferenceItem.CustomPreference -> {
item.content(item)
item.content()
}
}
}
@@ -59,6 +59,7 @@ import eu.kanade.tachiyomi.source.AndroidSourceManager
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.isDevFlavor
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
@@ -83,6 +84,7 @@ import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -369,6 +371,26 @@ object SettingsAdvancedScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
pref = basePreferences.hardwareBitmapThreshold(),
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
subtitleProvider = { value, options ->
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
},
enabled = !ImageUtil.HARDWARE_BITMAP_UNSUPPORTED &&
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.mapIndexed { index, option ->
val display = if (index == 0) {
stringResource(MR.strings.pref_hardware_bitmap_threshold_default, option)
} else {
option.toString()
}
option to display
}
.toMap()
.toImmutableMap(),
),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(),
@@ -376,10 +398,6 @@ object SettingsAdvancedScreen : SearchableSettings {
chooseColorProfile.launch(arrayOf("*/*"))
},
),
Preference.PreferenceItem.SwitchPreference(
pref = basePreferences.alwaysUseSSIVToDecode(),
title = stringResource(MR.strings.pref_always_use_ssiv_to_decode),
),
),
)
}
@@ -139,7 +139,7 @@ object SettingsMangadexScreen : SearchableSettings {
title = mdex.name + " Login",
content = {
BasePreferenceWidget(
title = it.title,
title = mdex.name + " Login",
widget = {
Icon(
imageVector = Icons.Outlined.PeopleAlt,
@@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.cancelNotification
@@ -80,6 +81,7 @@ import org.conscrypt.Conscrypt
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR
@@ -175,6 +177,14 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
.onEach(FirebaseConfig::setCrashlyticsEnabled)
.launchIn(scope)
basePreferences.hardwareBitmapThreshold().let { preference ->
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
}
basePreferences.hardwareBitmapThreshold().changes()
.onEach { ImageUtil.hardwareBitmapThreshold = it }
.launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
// Updates widget update
@@ -15,7 +15,6 @@ import coil3.request.bitmapConfig
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
import eu.kanade.tachiyomi.util.system.GLUtil
import mihon.core.common.archive.archiveReader
import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil
@@ -71,7 +70,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
options.bitmapConfig == Bitmap.Config.HARDWARE &&
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
ImageUtil.canUseHardwareBitmap(bitmap)
) {
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
if (hwBitmap != null) {
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.library
import android.content.Context
import android.content.pm.ServiceInfo
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.work.BackoffPolicy
import androidx.work.Constraints
@@ -135,10 +137,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
override suspend fun doWork(): Result {
if (tags.contains(WORK_NAME_AUTO)) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
}
// Find a running manual worker. If exists, try again later
@@ -768,15 +772,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
if (interval > 0) {
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val constraints = Constraints(
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
},
requiresCharging = DEVICE_CHARGING in restrictions,
requiresBatteryNotLow = true,
)
val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
}
val networkRequestBuilder = NetworkRequest.Builder()
if (DEVICE_ONLY_ON_WIFI in restrictions) {
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
}
if (DEVICE_NETWORK_NOT_METERED in restrictions) {
networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
}
val constraints = Constraints.Builder()
// 'networkRequest' only applies to Android 9+, otherwise 'networkType' is used
.setRequiredNetworkRequest(networkRequestBuilder.build(), networkType)
.setRequiresCharging(DEVICE_CHARGING in restrictions)
.setRequiresBatteryNotLow(true)
.build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(),
@@ -30,6 +30,9 @@ object Notifications {
const val ID_LIBRARY_SIZE_WARNING = -103
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
const val ID_LIBRARY_ERROR = -102
const val CHANNEL_LIBRARY_EHENTAI = "library_ehentai_channel"
const val ID_EHENTAI_PROGRESS = -199
const val ID_EHENTAI_ERROR = -198
/**
* Notification channel and ids used by the downloader.
@@ -71,6 +74,7 @@ object Notifications {
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
const val ID_APP_UPDATER = 1
const val ID_APP_UPDATE_PROMPT = 2
const val ID_APP_UPDATE_ERROR = 3
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
const val ID_UPDATES_TO_EXTS = -401
const val ID_EXTENSION_INSTALLER = -402
@@ -166,6 +170,13 @@ object Notifications {
setGroup(GROUP_APK_UPDATES)
setName(context.stringResource(MR.strings.channel_ext_updates))
},
// SY -->
buildNotificationChannel(CHANNEL_LIBRARY_EHENTAI, IMPORTANCE_LOW) {
setName("EHentai")
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
// SY <--
),
)
}
@@ -6,6 +6,7 @@ import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.Flow
@@ -120,6 +121,10 @@ abstract class BaseTracker(
updateRemote(track)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
throw NotImplementedError("Not implemented.")
}
private suspend fun updateRemote(track: Track): Unit = withIOContext {
try {
update(track)
@@ -5,6 +5,7 @@ import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
@@ -82,4 +83,6 @@ interface Tracker {
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata?
}
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -232,6 +233,10 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
interceptor.setAuth(null)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
fun saveOAuth(alOAuth: ALOAuth?) {
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
}
@@ -5,15 +5,18 @@ import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
@@ -288,6 +291,71 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
val query = """
|query (${'$'}mangaId: Int!) {
|Media (id: ${'$'}mangaId) {
|id
|title {
|userPreferred
|}
|coverImage {
|large
|}
|description
|staff {
|edges {
|role
|node {
|name {
|userPreferred
|}
|}
|}
|}
|}
|}
|
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("mangaId", track.remoteId)
}
}
with(json) {
authClient.newCall(
POST(
API_URL,
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<ALMangaMetadata>()
.let {
val media = it.data.media
TrackMangaMetadata(
remoteId = media.id,
title = media.title.userPreferred,
thumbnailUrl = media.coverImage.large,
description = media.description?.htmlDecode()?.ifEmpty { null },
authors = media.staff.edges
.filter { it.role == "Story" || it.role == "Story & Art" }
.map { it.node.name.userPreferred }
.joinToString(", ")
.ifEmpty { null },
artists = media.staff.edges
.filter { it.role == "Art" || it.role == "Story & Art" }
.map { it.node.name.userPreferred }
.joinToString(", ")
.ifEmpty { null },
)
}
}
}
}
private fun createDate(dateValue: Long): JsonObject {
if (dateValue == 0L) {
return buildJsonObject {
@@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.data.track.anilist.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ALMangaMetadata(
val data: ALMangaMetadataData,
)
@Serializable
data class ALMangaMetadataData(
@SerialName("Media")
val media: ALMangaMetadataMedia,
)
@Serializable
data class ALMangaMetadataMedia(
val id: Long,
val title: ALItemTitle,
val coverImage: ItemCover,
val description: String?,
val staff: ALStaff,
)
@Serializable
data class ALStaff(
val edges: List<ALStaffEdge>,
)
@Serializable
data class ALStaffEdge(
val role: String,
val node: ALStaffNode,
)
@Serializable
data class ALStaffNode(
val name: ALItemTitle,
)
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -75,6 +76,10 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
return api.search(query)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
override suspend fun refresh(track: Track): Track {
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
track.copyPersonalFrom(remoteStatusTrack)
@@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMCollectionResponse
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchResult
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSubject
import eu.kanade.tachiyomi.data.track.bangumi.dto.Infobox
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
@@ -21,6 +24,7 @@ import tachiyomi.core.common.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import tachiyomi.domain.track.model.Track as DomainTrack
class BangumiApi(
private val trackId: Long,
@@ -127,6 +131,34 @@ class BangumiApi(
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
with(json) {
authClient.newCall(GET("${API_URL}/v0/subjects/${track.remoteId}"))
.awaitSuccess()
.parseAs<BGMSubject>()
.let {
TrackMangaMetadata(
remoteId = it.id,
title = it.nameCn,
thumbnailUrl = it.images?.common,
description = it.summary,
authors = it.infobox
.filter { it.key == "作者" }
.filterIsInstance<Infobox.SingleValue>()
.map { it.value }
.joinToString(", "),
artists = it.infobox
.filter { it.key == "插图" }
.filterIsInstance<Infobox.SingleValue>()
.map { it.value }
.joinToString(", "),
)
}
}
}
}
suspend fun accessToken(code: String): BGMOAuth {
return withIOContext {
with(json) {
@@ -0,0 +1,62 @@
package eu.kanade.tachiyomi.data.track.bangumi.dto
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class BGMSubject(
val images: BGMSearchItemCovers?,
val summary: String,
val name: String,
@SerialName("name_cn")
val nameCn: String,
val infobox: List<Infobox>,
val id: Long,
)
// infobox deserializer and related classes courtesy of
// https://github.com/Snd-R/komf/blob/4c260a3dcd326a5e1d74ac9662eec8124ab7e461/komf-core/src/commonMain/kotlin/snd/komf/providers/bangumi/model/BangumiSubject.kt#L53-L89
object InfoBoxSerializer : JsonContentPolymorphicSerializer<Infobox>(Infobox::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Infobox> {
if (element !is JsonObject) throw SerializationException("Expected JsonObject go ${element::class}")
val value = element["value"]
return when (value) {
is JsonArray -> Infobox.MultipleValues.serializer()
is JsonPrimitive -> Infobox.SingleValue.serializer()
else -> throw SerializationException("Unexpected element type ${element::class}")
}
}
}
@Serializable(with = InfoBoxSerializer::class)
sealed interface Infobox {
val key: String
@Serializable
class SingleValue(
override val key: String,
val value: String,
) : Infobox
@Serializable
class MultipleValues(
override val key: String,
val value: List<InfoboxNestedValue>,
) : Infobox
}
@Serializable
data class InfoboxNestedValue(
@SerialName("k")
val key: String? = null,
@SerialName("v")
val value: String,
)
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -139,6 +140,10 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
interceptor.newAuth(null)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return api.getMangaMetadata(track)
}
private fun getUserId(): String {
return getPassword()
}
@@ -6,8 +6,10 @@ import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAddMangaResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAlgoliaSearchResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuMangaMetadata
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET
@@ -15,6 +17,7 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
@@ -240,11 +243,80 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
val query = """
|query(${'$'}libraryId: ID!, ${'$'}staffCount: Int) {
|findLibraryEntryById(id: ${'$'}libraryId) {
|media {
|id
|titles {
|preferred
|}
|posterImage {
|original {
|url
|}
|}
|description
|staff(first: ${'$'}staffCount) {
|nodes {
|role
|person {
|name
|}
|}
|}
|}
|}
|}
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("libraryId", track.remoteId)
put("staffCount", 25) // 25 based on nothing
}
}
with(json) {
authClient.newCall(
POST(
GRAPHQL_URL,
headers = headersOf("Accept-Language", "en"),
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<KitsuMangaMetadata>()
.let {
val manga = it.data.findLibraryEntryById.media
TrackMangaMetadata(
remoteId = manga.id.toLong(),
title = manga.titles.preferred,
thumbnailUrl = manga.posterImage.original.url,
description = manga.description.en?.htmlDecode()?.ifEmpty { null },
authors = manga.staff.nodes
.filter { it.role == "Story" || it.role == "Story & Art" }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
artists = manga.staff.nodes
.filter { it.role == "Art" || it.role == "Story & Art" }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
)
}
}
}
}
companion object {
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val BASE_URL = "https://kitsu.app/api/edge/"
private const val GRAPHQL_URL = "https://kitsu.app/api/graphql"
private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
@@ -0,0 +1,63 @@
package eu.kanade.tachiyomi.data.track.kitsu.dto
import kotlinx.serialization.Serializable
@Serializable
data class KitsuMangaMetadata(
val data: KitsuMangaMetadataData,
)
@Serializable
data class KitsuMangaMetadataData(
val findLibraryEntryById: KitsuMangaMetadataById,
)
@Serializable
data class KitsuMangaMetadataById(
val media: KitsuMangaMetadataMedia,
)
@Serializable
data class KitsuMangaMetadataMedia(
val id: String,
val titles: KitsuMangaTitle,
val posterImage: KitsuMangaCover,
val description: KitsuMangaDescription,
val staff: KitsuMangaStaff,
)
@Serializable
data class KitsuMangaTitle(
val preferred: String,
)
@Serializable
data class KitsuMangaCover(
val original: KitsuMangaCoverUrl,
)
@Serializable
data class KitsuMangaCoverUrl(
val url: String,
)
@Serializable
data class KitsuMangaDescription(
val en: String?,
)
@Serializable
data class KitsuMangaStaff(
val nodes: List<KitsuMangaStaffNode>,
)
@Serializable
data class KitsuMangaStaffNode(
val role: String,
val person: KitsuMangaStaffPerson,
)
@Serializable
data class KitsuMangaStaffPerson(
val name: String,
)
@@ -10,7 +10,9 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUListItem
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.htmlDecode
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import tachiyomi.i18n.MR
@@ -117,6 +119,20 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
interceptor.newAuth(authenticated.sessionToken)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
val series = api.getSeries(track)
return series?.let {
TrackMangaMetadata(
it.seriesId,
it.title?.htmlDecode(),
it.image?.url?.original,
it.description?.htmlDecode(),
it.authors?.filter { it.type == "Author" }?.joinToString(separator = ", ") { it.name ?: "" },
it.authors?.filter { it.type == "Artist" }?.joinToString(separator = ", ") { it.name ?: "" },
)
}
}
fun restoreSession(): String? {
return trackPreferences.trackPassword(this).get().ifBlank { null }
}
@@ -190,6 +190,14 @@ class MangaUpdatesApi(
}
}
suspend fun getSeries(track: DomainTrack): MURecord {
return with(json) {
client.newCall(GET("$BASE_URL/v1/series/${track.remoteId}"))
.awaitSuccess()
.parseAs<MURecord>()
}
}
companion object {
private const val BASE_URL = "https://api.mangaupdates.com"
@@ -21,6 +21,7 @@ data class MURecord(
val ratingVotes: Int? = null,
@SerialName("latest_chapter")
val latestChapter: Int? = null,
val authors: List<MUAuthor>? = null,
)
fun MURecord.toTrackSearch(id: Long): TrackSearch {
@@ -36,3 +37,9 @@ fun MURecord.toTrackSearch(id: Long): TrackSearch {
start_date = this@toTrackSearch.year.toString()
}
}
@Serializable
data class MUAuthor(
val type: String? = null,
val name: String? = null,
)
@@ -2,9 +2,11 @@ package eu.kanade.tachiyomi.data.track.mdlist
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
@@ -168,6 +170,21 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
trackPreferences.trackToken(this).delete()
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return withIOContext {
val mdex = mdex ?: throw MangaDexNotFoundException()
val manga = mdex.getMangaMetadata(track.toDbTrack())
TrackMangaMetadata(
remoteId = 0,
title = manga?.title,
thumbnailUrl = manga?.thumbnail_url, // Doesn't load the actual cover because of Refer header
description = manga?.description,
authors = manga?.author,
artists = manga?.artist,
)
}
}
override val isLoggedIn: Boolean
get() = trackPreferences.trackToken(this).get().isNotEmpty()
@@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.data.track.model
data class TrackMangaMetadata(
val remoteId: Long? = null,
val title: String? = null,
val thumbnailUrl: String? = null,
val description: String? = null,
val authors: String? = null,
val artists: String? = null,
)
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import kotlinx.collections.immutable.ImmutableList
@@ -156,6 +157,10 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
interceptor.setAuth(null)
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
fun getIfAuthExpired(): Boolean {
return trackPreferences.trackAuthExpired(this).get()
}
@@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALMangaMetadata
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
@@ -193,6 +195,41 @@ class MyAnimeListApi(
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return withIOContext {
val url = "$BASE_API_URL/manga".toUri().buildUpon()
.appendPath(track.remoteId.toString())
.appendQueryParameter(
"fields",
"id,title,synopsis,main_picture,authors{first_name,last_name}",
)
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<MALMangaMetadata>()
.let {
TrackMangaMetadata(
remoteId = it.id,
title = it.title,
thumbnailUrl = it.covers.large.ifEmpty { null } ?: it.covers.medium,
description = it.synopsis,
authors = it.authors
.filter { it.role == "Story" || it.role == "Story & Art" }
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
.joinToString(separator = ", ")
.ifEmpty { null },
artists = it.authors
.filter { it.role == "Art" || it.role == "Story & Art" }
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
.joinToString(separator = ", ")
.ifEmpty { null },
)
}
}
}
}
private suspend fun getListPage(offset: Int): MALUserSearchResult {
return withIOContext {
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
@@ -23,4 +23,29 @@ data class MALManga(
@Serializable
data class MALMangaCovers(
val large: String = "",
val medium: String,
)
@Serializable
data class MALMangaMetadata(
val id: Long,
val title: String,
val synopsis: String?,
@SerialName("main_picture")
val covers: MALMangaCovers,
val authors: List<MALAuthor>,
)
@Serializable
data class MALAuthor(
val node: MALAuthorNode,
val role: String,
)
@Serializable
data class MALAuthorNode(
@SerialName("first_name")
val firstName: String,
@SerialName("last_name")
val lastName: String,
)
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
import kotlinx.collections.immutable.ImmutableList
@@ -98,6 +99,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
return track
}
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
return api.getMangaMetadata(track)
}
override fun getLogo() = R.drawable.ic_tracker_shikimori
override fun getLogoColor() = Color.rgb(40, 40, 40)
@@ -3,9 +3,11 @@ package eu.kanade.tachiyomi.data.track.shikimori
import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMManga
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMMetadata
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry
@@ -132,6 +134,65 @@ class ShikimoriApi(
}
}
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
return withIOContext {
val query = """
|query(${'$'}ids: String!) {
|mangas(ids: ${'$'}ids) {
|id
|name
|description
|poster {
|originalUrl
|}
|personRoles {
|person {
|name
|}
|rolesEn
|}
|}
|}
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("ids", "${track.remoteId}")
}
}
with(json) {
authClient.newCall(
POST(
"https://shikimori.one/api/graphql",
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<SMMetadata>()
.let {
if (it.data.mangas.isEmpty()) throw Exception("Could not get metadata from Shikimori")
val manga = it.data.mangas[0]
TrackMangaMetadata(
remoteId = manga.id.toLong(),
title = manga.name,
thumbnailUrl = manga.poster.originalUrl,
description = manga.description,
authors = manga.personRoles
.filter { it.rolesEn.contains("Story") || it.rolesEn.contains("Story & Art") }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
artists = manga.personRoles
.filter { it.rolesEn.contains("Art") || it.rolesEn.contains("Story & Art") }
.map { it.person.name }
.joinToString(", ")
.ifEmpty { null },
)
}
}
}
}
suspend fun accessToken(code: String): SMOAuth {
return withIOContext {
with(json) {
@@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.data.track.shikimori.dto
import kotlinx.serialization.Serializable
@Serializable
data class SMMetadata(
val data: SMMetadataData,
)
@Serializable
data class SMMetadataData(
val mangas: List<SMMetadataResult>,
)
@Serializable
data class SMMetadataResult(
val id: String,
val name: String,
val description: String,
val poster: SMMangaPoster,
val personRoles: List<SMMangaPersonRoles>,
)
@Serializable
data class SMMangaPoster(
val originalUrl: String,
)
@Serializable
data class SMMangaPersonRoles(
val person: SMPerson,
val rolesEn: List<String>,
)
@Serializable
data class SMPerson(
val name: String,
)
@@ -181,9 +181,9 @@ internal class AppUpdateNotifier(private val context: Context) {
addAction(
R.drawable.ic_close_24dp,
context.stringResource(MR.strings.action_cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_ERROR),
)
}
notificationBuilder.show(Notifications.ID_APP_UPDATER)
notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR)
}
}
@@ -313,6 +313,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return similarHandler.getRelated(manga)
}
suspend fun getMangaMetadata(track: Track): SManga? {
return mangaHandler.getMangaMetadata(track, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc())
}
companion object {
private const val dataSaverPref = "dataSaverV5"
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -29,8 +27,7 @@ import tachiyomi.i18n.sy.SYMR
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
@delegate:Transient
var newSelectedItem by mutableStateOf<Pair<Long, Long>?>(null)
var newSelectedItem: Pair<Long, Long>? = null
@Composable
override fun Content() {
@@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import exh.ui.ifSourcesLoaded
import kotlinx.collections.immutable.persistentListOf
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold
@@ -71,7 +71,6 @@ data class SourceSearchScreen(
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
val openMigrateDialog: (Manga) -> Unit = {
// SY -->
navigator.items
@@ -83,7 +82,7 @@ data class SourceSearchScreen(
}
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.InputChip
import androidx.compose.material3.InputChipDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -154,7 +155,7 @@ fun AutoCompleteTextField(
null
},
modifier = Modifier
.menuAnchor()
.menuAnchor(MenuAnchorType.PrimaryEditable)
.fillMaxWidth()
.runOnEnterKeyPressed { submit() },
singleLine = true,
@@ -32,7 +32,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -61,6 +60,7 @@ import exh.ui.ifSourcesLoaded
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.UnsortedPreferences
@@ -240,11 +240,9 @@ data class BrowseSourceScreen(
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -30,13 +30,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabNavigator
import eu.kanade.core.preference.asState
import eu.kanade.core.util.fastFilter
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.util.Screen
@@ -7,16 +7,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMapNotNull
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.PreferenceMutableState
import eu.kanade.core.preference.asState
import eu.kanade.core.util.fastDistinctBy
import eu.kanade.core.util.fastFilter
import eu.kanade.core.util.fastFilterNot
import eu.kanade.core.util.fastMapNotNull
import eu.kanade.core.util.fastPartition
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.interactor.SetReadStatus
@@ -334,8 +334,8 @@ data object LibraryTab : Tab {
// SY -->
SyncFavoritesProgressDialog(
status = screenModel.favoritesSync.status.collectAsState().value,
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
openManga = { navigator.push(MangaScreen(it.id)) },
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle },
openManga = { navigator.push(MangaScreen(it)) },
)
// SY <--
@@ -3,20 +3,26 @@ package eu.kanade.tachiyomi.ui.manga
import android.content.Context
import android.view.LayoutInflater
import android.widget.ArrayAdapter
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.core.view.children
@@ -26,22 +32,34 @@ import coil3.transform.RoundedCornersTransformation
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
import exh.util.dropBlank
import exh.util.trimOrNull
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
fun EditMangaDialog(
@@ -61,6 +79,10 @@ fun EditMangaDialog(
var binding by remember {
mutableStateOf<EditMangaDialogBinding?>(null)
}
val showTrackerSelectionDialogue = remember { mutableStateOf(false) }
val getTracks = remember { Injekt.get<GetTracks>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val tracks = remember { mutableStateOf(emptyList<Pair<Track, Tracker>>()) }
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
@@ -109,7 +131,7 @@ fun EditMangaDialog(
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
.also { binding = it }
.apply {
onViewCreated(manga, factoryContext, this, scope)
onViewCreated(manga, factoryContext, this, scope, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
}
.root
},
@@ -118,9 +140,61 @@ fun EditMangaDialog(
}
},
)
if (showTrackerSelectionDialogue.value) {
TrackerSelectDialog(
tracks = tracks.value,
onDismissRequest = { showTrackerSelectionDialogue.value = false },
onTrackerSelect = { tracker, track ->
scope.launch {
autofillFromTracker(binding!!, track, tracker)
}
},
)
}
}
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope) {
@Composable
private fun TrackerSelectDialog(
tracks: List<Pair<Track, Tracker>>,
onDismissRequest: () -> Unit,
onTrackerSelect: (
tracker: Tracker,
track: Track,
) -> Unit,
) {
AlertDialog(
modifier = Modifier.fillMaxWidth(),
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(MR.strings.action_cancel))
}
},
title = {
Text(stringResource(SYMR.strings.select_tracker))
},
text = {
FlowRow(
modifier = Modifier
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
tracks.forEach { (track, tracker) ->
TrackLogoIcon(
tracker,
onClick = {
onTrackerSelect(tracker, track)
onDismissRequest()
},
)
}
}
},
)
}
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
loadCover(manga, binding)
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
@@ -203,6 +277,55 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
binding.autofillFromTracker.setOnClickListener {
scope.launch {
getTrackers(manga, binding, context, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
}
}
}
private suspend fun getTrackers(manga: Manga, binding: EditMangaDialogBinding, context: Context, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
tracks.value = getTracks.await(manga.id).map { track ->
track to trackerManager.get(track.trackerId)!!
}
.filterNot { (_, tracker) -> tracker is EnhancedTracker }
if (tracks.value.isEmpty()) {
context.toast(context.stringResource(SYMR.strings.entry_not_tracked))
return
}
if (tracks.value.size > 1) {
showTrackerSelectionDialogue.value = true
return
}
autofillFromTracker(binding, tracks.value.first().first, tracks.value.first().second)
}
private fun setTextIfNotBlank(field: (String) -> Unit, value: String?) {
value?.takeIf { it.isNotBlank() }?.let { field(it) }
}
private suspend fun autofillFromTracker(binding: EditMangaDialogBinding, track: Track, tracker: Tracker) {
try {
val trackerMangaMetadata = tracker.getMangaMetadata(track)
setTextIfNotBlank(binding.title::setText, trackerMangaMetadata?.title)
setTextIfNotBlank(binding.mangaAuthor::setText, trackerMangaMetadata?.authors)
setTextIfNotBlank(binding.mangaArtist::setText, trackerMangaMetadata?.artists)
setTextIfNotBlank(binding.thumbnailUrl::setText, trackerMangaMetadata?.thumbnailUrl)
setTextIfNotBlank(binding.mangaDescription::setText, trackerMangaMetadata?.description)
} catch (e: Throwable) {
tracker.logcat(LogPriority.ERROR, e)
binding.root.context.toast(
binding.root.context.stringResource(
MR.strings.track_error,
tracker.name,
e.message ?: "",
),
)
}
}
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
@@ -33,17 +33,13 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
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.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* A wrapper view for showing page image.
@@ -61,8 +57,6 @@ open class ReaderPageImageView @JvmOverloads constructor(
private val isWebtoon: Boolean = false,
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
private val alwaysUseSSIVToDecode by lazy { Injekt.get<BasePreferences>().alwaysUseSSIVToDecode().get() }
private var pageView: View? = null
private var config: Config? = null
@@ -122,21 +116,22 @@ open class ReaderPageImageView @JvmOverloads constructor(
}
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
val config = config
if (config != null &&
config!!.landscapeZoom &&
config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
config.landscapeZoom &&
config.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
sWidth > sHeight &&
scale == minScale
) {
handler?.postDelayed(500) {
val point = when (config!!.zoomStartPosition) {
val point = when (config.zoomStartPosition) {
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
ZoomStartPosition.CENTER -> center
}
val targetScale = height.toFloat() / sHeight.toFloat()
animateScaleAndCenter(targetScale, point)!!
(animateScaleAndCenter(targetScale, point) ?: return@postDelayed)
.withDuration(500)
.withEasing(EASE_IN_OUT_QUAD)
.withInterruptible(true)
@@ -238,7 +233,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} else {
SubsamplingScaleImageView(context)
}.apply {
setMaxTileSize(GLUtil.maxTextureSize)
setMaxTileSize(ImageUtil.hardwareBitmapThreshold)
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
setMinimumTileDpi(180)
@@ -299,32 +294,34 @@ open class ReaderPageImageView @JvmOverloads constructor(
isVisible = true
}
is BufferedSource -> {
if (alwaysUseSSIVToDecode || !isWebtoon || !ImageUtil.canUseCoilToDecode(data)) {
if (!isWebtoon) {
setHardwareConfig(ImageUtil.canUseHardwareBitmap(data))
setImage(ImageSource.inputStream(data.inputStream()))
isVisible = true
} else {
val request = ImageRequest.Builder(context)
.data(data)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val image = result as BitmapImage
setImage(ImageSource.bitmap(image.bitmap))
isVisible = true
},
onError = {
this@ReaderPageImageView.onImageLoadError()
},
)
.size(ViewSizeResolver(this@ReaderPageImageView))
.precision(Precision.INEXACT)
.cropBorders(config.cropBorders)
.customDecoder(true)
.crossfade(false)
.build()
context.imageLoader.enqueue(request)
return@apply
}
ImageRequest.Builder(context)
.data(data)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val image = result as BitmapImage
setImage(ImageSource.bitmap(image.bitmap))
isVisible = true
},
onError = {
onImageLoadError()
},
)
.size(ViewSizeResolver(this@ReaderPageImageView))
.precision(Precision.INEXACT)
.cropBorders(config.cropBorders)
.customDecoder(true)
.crossfade(false)
.build()
.let(context.imageLoader::enqueue)
}
else -> {
throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
@@ -353,8 +353,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
else -> oldCurrent?.first ?: return
}
val index = when (newPage) {
is ChapterTransition -> {
val index = when {
newPage is ChapterTransition && joinedItems.none { it.first == newPage || it.second == newPage } -> {
val filteredPages = joinedItems.filter {
it.first is ReaderPage &&
(it.first as ReaderPage).chapter == newPage.to
@@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.ui.stats
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastMapNotNull
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.fastCountNot
import eu.kanade.core.util.fastDistinctBy
import eu.kanade.core.util.fastFilter
import eu.kanade.core.util.fastFilterNot
import eu.kanade.core.util.fastMapNotNull
import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.tachiyomi.data.download.DownloadManager
@@ -15,6 +15,7 @@ import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
@@ -107,9 +108,13 @@ fun Context.createFileInCacheDir(name: String): File {
fun Context.createReaderThemeContext(): Context {
val preferences = Injekt.get<UiPreferences>()
val readerPreferences = Injekt.get<ReaderPreferences>()
val themeMode = preferences.themeMode().get()
val isDarkBackground = when (readerPreferences.readerTheme().get()) {
1, 2 -> true // Black, Gray
3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default
3 -> when (themeMode) { // Automatic bg uses activity background by default
ThemeMode.SYSTEM -> applicationContext.isNightMode()
else -> themeMode == ThemeMode.DARK
}
else -> false // White
}
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
@@ -119,4 +119,10 @@ data class DummyTracker(
track: eu.kanade.tachiyomi.data.database.models.Track,
epochMillis: Long,
) = Unit
override suspend fun getMangaMetadata(
track: tachiyomi.domain.track.model.Track,
): eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata = eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata(
0, "test", "test", "test", "test", "test",
)
}
@@ -0,0 +1,109 @@
package exh.eh
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.math.RoundingMode
import java.text.NumberFormat
class EHentaiUpdateNotifier(private val context: Context) {
private val securityPreferences: SecurityPreferences by injectLazy()
private val percentFormatter = NumberFormat.getPercentInstance().apply {
roundingMode = RoundingMode.DOWN
maximumFractionDigits = 0
}
/**
* Bitmap of the app for notifications.
*/
private val notificationBitmap by lazy {
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
}
/**
* Cached progress notification to avoid creating a lot.
*/
val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_EHENTAI) {
setContentTitle(context.stringResource(MR.strings.app_name))
setSmallIcon(R.drawable.ic_refresh_24dp)
setLargeIcon(notificationBitmap)
setOngoing(true)
setOnlyAlertOnce(true)
}
}
/**
* Shows the notification containing the currently updating manga and the progress.
*
* @param manga the manga that are being updated.
* @param current the current progress.
* @param total the total progress.
*/
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
progressNotificationBuilder
.setContentTitle(
context.stringResource(
MR.strings.notification_updating_progress,
percentFormatter.format(current.toFloat() / total),
),
)
if (!securityPreferences.hideNotificationContent().get()) {
val updatingText = manga.title.chop(40)
progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
}
context.notify(
Notifications.ID_EHENTAI_PROGRESS,
progressNotificationBuilder
.setProgress(total, current, false)
.build(),
)
}
/**
* Shows notification containing update entries that failed with action to open full log.
*
* @param failed Number of entries that failed to update.
* @param uri Uri for error log file containing all titles that failed.
*/
fun showUpdateErrorNotification(failed: Int, uri: Uri) {
if (failed == 0) {
return
}
context.notify(
Notifications.ID_EHENTAI_ERROR,
Notifications.CHANNEL_LIBRARY_EHENTAI,
) {
setContentTitle(context.stringResource(MR.strings.notification_update_error, failed))
setContentText(context.stringResource(MR.strings.action_show_errors))
setSmallIcon(R.drawable.ic_tachi)
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
}
}
/**
* Cancels the progress notification.
*/
fun cancelProgressNotification() {
context.cancelNotification(Notifications.ID_EHENTAI_PROGRESS)
}
}
@@ -1,11 +1,15 @@
package exh.eh
import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters
import com.elvishew.xlog.Logger
@@ -14,8 +18,10 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager
import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
@@ -27,9 +33,11 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
@@ -46,9 +54,10 @@ import kotlin.time.Duration.Companion.days
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val preferences: UnsortedPreferences by injectLazy()
private val libraryPreferences: LibraryPreferences by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val updateHelper: EHentaiUpdateHelper by injectLazy()
private val logger: Logger = xLog()
private val logger: Logger by lazy { xLog() }
private val updateManga: UpdateManga by injectLazy()
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
private val getChaptersByMangaId: GetChaptersByMangaId by injectLazy()
@@ -56,22 +65,38 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
private val insertFlatMetadata: InsertFlatMetadata by injectLazy()
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
private val updateNotifier by lazy { EHentaiUpdateNotifier(context) }
private val libraryUpdateNotifier by lazy { LibraryUpdateNotifier(context) }
override suspend fun doWork(): Result {
return try {
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
Result.success() // retry again later
} else {
setForegroundSafely()
startUpdating()
logger.d("Update job completed!")
Result.success()
}
} catch (e: Exception) {
Result.success() // retry again later
} finally {
updateNotifier.cancelProgressNotification()
}
}
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(
Notifications.ID_EHENTAI_PROGRESS,
updateNotifier.progressNotificationBuilder.build(),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
} else {
0
},
)
}
private suspend fun startUpdating() {
logger.d("Update job started!")
val startTime = System.currentTimeMillis()
@@ -138,6 +163,11 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
}
val (new, chapters) = try {
updateNotifier.showProgressNotification(
manga,
updatedThisIteration + failuresThisIteration,
mangaMetaToUpdateThisIter.size,
)
updateEntryAndGetChapters(manga)
} catch (e: GalleryNotUpdatedException) {
if (e.network) {
@@ -173,8 +203,10 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
if (new.isNotEmpty() && manga.id == acceptedRoot.manga.id) {
libraryPreferences.newUpdatesCount().getAndSet { it + new.size }
updatedManga += acceptedRoot.manga to new.toTypedArray()
} else if (exhNew.isNotEmpty() && updatedManga.none { it.first.id == acceptedRoot.manga.id }) {
libraryPreferences.newUpdatesCount().getAndSet { it + exhNew.size }
updatedManga += acceptedRoot.manga to exhNew.toTypedArray()
}
@@ -193,8 +225,9 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
),
)
updateNotifier.cancelProgressNotification()
if (updatedManga.isNotEmpty()) {
updateNotifier.showUpdateNotifications(updatedManga)
libraryUpdateNotifier.showUpdateNotifications(updatedManga)
}
}
}
@@ -237,7 +270,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
fun launchBackgroundTest(context: Context) {
context.workManager.enqueue(OneTimeWorkRequestBuilder<EHentaiUpdateWorker>().build())
context.workManager.enqueue(
OneTimeWorkRequestBuilder<EHentaiUpdateWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.addTag(TAG)
.build(),
)
}
fun scheduleBackground(context: Context, prefInterval: Int? = null, prefRestrictions: Set<String>? = null) {
@@ -92,7 +92,7 @@ class MemAutoFlushingLookupTable<T>(
val size = bb.getInt(4)
val strBArr = ByteArray(size)
if (!input.requireBytes(strBArr, size)) break
table.put(k, serializer.read(strBArr.toString(Charsets.UTF_8)))
table.put(k, serializer.read(strBArr.decodeToString()))
}
}
} catch (e: FileNotFoundException) {
@@ -131,7 +131,7 @@ class MemAutoFlushingLookupTable<T>(
try {
val out = fos.sink().buffer()
table.forEach { key, value ->
val v = serializer.write(value).toByteArray(Charsets.UTF_8)
val v = serializer.write(value).encodeToByteArray()
bb.putInt(0, key)
bb.putInt(4, v.size)
out.write(bb.array())
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+133
View File
@@ -7,22 +7,31 @@ object Cosplayer : TagList {
"cosplayer:abby dark star",
"cosplayer:adamae-dono",
"cosplayer:ai lei jiang",
"cosplayer:ai xi",
"cosplayer:aiga mizuki",
"cosplayer:aimy",
"cosplayer:aizawa ren",
"cosplayer:ajo cosplay",
"cosplayer:akane araragi",
"cosplayer:akari yamazaki",
"cosplayer:akemi101xoxo",
"cosplayer:akiba cute star",
"cosplayer:akitsu honoka",
"cosplayer:aleksandra bodler",
"cosplayer:aleksandra lerman",
"cosplayer:aleksis hitc",
"cosplayer:alexis lust",
"cosplayer:alice bong",
"cosplayer:alice cosplay",
"cosplayer:alice wonder",
"cosplayer:aliceholic",
"cosplayer:alicekyo",
"cosplayer:alin ma",
"cosplayer:alisa arkhangelskaya",
"cosplayer:alisa kiss",
"cosplayer:alisa valeeva",
"cosplayer:alodia gosiengfiao",
"cosplayer:alva velasco",
"cosplayer:alycia elvie",
"cosplayer:amanda welp",
"cosplayer:amber hallibell",
@@ -31,16 +40,20 @@ object Cosplayer : TagList {
"cosplayer:anabelle joy",
"cosplayer:anastasia komori",
"cosplayer:angelina preobrazhenskaya",
"cosplayer:aniela verbin",
"cosplayer:aninnyan",
"cosplayer:anizu chie",
"cosplayer:anna kuramoto",
"cosplayer:annie seixas",
"cosplayer:anxi",
"cosplayer:aokotan",
"cosplayer:arai yomi",
"cosplayer:araki mai",
"cosplayer:ari.anna",
"cosplayer:arisa mizuhara",
"cosplayer:arty huang",
"cosplayer:asakawa ran",
"cosplayer:asakura kotomi",
"cosplayer:asakura naho",
"cosplayer:ashiya noriko",
"cosplayer:astasiadream",
@@ -48,28 +61,39 @@ object Cosplayer : TagList {
"cosplayer:atina",
"cosplayer:atsuki",
"cosplayer:audalove",
"cosplayer:aveline tetsuya",
"cosplayer:ayaka matsunaga",
"cosplayer:bailey jay",
"cosplayer:banbanko",
"cosplayer:beibei kappu",
"cosplayer:bella bunbun",
"cosplayer:bili bili",
"cosplayer:bishoujomom",
"cosplayer:blacqkl",
"cosplayer:bloodraven",
"cosplayer:bobbi starr",
"cosplayer:bonn1ethebunny",
"cosplayer:bonnie bonkers",
"cosplayer:boople snoot",
"cosplayer:breesknees",
"cosplayer:bukkitbrown",
"cosplayer:bunny ayumi",
"cosplayer:carry key",
"cosplayer:chadkasa",
"cosplayer:charess",
"cosplayer:charles dera",
"cosplayer:chisai cosplay",
"cosplayer:chokoboll mukakoi.",
"cosplayer:christina volkova",
"cosplayer:chunmomo",
"cosplayer:cocopie",
"cosplayer:comonun",
"cosplayer:cristina luise",
"cosplayer:dakko ja rrs",
"cosplayer:dani doe",
"cosplayer:danielle beaulieu",
"cosplayer:danielle vedovelli",
"cosplayer:daria kravets",
"cosplayer:darling cute",
"cosplayer:dessyy",
"cosplayer:dillion harper",
@@ -78,39 +102,56 @@ object Cosplayer : TagList {
"cosplayer:donnaloli",
"cosplayer:eira wang",
"cosplayer:electricbum",
"cosplayer:elena lenina",
"cosplayer:elena yuna",
"cosplayer:elisa cattabriga",
"cosplayer:elyhria",
"cosplayer:erin eevee",
"cosplayer:erotic doki",
"cosplayer:eroticneko",
"cosplayer:esther rutkovskaya-tudor",
"cosplayer:eunji pyo",
"cosplayer:evawxsh",
"cosplayer:evenink",
"cosplayer:evie evangelion",
"cosplayer:ezy summers",
"cosplayer:fatiaoliii",
"cosplayer:fay sg",
"cosplayer:fe galvao",
"cosplayer:felvelial",
"cosplayer:feng jiang jiang",
"cosplayer:ferin feirn",
"cosplayer:feywilde",
"cosplayer:firtsbornunicorn",
"cosplayer:flora daria",
"cosplayer:fluffy nemu",
"cosplayer:franxcos",
"cosplayer:frauleinmilk",
"cosplayer:fubuki ami",
"cosplayer:fuji serika",
"cosplayer:futaba emiru",
"cosplayer:g44 wa kizutsukanai",
"cosplayer:garo dazay",
"cosplayer:generic egirl",
"cosplayer:genthehobbit",
"cosplayer:ghostly cosplay",
"cosplayer:giorgia vecchini",
"cosplayer:giulia valeriani",
"cosplayer:goth egg",
"cosplayer:gumiho hannya",
"cosplayer:guo chengzi",
"cosplayer:hakuhi kaede",
"cosplayer:hamasaki rio",
"cosplayer:han yeri",
"cosplayer:hanamura misaki",
"cosplayer:hane ame",
"cosplayer:harukaism",
"cosplayer:helly von valentine",
"cosplayer:hessakai",
"cosplayer:hey shika",
"cosplayer:higurashi rin",
"cosplayer:himeecosplay",
"cosplayer:hinatasama",
"cosplayer:hinaughtya",
"cosplayer:hiyo nishizuku",
"cosplayer:holly ava",
@@ -121,18 +162,30 @@ object Cosplayer : TagList {
"cosplayer:imokawa naoko",
"cosplayer:ino cosplay",
"cosplayer:iori moe",
"cosplayer:iri",
"cosplayer:ishikawa asami",
"cosplayer:jannet kat",
"cosplayer:jannet vinogradova",
"cosplayer:jasming chea",
"cosplayer:jaycee",
"cosplayer:jenezial",
"cosplayer:jenna lynn meowri",
"cosplayer:jenni kaellberg",
"cosplayer:jessica nigri",
"cosplayer:jessika jinx",
"cosplayer:jill",
"cosplayer:jinxie",
"cosplayer:jiuqujean",
"cosplayer:jiuyan",
"cosplayer:jiyun choi",
"cosplayer:joanna muller",
"cosplayer:julia larangeiras",
"cosplayer:julia shuenkova",
"cosplayer:jun ye tako",
"cosplayer:kae kaieda",
"cosplayer:kalinka fox",
"cosplayer:kamelya chan",
"cosplayer:kamijiri ichigo",
"cosplayer:kamui alice",
"cosplayer:kanda likitsangjaroen",
"cosplayer:kanda midori",
@@ -141,6 +194,7 @@ object Cosplayer : TagList {
"cosplayer:katiecakey",
"cosplayer:kay bear",
"cosplayer:kaya huang",
"cosplayer:kei shino",
"cosplayer:khainsaw",
"cosplayer:kibashi",
"cosplayer:kiera marie",
@@ -159,19 +213,27 @@ object Cosplayer : TagList {
"cosplayer:kotea dali",
"cosplayer:koyama rikako",
"cosplayer:kqueentsun",
"cosplayer:kurasaka kururu",
"cosplayer:kurumi.",
"cosplayer:kururugi aoi",
"cosplayer:kuuko w",
"cosplayer:kyonatix",
"cosplayer:lagertha",
"cosplayer:lea martinez",
"cosplayer:lelewu",
"cosplayer:lenfried",
"cosplayer:lewdoart",
"cosplayer:lex kuma",
"cosplayer:lightz",
"cosplayer:lili erlih",
"cosplayer:lilly rose",
"cosplayer:lilya victorovna",
"cosplayer:lilylit",
"cosplayer:lina erdel",
"cosplayer:ling li",
"cosplayer:linneas life",
"cosplayer:linzi jiang",
"cosplayer:little blue girl",
"cosplayer:lizyhsan",
"cosplayer:lmusicl",
"cosplayer:lovelyspacekitten",
@@ -180,11 +242,16 @@ object Cosplayer : TagList {
"cosplayer:mags.irl",
"cosplayer:mais conheyo",
"cosplayer:manyu hanausagi",
"cosplayer:mappy sanchez",
"cosplayer:marie-claude bourbonnais",
"cosplayer:mariigabii",
"cosplayer:masako yume",
"cosplayer:meagan vanburkleo",
"cosplayer:mei succubus",
"cosplayer:meikoui",
"cosplayer:meriol-chan",
"cosplayer:mianbing xianer",
"cosplayer:micro kitty",
"cosplayer:miih cosplay",
"cosplayer:miiya",
"cosplayer:mik allen",
@@ -192,18 +259,27 @@ object Cosplayer : TagList {
"cosplayer:milena hime",
"cosplayer:milky",
"cosplayer:mimi-chan",
"cosplayer:mimmi",
"cosplayer:mimo ningyo",
"cosplayer:minaduki miri",
"cosplayer:minato riku",
"cosplayer:minematsu rie",
"cosplayer:mingchudesu",
"cosplayer:mingming kizami",
"cosplayer:mingtao",
"cosplayer:minzy tea",
"cosplayer:miorin",
"cosplayer:misaco",
"cosplayer:mishka bear",
"cosplayer:missbrisolo",
"cosplayer:misty silver",
"cosplayer:mitsuki riyu",
"cosplayer:miura aika",
"cosplayer:miyoki",
"cosplayer:mizhimaoqiu",
"cosplayer:mizuki akira",
"cosplayer:mochichuu",
"cosplayer:mochimochi-nn",
"cosplayer:mochizuki eiko",
"cosplayer:mochizuki kanade",
"cosplayer:moiicos",
@@ -211,8 +287,11 @@ object Cosplayer : TagList {
"cosplayer:momoiro reku",
"cosplayer:momoko",
"cosplayer:momokun",
"cosplayer:momousagi mao",
"cosplayer:moody feet",
"cosplayer:morgana cosplay",
"cosplayer:mowky",
"cosplayer:moyu mommy",
"cosplayer:mozuku kimura",
"cosplayer:mu zhi ben lan",
"cosplayer:murasaki",
@@ -222,7 +301,11 @@ object Cosplayer : TagList {
"cosplayer:nadyasonika",
"cosplayer:nagisa",
"cosplayer:nan tao momoko",
"cosplayer:natalya ditrikh",
"cosplayer:natasha roik",
"cosplayer:nateephan thammasilbanyad",
"cosplayer:nawo019",
"cosplayer:nayfi bardales",
"cosplayer:nekob0icarti",
"cosplayer:neroko kaigan",
"cosplayer:niannian d",
@@ -235,58 +318,81 @@ object Cosplayer : TagList {
"cosplayer:nonbinate",
"cosplayer:nora fawn",
"cosplayer:nuko meguro",
"cosplayer:nuria gonzalez",
"cosplayer:nyako",
"cosplayer:nymph-princess",
"cosplayer:octokuro",
"cosplayer:odoru neko ningen",
"cosplayer:oharucosplay",
"cosplayer:oichi",
"cosplayer:okada yui",
"cosplayer:oki-cospi",
"cosplayer:olivia metric",
"cosplayer:olyashaa saxon",
"cosplayer:pattie cosplay",
"cosplayer:pattycake",
"cosplayer:peachtot",
"cosplayer:penkarui",
"cosplayer:penny walsh",
"cosplayer:pichapu",
"cosplayer:pokket",
"cosplayer:poon warunya",
"cosplayer:punk macarroni",
"cosplayer:purrblind",
"cosplayer:pushiku",
"cosplayer:qiqi nanazi",
"cosplayer:qiqi xiaojie",
"cosplayer:qiuhe keji",
"cosplayer:queenie",
"cosplayer:quist",
"cosplayer:rachel ravaged",
"cosplayer:rakuraku",
"cosplayer:ravvcoser",
"cosplayer:raynearts",
"cosplayer:rea kami",
"cosplayer:renee storm",
"cosplayer:rhylee passfield",
"cosplayer:ri care",
"cosplayer:riani haratina",
"cosplayer:rinami",
"cosplayer:ringo mitsuki",
"cosplayer:rinoa",
"cosplayer:rio-chan",
"cosplayer:rioko",
"cosplayer:rissoft",
"cosplayer:rocksy light",
"cosplayer:rolyatistaylor",
"cosplayer:rongrongzi",
"cosplayer:rusuwu",
"cosplayer:sachi budou",
"cosplayer:saiwari ph",
"cosplayer:saki kawanami",
"cosplayer:saku",
"cosplayer:sakura ema",
"cosplayer:sakurai",
"cosplayer:sakurai hinoki",
"cosplayer:samantha boon",
"cosplayer:sandykuroneko",
"cosplayer:saotome love",
"cosplayer:sara underwood",
"cosplayer:sarah quillian",
"cosplayer:sarawrcosplay",
"cosplayer:sasaki remi",
"cosplayer:sato yuri",
"cosplayer:savannah sixx",
"cosplayer:sawaka",
"cosplayer:scarlett afterdark",
"cosplayer:sean lawless",
"cosplayer:sei",
"cosplayer:seltin sweet",
"cosplayer:sena",
"cosplayer:sexy toys",
"cosplayer:sexyflowerwater",
"cosplayer:sharkparty",
"cosplayer:shermie",
"cosplayer:shibuya kaho",
"cosplayer:shimizu yuno",
"cosplayer:shinen",
"cosplayer:shiro kitsune",
"cosplayer:shiroluxx",
"cosplayer:siao ding",
@@ -295,14 +401,24 @@ object Cosplayer : TagList {
"cosplayer:soa lianna",
"cosplayer:son yeeun",
"cosplayer:sophie snomster",
"cosplayer:stanislava anushkina",
"cosplayer:starfmodel",
"cosplayer:stelar hoshi",
"cosplayer:sunny lin",
"cosplayer:sunny ray",
"cosplayer:sunnyvier",
"cosplayer:sunohara miki",
"cosplayer:sushiflavoredmilk",
"cosplayer:suspira grey",
"cosplayer:tachibana remika",
"cosplayer:tanaka hitomi",
"cosplayer:tanaka mana",
"cosplayer:tangtang",
"cosplayer:tanja kensinger",
"cosplayer:tao liang azhai",
"cosplayer:tara nicole azarian",
"cosplayer:tasha leigh",
"cosplayer:tatiana neva",
"cosplayer:tenleid",
"cosplayer:tenryu-0",
"cosplayer:tenshi myu.",
@@ -310,8 +426,10 @@ object Cosplayer : TagList {
"cosplayer:tiffany gordon",
"cosplayer:tiny asa",
"cosplayer:todopokie",
"cosplayer:tristan valdez",
"cosplayer:tsubaki zakuro",
"cosplayer:tsuki desu",
"cosplayer:tsuki miko",
"cosplayer:tsukimiya madoka",
"cosplayer:tsuyato",
"cosplayer:turkish chi-chi",
@@ -320,34 +438,49 @@ object Cosplayer : TagList {
"cosplayer:ukyuu nako",
"cosplayer:una cosplayer",
"cosplayer:uno megumi",
"cosplayer:ur senpai june",
"cosplayer:uru uruu",
"cosplayer:uta kohaku",
"cosplayer:valery himera",
"cosplayer:velvet",
"cosplayer:veroodle",
"cosplayer:vlada lutsak",
"cosplayer:wanco chan",
"cosplayer:whimpercat",
"cosplayer:wifey",
"cosplayer:wildhoney423",
"cosplayer:xansoon",
"cosplayer:xia xia zi",
"cosplayer:xiaoyao yaoyao",
"cosplayer:xiaoying shi zhi xiaomulong",
"cosplayer:xidaidai",
"cosplayer:xue qi-sama",
"cosplayer:yaki",
"cosplayer:yaokoututu",
"cosplayer:yaoyaoqwq",
"cosplayer:ying lili",
"cosplayer:yoko inui",
"cosplayer:yor succubus",
"cosplayer:yorkie w",
"cosplayer:youyou",
"cosplayer:yuki astra",
"cosplayer:yuki lefay",
"cosplayer:yuki teyi",
"cosplayer:yume",
"cosplayer:yummykimmy",
"cosplayer:yunie lannister",
"cosplayer:yunocos69",
"cosplayer:yurihime",
"cosplayer:yuumeilyn",
"cosplayer:yuyunte",
"cosplayer:yuzu chan",
"cosplayer:yuzuki",
"cosplayer:yuzukimiiu",
"cosplayer:yuzupyon",
"cosplayer:zara durose",
"cosplayer:zeico",
"cosplayer:zhenya zhuk",
"cosplayer:zhuimingyou",
"cosplayer:zyunka mukhina",
)
}
+8
View File
@@ -63,7 +63,9 @@ object Female : TagList {
"female:big vagina",
"female:bike shorts",
"female:bikini",
"female:bird girl",
"female:bisexual",
"female:bite mark",
"female:blackmail",
"female:blind",
"female:blindfold",
@@ -120,8 +122,10 @@ object Female : TagList {
"female:cockslapping",
"female:collar",
"female:condom",
"female:confinement",
"female:conjoined",
"female:coprophagia",
"female:corpse",
"female:corruption",
"female:corset",
"female:cosplaying",
@@ -218,6 +222,7 @@ object Female : TagList {
"female:frog",
"female:frog girl",
"female:frottage",
"female:full tour",
"female:full-packaged futanari",
"female:fundoshi",
"female:furry",
@@ -299,6 +304,7 @@ object Female : TagList {
"female:kindergarten uniform",
"female:kissing",
"female:kneepit sex",
"female:kodomo doushi",
"female:kunoichi",
"female:lab coat",
"female:lactation",
@@ -319,6 +325,7 @@ object Female : TagList {
"female:long tongue",
"female:low bestiality",
"female:low guro",
"female:low incest",
"female:low lolicon",
"female:low scat",
"female:low smegma",
@@ -369,6 +376,7 @@ object Female : TagList {
"female:muscle growth",
"female:mute",
"female:nakadashi",
"female:navel birth",
"female:navel fuck",
"female:nazi",
"female:necrophilia",
+119 -119
View File
@@ -15,6 +15,7 @@ object Group : TagList {
"group:008",
"group:0123456789",
"group:0909",
"group:1 equals 1ziz",
"group:1 slash 0 kansokujo",
"group:1-up",
"group:10 slash 19",
@@ -336,7 +337,6 @@ object Group : TagList {
"group:alice-do",
"group:alice.blood",
"group:alicegarden",
"group:aliceholic",
"group:alicemirror",
"group:alices house",
"group:alicesoft",
@@ -359,6 +359,7 @@ object Group : TagList {
"group:alz-hammer",
"group:am colon tiger",
"group:am400",
"group:am644",
"group:amaama-tei",
"group:amaembo",
"group:amagasa cycle",
@@ -380,6 +381,7 @@ object Group : TagList {
"group:amanogawa tsuushin",
"group:amapoteya",
"group:amarini senpaku",
"group:amaterasu tsukikage",
"group:amatoro bow",
"group:amatosui",
"group:amatouenpitsukezuri",
@@ -539,6 +541,7 @@ object Group : TagList {
"group:arkham products team ankoku baitai",
"group:armadillo",
"group:armanium",
"group:arnest room",
"group:aroimark",
"group:aroma gaeru",
"group:arsenothelus",
@@ -557,6 +560,7 @@ object Group : TagList {
"group:arukomu",
"group:aruku denpatou no kai",
"group:arumike",
"group:arumirua",
"group:aruto-ya",
"group:arysuivery",
"group:asa made go-ya",
@@ -612,6 +616,7 @@ object Group : TagList {
"group:at mztm",
"group:at no 464",
"group:at oz",
"group:at szkn",
"group:atara shindou",
"group:ataraxia",
"group:atelier d",
@@ -709,6 +714,7 @@ object Group : TagList {
"group:bakuhatsu brs.",
"group:bakunyu fullnerson",
"group:bakuretsu fusen",
"group:bakushu koujou",
"group:bakusou special",
"group:balgus rec",
"group:balklash.",
@@ -746,6 +752,7 @@ object Group : TagList {
"group:bbb-extra",
"group:bbuttondash",
"group:beaf emotion",
"group:bear valley",
"group:beart",
"group:beat-pop",
"group:beauty salon b and s",
@@ -764,6 +771,7 @@ object Group : TagList {
"group:beni namazu dan",
"group:benichigaya",
"group:beniiro kaitenkikou",
"group:benimaru suisan",
"group:benimomo dou",
"group:benisuzumedo",
"group:beniya",
@@ -804,6 +812,7 @@ object Group : TagList {
"group:biroon jr.",
"group:biruban",
"group:bisaid label",
"group:bishamon.",
"group:bishou neko",
"group:bishoujo production",
"group:bisketty",
@@ -1083,6 +1092,7 @@ object Group : TagList {
"group:chimamire yashiki",
"group:chimatsuriya honpo",
"group:chimchimteam",
"group:chimeishou",
"group:chimere marie",
"group:chin soft",
"group:chinasanchi",
@@ -1243,6 +1253,7 @@ object Group : TagList {
"group:coonelius",
"group:copo deluxe",
"group:coppo-otome",
"group:coscoteikoku",
"group:cosmic-3d-angels",
"group:cosplay kissa nyan nyan",
"group:cosplaydeviants",
@@ -1301,6 +1312,7 @@ object Group : TagList {
"group:d-ten",
"group:d.d.d.b.",
"group:d.n.a.lab.",
"group:d.o.",
"group:d2",
"group:da hootch",
"group:da pomb no tokoro",
@@ -1413,6 +1425,7 @@ object Group : TagList {
"group:deucesworld",
"group:dewdrop",
"group:dex plus",
"group:dez climax",
"group:dhr-ken",
"group:diablo",
"group:dicpic studio",
@@ -1424,6 +1437,7 @@ object Group : TagList {
"group:digital graffiti",
"group:digital lover",
"group:digital tambourine",
"group:dingiruutoushi",
"group:diogenes club",
"group:dioxin",
"group:dirty",
@@ -1445,12 +1459,14 @@ object Group : TagList {
"group:doing crew",
"group:doisakaken",
"group:dojin otome",
"group:dojiro books",
"group:dokkoi-tori gomoku",
"group:doku alice",
"group:doku doku kinoko",
"group:doku pepper",
"group:doku usagi tai",
"group:dokudenpa jushintei",
"group:dokudoku ryouki garou",
"group:dokugiri",
"group:dokukinokosha",
"group:dokupan koubou",
@@ -1461,11 +1477,13 @@ object Group : TagList {
"group:donaora",
"group:donburi beya",
"group:dondondon",
"group:dongurineko",
"group:dont understand",
"group:doomcomic",
"group:dopyunger oukoku",
"group:dorepooru",
"group:doro-coppelia",
"group:dorokuma kumaya",
"group:doronuma bunshitsu",
"group:doronuma kyoudai",
"group:doropanda tours",
@@ -1489,8 +1507,10 @@ object Group : TagList {
"group:doujin mukashibanashi",
"group:douke romance",
"group:doumo sumimasen",
"group:dounimo naranai nou",
"group:dourakuya honpo",
"group:doushin chaya",
"group:doushoku",
"group:douwa-kensetsu",
"group:doyondo.",
"group:dr.vermilion",
@@ -1533,6 +1553,7 @@ object Group : TagList {
"group:earthlyparadise",
"group:easymode",
"group:ebimayo",
"group:ebiten kaido",
"group:ecchi na taikendan kokuhaku toukou otoko jyuku",
"group:ecchuu douga honpo",
"group:eclair ringo tea",
@@ -1622,6 +1643,7 @@ object Group : TagList {
"group:etopi kan",
"group:eucalyptus house",
"group:euereuphorie",
"group:euglena factory",
"group:eunospress",
"group:everyday milk challenge",
"group:evil aratame baroque store",
@@ -1647,6 +1669,7 @@ object Group : TagList {
"group:fakereal",
"group:fakers manual",
"group:fakestar",
"group:falcon115",
"group:fallinmoon",
"group:famous comics",
"group:famous toons facial",
@@ -1730,6 +1753,7 @@ object Group : TagList {
"group:frill frill",
"group:frontwing",
"group:fruitsjam",
"group:fu rairyuu",
"group:fuantei",
"group:fudeoki seisakujo",
"group:fuegerstef",
@@ -1778,6 +1802,7 @@ object Group : TagList {
"group:fururi.",
"group:furuya",
"group:fuseimyaku",
"group:fusha fusha kingdom",
"group:fushichou no yoake",
"group:fushinsya guilty",
"group:fushizen doubutsu hogodantai",
@@ -1829,10 +1854,12 @@ object Group : TagList {
"group:gamera 8th army",
"group:gamma menia",
"group:gammaedge",
"group:ganbaru dou",
"group:gang bang comix",
"group:ganmo-no-oyatsu",
"group:ganryuu island",
"group:ganso sonodaya",
"group:gaoookyouryu",
"group:gara ayuri nisshi",
"group:garage-talk",
"group:garahadoh",
@@ -1849,6 +1876,7 @@ object Group : TagList {
"group:gas ketsu jinsei",
"group:gasshuukoku netamekoru",
"group:gate of xiii",
"group:gatekeeper",
"group:gaton.",
"group:gavial no sumika",
"group:gd-mechano",
@@ -1870,12 +1898,14 @@ object Group : TagList {
"group:general bacchus",
"group:genesys",
"group:genki no mizu no wakutokoro",
"group:genkin-dou souhonpo",
"group:genmaiya",
"group:genocidekiss",
"group:gensancha",
"group:gensou graphics",
"group:gensou kuukan",
"group:gensou stlavus",
"group:gensou yakai",
"group:gensyokuhakoniwa",
"group:genwakukinema",
"group:geregere negro",
@@ -1897,12 +1927,14 @@ object Group : TagList {
"group:giantessfan",
"group:giftbell",
"group:giftkuchen",
"group:giga omaru",
"group:gigameka",
"group:gikogakodo",
"group:gin eiji",
"group:gin no hoshitei",
"group:gin penguin",
"group:gin-ion",
"group:ginga no arakuremon",
"group:ginga no himitu kichi",
"group:ginga-ryusei",
"group:giniro noel",
@@ -1960,6 +1992,7 @@ object Group : TagList {
"group:gouhouwakan",
"group:gouriki hyakkaten",
"group:gouten doujou",
"group:gozen 4-ji one call",
"group:gozen niji no ushigaeru",
"group:gpen",
"group:gpx",
@@ -1969,6 +2002,9 @@ object Group : TagList {
"group:gravidan",
"group:great acta",
"group:great canyon",
)
override fun getTags2(): List<String> = listOf(
"group:great dadan",
"group:greatest18club",
"group:greatmanjuu",
@@ -2002,9 +2038,6 @@ object Group : TagList {
"group:gyara cter",
"group:gyaran rose",
"group:gyarandou",
)
override fun getTags2(): List<String> = listOf(
"group:gyogyou rengou",
"group:gyokasuisin",
"group:gyokotsu kouzou",
@@ -2018,6 +2051,7 @@ object Group : TagList {
"group:h na hon. ya san.",
"group:h plus",
"group:h senshokutai",
"group:h sparkle",
"group:h-m",
"group:h-na-ojisan",
"group:h-sys.",
@@ -2208,6 +2242,7 @@ object Group : TagList {
"group:hellfragrance",
"group:hellter skelter",
"group:helmet ga naosemasen",
"group:henachoko-domei",
"group:hengen monogatari",
"group:henntai-shinshi",
"group:henreikai",
@@ -2223,6 +2258,7 @@ object Group : TagList {
"group:hero hero tei",
"group:hero oukoku",
"group:heroes factory",
"group:herunian zokusei",
"group:heshi factory",
"group:heta no yoko zuki",
"group:hetalearts",
@@ -2250,6 +2286,7 @@ object Group : TagList {
"group:high heel syndrome",
"group:high risk revolution",
"group:high-octane",
"group:high-rised fossil garden",
"group:high-soft",
"group:high-spirit",
"group:highway-senmu",
@@ -2315,7 +2352,7 @@ object Group : TagList {
"group:hitsuji-1ban-shibori",
"group:hitsujin toko",
"group:hitujinoki",
"group:hiyashi chuuka hajimemashita",
"group:hiyashi chuuka owarimashita",
"group:hiyoko no gekijoh",
"group:hiyosanchi",
"group:hizadati zekkouchou",
@@ -2331,6 +2368,7 @@ object Group : TagList {
"group:hokoushayou shingou",
"group:hokuroza",
"group:holiday school",
"group:holy up",
"group:home not found",
"group:homepie koubou",
"group:homerun chaya",
@@ -2348,6 +2386,7 @@ object Group : TagList {
"group:honeypie",
"group:hong kong dou",
"group:honnokimochiya",
"group:honpo kes",
"group:hontoinu",
"group:hook",
"group:hooliganism",
@@ -2379,6 +2418,7 @@ object Group : TagList {
"group:hotori bocchi",
"group:hotpink",
"group:hougakuya",
"group:houjou-kun mania",
"group:houkago inokorigumi",
"group:houkago paradise",
"group:houkaiseki.",
@@ -2424,6 +2464,7 @@ object Group : TagList {
"group:ice to choco",
"group:ice-place",
"group:ichachi",
"group:ichi dollar kouka",
"group:ichi-kan",
"group:ichigiteishi",
"group:ichigo maririn",
@@ -2507,6 +2548,7 @@ object Group : TagList {
"group:intermikan",
"group:interracial-comics",
"group:intoku.info",
"group:inuchan equals land",
"group:inudrill.",
"group:inukamedou",
"group:inukichi club",
@@ -2587,12 +2629,15 @@ object Group : TagList {
"group:jet-black baselarde",
"group:jewel box",
"group:jhk",
"group:jibaku mecha",
"group:jibaku-system",
"group:jidaraku risutorante",
"group:jido-hikki",
"group:jigen no wataridori",
"group:jiggly girls",
"group:jigizagi",
"group:jigoku no cakeya-san",
"group:jigoku no monban",
"group:jigoku potion",
"group:jigyaku jihen",
"group:jikan-ya",
@@ -2697,6 +2742,7 @@ object Group : TagList {
"group:kairoudou",
"group:kairyuu",
"group:kaisanbou",
"group:kaitaiya",
"group:kaitatuku",
"group:kaiteisinden",
"group:kaiten sommelier",
@@ -2824,6 +2870,7 @@ object Group : TagList {
"group:keiyou tsudanuma juku",
"group:kemao coopercent",
"group:kemokomoya",
"group:kemomimi-chan ya",
"group:kemominnosuke",
"group:kemono ekaki no kousoku 2",
"group:kemono masshigura.",
@@ -2849,6 +2896,7 @@ object Group : TagList {
"group:khaos distance",
"group:khaos wind",
"group:kharisma jati",
"group:khpn style",
"group:kibawomuku",
"group:kichiku bansankai",
"group:kichiku koubou",
@@ -2959,6 +3007,7 @@ object Group : TagList {
"group:koge croquette",
"group:kogemaru tsuushinkyoku",
"group:kogitune",
"group:koh no atelier",
"group:kohagura.",
"group:kohau no heya",
"group:kohitsujitei",
@@ -3010,6 +3059,7 @@ object Group : TagList {
"group:komorikiri.",
"group:komugiko 100 percent",
"group:kon no pencase",
"group:konekoconnection",
"group:konekopan",
"group:konekopunch",
"group:kongou rikisi",
@@ -3057,6 +3107,7 @@ object Group : TagList {
"group:kousaien",
"group:kousoku gurihari-tei",
"group:kousoku purin",
"group:koutatsu dennou koushi",
"group:kouzaka-san to makino jimusho",
"group:kouzu shoukai",
"group:kouzuya",
@@ -3087,12 +3138,14 @@ object Group : TagList {
"group:kumo to koumori",
"group:kumohitode of world",
"group:kumonosu",
"group:kuni gamma",
"group:kunkakunka teikoku",
"group:kunon",
"group:kurage kyoudai",
"group:kurahashi shoin",
"group:kurai mori no soko de",
"group:kurakura-honey",
"group:kurasan",
"group:kuraudo.",
"group:kureboti ufo",
"group:kurige wagyuu",
@@ -3162,6 +3215,7 @@ object Group : TagList {
"group:kuukiisu",
"group:kuuronziyou",
"group:kuusou idol labo bellberry",
"group:kuusou kouko gakkai",
"group:kuusou switch",
"group:kuzuryuu",
"group:kyakuniku kanzume",
@@ -3178,6 +3232,7 @@ object Group : TagList {
"group:kyouken diners",
"group:kyouki na shiunten",
"group:kyoumo spaghe",
"group:kyourakuen",
"group:kyoushuugata",
"group:kyuu no mon",
"group:kyuu tekki jidai",
@@ -3246,6 +3301,7 @@ object Group : TagList {
"group:lily heart",
"group:lily lily rose",
"group:lily-put",
"group:lime green",
"group:limit break",
"group:limit max",
"group:limit over",
@@ -3260,6 +3316,7 @@ object Group : TagList {
"group:little mantis",
"group:little princess",
"group:littlehopper",
"group:littlepool.",
"group:littletail",
"group:live house",
"group:lo likyo new",
@@ -3274,6 +3331,7 @@ object Group : TagList {
"group:lolipop complete",
"group:lolitachannel",
"group:longhorntrain",
"group:longlong de cangku",
"group:looptheloop",
"group:lopet dan",
"group:lostscript",
@@ -3284,6 +3342,7 @@ object Group : TagList {
"group:love kitten",
"group:love kyun maiden",
"group:love lily",
"group:love love craft",
"group:love nyanko",
"group:love scythe",
"group:love shine",
@@ -3325,6 +3384,7 @@ object Group : TagList {
"group:m kichibeya",
"group:m plus dilore",
"group:m slash k club",
"group:m-family",
"group:m-i-p",
"group:m-keifu",
"group:m-koujou",
@@ -3364,6 +3424,7 @@ object Group : TagList {
"group:magono-tei",
"group:magudara kaihou doumei",
"group:maguro bokujo",
"group:maguro fiction",
"group:maguro koubou",
"group:mahiru no tsuki",
"group:mahiru nosora",
@@ -3417,9 +3478,11 @@ object Group : TagList {
"group:manjuu x",
"group:manles laboratory",
"group:manshin soui",
"group:mantohihi atoz",
"group:many menu",
"group:maokonzu",
"group:maple-go",
"group:mappa namatta",
"group:mara apocalypse",
"group:marashion",
"group:marble kid",
@@ -3431,6 +3494,7 @@ object Group : TagList {
"group:marge-loop",
"group:maria system00",
"group:mariana kaikou kikaku",
"group:marimo-ya",
"group:marinesapphire",
"group:marionette soukou ryouhei",
"group:marireimari inochi",
@@ -3651,6 +3715,7 @@ object Group : TagList {
"group:misaki shoujokei.",
"group:misakix megamix",
"group:misin koujou",
"group:misobolo dou",
"group:misonodenpatou",
"group:misoyahonpo",
"group:misssail",
@@ -3697,6 +3762,7 @@ object Group : TagList {
"group:mocha plus ccc",
"group:mocha2popcorn",
"group:mochi hasamiuchi da",
"group:mochi mochi bomb",
"group:mochi usagi",
"group:mochi-ya",
"group:mochimoonya",
@@ -3716,8 +3782,10 @@ object Group : TagList {
"group:mogura-dou",
"group:mogyutto cheesecake",
"group:mojibone",
"group:mokkindo",
"group:mokkorihan",
"group:mokkoubondobu",
"group:mokkuafunfun",
"group:mokomaru suisan",
"group:mokugyuutan",
"group:mokusa",
@@ -3738,6 +3806,7 @@ object Group : TagList {
"group:momoiro funenmono",
"group:momoiro mimic",
"group:momoiro tanzaku",
"group:momoiro zundoko",
"group:momoiro-gekijyou",
"group:momoiro-rip",
"group:momokamasu",
@@ -3787,8 +3856,8 @@ object Group : TagList {
"group:morning star",
"group:morning tea.",
"group:morningmoon merchandising products",
"group:morohei-ya",
"group:moroheiya break",
"group:moroheiya no agata",
"group:morokochiffon cake",
"group:morokosheet",
"group:moromi-ya",
@@ -3802,6 +3871,7 @@ object Group : TagList {
"group:mosoya",
"group:mosquitone.",
"group:motchie kingdom",
"group:motemote life",
"group:mothman",
"group:mou sukoshi hidari e",
"group:mouko mouretsu hasai dan",
@@ -3864,6 +3934,7 @@ object Group : TagList {
"group:mukokoro no kumo",
"group:mukousharan",
"group:mukuchi na hakoniwa",
"group:mukyou no utopia",
"group:mukyuu dynamic",
"group:mulberry",
"group:multi media studio l.o.e.r.",
@@ -3934,6 +4005,9 @@ object Group : TagList {
"group:nagomi-chaya",
"group:nagomisui",
"group:nagomiyasan",
)
override fun getTags3(): List<String> = listOf(
"group:nagucha.",
"group:nagumo curry-bu",
"group:nagumoya",
@@ -3957,9 +4031,11 @@ object Group : TagList {
"group:namahage-dou",
"group:namahamu sando",
"group:namaikichibi",
"group:namaitati teishoku",
"group:namakemono sou",
"group:namakura dou",
"group:namanecotei",
"group:namaniku aikoukai",
"group:namasute koubou",
"group:namazuchaya",
"group:nameco-soup",
@@ -4005,9 +4081,6 @@ object Group : TagList {
"group:nanto wachou ken",
"group:nantoka ikitemasu",
"group:nantoka suroun",
)
override fun getTags3(): List<String> = listOf(
"group:nappy",
"group:naraduke biyori",
"group:naragyogyo kumiai",
@@ -4018,6 +4091,7 @@ object Group : TagList {
"group:nasi-pasuya",
"group:nasu no mono",
"group:nasuan",
"group:natadecoco company",
"group:natakuga-yuku",
"group:natorina dou",
"group:natrinium",
@@ -4120,8 +4194,10 @@ object Group : TagList {
"group:nendo ningyo",
"group:nengaranenjuu",
"group:neo maiden",
"group:neoniro",
"group:neruneru",
"group:netemo sametemo",
"group:netorareru tamashigi no hitoshizuku",
"group:netsuzukeru ishiryoku",
"group:nettaigyo club",
"group:neutron city",
@@ -4152,6 +4228,7 @@ object Group : TagList {
"group:nighthawk",
"group:nightmare software",
"group:nigimitama no ya",
"group:nihao series",
"group:nihatsu shika ataranai",
"group:nihon dandy",
"group:nihon denga senmon gakkou",
@@ -4168,6 +4245,7 @@ object Group : TagList {
"group:nijiiro zakura",
"group:nijinoren",
"group:nijiyome",
"group:niko-chan planning",
"group:nikomark",
"group:nikomi omurice",
"group:nikoniko company",
@@ -4323,6 +4401,7 @@ object Group : TagList {
"group:obsession.",
"group:ochanomizu mokujinkai",
"group:ochawan",
"group:ochi mono kanzume",
"group:ochikochitei",
"group:ochikonium",
"group:ochimusha.",
@@ -4339,6 +4418,7 @@ object Group : TagList {
"group:office amagasa",
"group:ofuton de suyaa",
"group:ogeretsu-dan",
"group:ogon shinshi club",
"group:ogre no heya",
"group:oh-banzai studio",
"group:ohagi.",
@@ -4413,6 +4493,7 @@ object Group : TagList {
"group:onsen",
"group:oobari doujou",
"group:ookami no o",
"group:ookina gomibako",
"group:ookina kodomo no omocha bako",
"group:operating room",
"group:oppawi shitei",
@@ -4489,8 +4570,10 @@ object Group : TagList {
"group:owlpop",
"group:oxide.lab",
"group:oyako donburi tei",
"group:oyasumi kobe gyuu",
"group:ozawa kobo",
"group:p kikaku",
"group:p shoukai",
"group:p-collection",
"group:p-forest",
"group:p-pooh",
@@ -4632,6 +4715,7 @@ object Group : TagList {
"group:pink-noise",
"group:pinkbell software",
"group:pinkharlem",
"group:pinki wana",
"group:pinktips.info",
"group:pinkysoft",
"group:pinpoint",
@@ -4689,6 +4773,7 @@ object Group : TagList {
"group:ponchees kari",
"group:ponkotuna potunoya",
"group:ponpon-black",
"group:ponponponpo",
"group:pons lab",
"group:pony farm",
"group:ponyfarm",
@@ -4864,6 +4949,7 @@ object Group : TagList {
"group:remora works",
"group:ren-kon-an",
"group:renai mangaka",
"group:rengaworks",
"group:renge-dou",
"group:rengeza",
"group:renglet",
@@ -4967,6 +5053,7 @@ object Group : TagList {
"group:ruku-pusyu",
"group:running girl",
"group:runrun soft",
"group:ruri-iro special room",
"group:ruriiro honpo",
"group:rurirara star",
"group:ruruna and nimunimu",
@@ -5053,6 +5140,7 @@ object Group : TagList {
"group:sakuraproject",
"group:sakurasaku",
"group:sakurasaku koubou",
"group:sakurayakan no hanare",
"group:sakuryu",
"group:sakusaku kangen noushuku",
"group:sakusakusakuchan",
@@ -5241,6 +5329,7 @@ object Group : TagList {
"group:shiina club",
"group:shiinotomoshibitake",
"group:shiitake nouen",
"group:shijimi wari ningyou",
"group:shijou misaki",
"group:shikakui tori",
"group:shiki be careful",
@@ -5274,6 +5363,7 @@ object Group : TagList {
"group:shingeki no nameko",
"group:shining star",
"group:shining star lilys",
"group:shinise ikedaya",
"group:shinkirou akatsuki",
"group:shinkuraiku",
"group:shinnihon pepsitou",
@@ -5338,8 +5428,11 @@ object Group : TagList {
"group:short kami",
"group:shortcut koubou",
"group:shosekido",
"group:shota mangaya-san",
"group:shotacon-do",
"group:shouchuu mac",
"group:shoudansha",
"group:shougusha",
"group:shoujo 2-jou",
"group:shoujo gesshoku",
"group:shoujo kousaku",
@@ -5356,6 +5449,7 @@ object Group : TagList {
"group:showa saishuu sensen",
"group:showa shojo",
"group:showano",
"group:shubi-ryoku 4man",
"group:shudoushiki denki jidousha",
"group:shumi eshi",
"group:shumisen jiru",
@@ -5384,6 +5478,7 @@ object Group : TagList {
"group:silky to yukai na nakama-tachi",
"group:silkys plus wasabi",
"group:silmaril",
"group:silver rice",
"group:silver ring",
"group:silver-kingdom",
"group:silver-rx",
@@ -5407,6 +5502,7 @@ object Group : TagList {
"group:sirouto plan",
"group:sirubedou",
"group:sisinabeya",
"group:sistny and anasis",
"group:sittori oblaat",
"group:sketch-book",
"group:skid-mark",
@@ -5512,6 +5608,7 @@ object Group : TagList {
"group:spicia",
"group:spig at",
"group:spiral brain",
"group:spiritguide",
"group:spock-san",
"group:sponge empire",
"group:sql",
@@ -5559,6 +5656,7 @@ object Group : TagList {
"group:studio c-take",
"group:studio cardamom",
"group:studio ciao",
"group:studio diamond",
"group:studio dna",
"group:studio e.go",
"group:studio erohouse",
@@ -5602,6 +5700,7 @@ object Group : TagList {
"group:studio southpaw",
"group:studio sunadokei",
"group:studio sushi kui-ne",
"group:studio t.r.c.",
"group:studio tapa tapa",
"group:studio wallaby",
"group:studio waltz",
@@ -5615,6 +5714,7 @@ object Group : TagList {
"group:studio30neko",
"group:studiomia",
"group:studios",
"group:stukitora",
"group:stulli-yasan",
"group:sturm no shukuten",
"group:su kanchou koubou",
@@ -5657,6 +5757,7 @@ object Group : TagList {
"group:sumomo hana koushu",
"group:sunadokei to enpitsu",
"group:sunatoka aoi noyama",
"group:sunege6",
"group:sunezumi fauvism",
"group:sunora",
"group:sunsetmoon",
@@ -5766,8 +5867,10 @@ object Group : TagList {
"group:tamago no kimi",
"group:tamanegiya",
"group:tamatamasanmyaku",
"group:tamima-ya",
"group:tamokuteki hall",
"group:tamokuteki kuukan",
"group:tana kara marriage",
"group:tanajou",
"group:tanaka shouten",
"group:tanaura honpo",
@@ -5778,10 +5881,13 @@ object Group : TagList {
"group:tanmatsu ijou",
"group:tanpatsu kikaku",
"group:tansanshonen",
"group:tanu-chan chi",
"group:tanukineiri",
"group:tarai death",
"group:tarako koubou",
"group:tarakospa",
"group:tarantula",
"group:tarareba naraba",
"group:taromarun",
"group:tashikani",
"group:tasogare hakubutukan",
@@ -5799,6 +5905,7 @@ object Group : TagList {
"group:team dai 7 youhei shidan",
"group:team hin ga 9",
"group:team ibm",
"group:team lv",
"group:team okays",
"group:team tanabe",
"group:team z and 3n",
@@ -5854,6 +5961,8 @@ object Group : TagList {
"group:terolin soft",
"group:terra drive",
"group:testa kitchen",
"group:testme1111",
"group:tetora star gumi",
"group:tetorapotto bunsitu",
"group:tetrodotoxin",
"group:tetsubou shounen",
@@ -5866,6 +5975,7 @@ object Group : TagList {
"group:th",
"group:th4",
"group:the block buster destruction",
"group:the fourth sequence",
"group:the fuckin toyzaras",
"group:the jolly roger",
"group:the knight of the pants",
@@ -5898,115 +6008,5 @@ object Group : TagList {
"group:tirol bunko",
"group:tissuhaco",
"group:titanaluminiden",
"group:titancolor brand",
"group:titeki-kairaku",
"group:titillatio",
"group:titokara 2nd branch",
"group:tits",
"group:tiusan kingdom",
"group:tj studio",
"group:tkh soft",
"group:tkspower",
"group:tnc.",
"group:tobihizageri",
"group:todd special",
"group:toei animation",
"group:togari-nozawa",
"group:tohonifun",
"group:tojora-men",
"group:tokaeshina koubou",
"group:tokai oohashi",
"group:tokinoame",
"group:toko-ya",
"group:tokohuyu no bakansu",
"group:tokonatsu tou",
"group:tokoroniyori-tengoku",
"group:tokumori ajillo",
"group:tokutan biyori",
"group:tokyo big eros",
"group:tokyo bungeling bay yokohama",
"group:tokyo corechica",
"group:tokyo gamachannel",
"group:tokyo kumitaisougumi",
"group:tokyo note",
"group:tokyo ponpon dou",
"group:tokyo rox",
"group:tokyo tomodachi kouen",
"group:tokyo tsunamushi land",
"group:tokyo-rozewomond club",
"group:tokyoboogienight",
"group:tokyusen",
"group:toluene ittokan",
"group:tomatogohan.",
"group:tomatohouse-905s room",
"group:tomatta tokei",
"group:tomcat",
"group:tomizofu",
"group:tomoe project",
"group:tomonokai",
"group:tomoshibi-ya",
"group:tomoshibiya koubou",
"group:tonari no dagashiya-san",
"group:tonari no machi no teishokuya",
"group:tonari no yama",
"group:tondemo 8 pun",
"group:tongari gorigori",
"group:tonikakuushi",
"group:tonkotsu fuumi",
"group:tonny club",
"group:tonpuuratei",
"group:tonteki teishoku",
"group:tonton byoushi",
"group:tontoro daiyokujou",
"group:tonyu bokujo",
"group:tonzura douchuu",
"group:top hat studios",
"group:toppuu dooro",
"group:toragoyashiki",
"group:toraisix",
"group:toraiya",
"group:torajirusi",
"group:torano ori",
"group:toratepotto",
"group:toratsugumi",
"group:tori salt",
"group:toriaezu kari",
"group:toridorinori",
"group:toriha dance",
"group:toriihime",
"group:torikaeshi no tsukanai sex",
"group:torino sunakimo",
"group:torinoya",
"group:toriten software studio.",
"group:toro plus drop",
"group:toro toro resistance",
"group:toro-chin teishoku",
"group:toro2 circus",
"group:torochidan",
"group:toruneko chaya",
"group:totencop",
"group:toto max",
"group:totocetera",
"group:totontei",
"group:totoyasu no tsf lab",
"group:totsugasa",
"group:totsugeki tonarino jo-galbi",
"group:tottoko mtarou",
"group:tottori-sabaku kingdom",
"group:tottototomekichi",
"group:touch",
"group:tougall kai",
"group:touge mine",
"group:tougenkyou",
"group:tougesakuraya",
"group:touhou marupondou",
"group:touin",
"group:toukon iwashikusa",
"group:toumei kousoku",
"group:toumei tsuushin",
"group:toushi ryoku kenkyuujo",
"group:toutaku tuyagadou",
"group:touyou bujutsu gakkou",
"group:touyu okiba kari",
)
}
+128
View File
@@ -2,10 +2,124 @@ package exh.eh.tags
object Group2 : TagList {
override fun getTags1(): List<String> = listOf(
"group:titancolor brand",
"group:titeki-kairaku",
"group:titillatio",
"group:titokara 2nd branch",
"group:tits",
"group:tiusan kingdom",
"group:tj studio",
"group:tk jesus",
"group:tkh soft",
"group:tkspower",
"group:tnc.",
"group:tobihizageri",
"group:todd special",
"group:toei animation",
"group:togari-nozawa",
"group:tohonifun",
"group:tojora-men",
"group:tokaeshina koubou",
"group:tokai oohashi",
"group:tokinoame",
"group:toko-ya",
"group:tokohuyu no bakansu",
"group:tokonatsu tou",
"group:tokoroniyori-tengoku",
"group:tokumori ajillo",
"group:tokushuyokujou tondenhei",
"group:tokutan biyori",
"group:tokyo big eros",
"group:tokyo bungeling bay yokohama",
"group:tokyo corechica",
"group:tokyo gamachannel",
"group:tokyo kumitaisougumi",
"group:tokyo manga kenkyuujo",
"group:tokyo note",
"group:tokyo ponpon dou",
"group:tokyo rox",
"group:tokyo tomodachi kouen",
"group:tokyo tsunamushi land",
"group:tokyo-rozewomond club",
"group:tokyoboogienight",
"group:tokyusen",
"group:toluene ittokan",
"group:tomatogohan.",
"group:tomatohouse-905s room",
"group:tomatta tokei",
"group:tomcat",
"group:tomizofu",
"group:tomoe project",
"group:tomonokai",
"group:tomoshibi-ya",
"group:tomoshibiya koubou",
"group:tonari no dagashiya-san",
"group:tonari no machi no teishokuya",
"group:tonari no yama",
"group:tondemo 8 pun",
"group:tongari gorigori",
"group:tonikakuushi",
"group:tonkotsu fuumi",
"group:tonny club",
"group:tonpuuratei",
"group:tonteki teishoku",
"group:tonton byoushi",
"group:tontoro daiyokujou",
"group:tonyu bokujo",
"group:tonzura douchuu",
"group:top hat studios",
"group:toppuu dooro",
"group:toragoyashiki",
"group:toraisix",
"group:toraiya",
"group:torajirusi",
"group:torano ori",
"group:toratepotto",
"group:toratsugumi",
"group:tori salt",
"group:toriaezu kari",
"group:toridorinori",
"group:toriha dance",
"group:toriihime",
"group:torikaeshi no tsukanai sex",
"group:torino sunakimo",
"group:torinoya",
"group:toriten software studio.",
"group:toro plus drop",
"group:toro toro resistance",
"group:toro-chin teishoku",
"group:toro2 circus",
"group:torochidan",
"group:toruneko chaya",
"group:totencop",
"group:toto max",
"group:totocetera",
"group:totontei",
"group:totoyasu no tsf lab",
"group:totsugasa",
"group:totsugeki tonarino jo-galbi",
"group:tottoko mtarou",
"group:tottori-sabaku kingdom",
"group:tottototomekichi",
"group:touch",
"group:tougall kai",
"group:touge mine",
"group:tougenkyou",
"group:tougesakuraya",
"group:touhou marupondou",
"group:touin",
"group:toukon iwashikusa",
"group:toumei kousoku",
"group:toumei tsuushin",
"group:toushi ryoku kenkyuujo",
"group:toutaku tuyagadou",
"group:touyou bujutsu gakkou",
"group:touyu okiba kari",
"group:touyu stand",
"group:touzainanboku",
"group:touzoku tachi no rakuda no mure",
"group:toxic love",
"group:toyamando",
"group:toybox",
"group:tozan bu",
"group:tp",
@@ -55,6 +169,7 @@ object Group2 : TagList {
"group:tsuredure children",
"group:tsurezurezuki",
"group:tsurikichi doumei",
"group:tsurugashima heights",
"group:tsurumiku",
"group:tsurupeta kenkyuusho",
"group:tsurutasousankai",
@@ -70,6 +185,7 @@ object Group2 : TagList {
"group:tukinon bunko",
"group:tukishitahikou",
"group:tumiribbon",
"group:tuna ozawa",
"group:tunacan.",
"group:tunadrive",
"group:turuvege.",
@@ -141,8 +257,10 @@ object Group2 : TagList {
"group:uminouie",
"group:umon paradise",
"group:unagi no nedoko",
"group:unagineco house",
"group:unaginobori",
"group:unceder",
"group:uncertain field",
"group:unconscious",
"group:undamesi",
"group:undead",
@@ -201,6 +319,7 @@ object Group2 : TagList {
"group:uribatakebokujou",
"group:urondou",
"group:urusai kokuen",
"group:uruudoshi",
"group:us",
"group:usa daioh",
"group:usa.k",
@@ -218,6 +337,7 @@ object Group2 : TagList {
"group:usausa",
"group:ushi ushido",
"group:ushidon-ya",
"group:ushikani gassen",
"group:uso seisakusho",
"group:uso293",
"group:usotsuki house",
@@ -282,6 +402,7 @@ object Group2 : TagList {
"group:wankyoku canvas",
"group:wanwandoh",
"group:warabimochi",
"group:warau kado ni wa",
"group:waretama",
"group:warp loop",
"group:wasa wasa",
@@ -364,6 +485,7 @@ object Group2 : TagList {
"group:yakisaketeishoku",
"group:yakisoba pants",
"group:yakisoba rengo",
"group:yakitate jamaica",
"group:yakiubu",
"group:yakousei fan club",
"group:yaku 40 man sarad",
@@ -380,6 +502,7 @@ object Group2 : TagList {
"group:yamakawa denenhuukei",
"group:yamami no yado",
"group:yamamori gohan",
"group:yamamoto keiji",
"group:yamanaka no naka",
"group:yamanashi musume.",
"group:yamano murao",
@@ -463,10 +586,12 @@ object Group2 : TagList {
"group:yoyude ikemasu",
"group:yozorairodrops",
"group:ys company",
"group:yu-chu-bu",
"group:yu-ta.18",
"group:yu-yu-tei",
"group:yuasa rengou",
"group:yubidou",
"group:yubunecity",
"group:yudenakya nama-beer",
"group:yudokuya",
"group:yuhshiki",
@@ -497,6 +622,7 @@ object Group2 : TagList {
"group:yumeoikyounouta",
"group:yumeoukoku",
"group:yunabon",
"group:yuo kurokawa",
"group:yurayuraseyuura",
"group:yurei yashiki",
"group:yureika blade",
@@ -564,6 +690,7 @@ object Group2 : TagList {
"group:zenshuu bougyo",
"group:zensoku zenkai.",
"group:zensun habaku",
"group:zenten ukemi tomonokai",
"group:zenzidou kosyubenjo",
"group:zero byte",
"group:zero equals mono",
@@ -589,6 +716,7 @@ object Group2 : TagList {
"group:zugaikotsu marudashi",
"group:zundoko sperm bank",
"group:zurumuke taro",
"group:zutto mae kara darui.",
"group:zvizva-dan",
"group:zydan",
"group:zyulokuya",
+12
View File
@@ -37,6 +37,7 @@ object Male : TagList {
"male:balljob",
"male:balls expansion",
"male:bandages",
"male:bandaid",
"male:bat boy",
"male:bbm",
"male:bdsm",
@@ -55,7 +56,9 @@ object Male : TagList {
"male:big penis",
"male:bike shorts",
"male:bikini",
"male:bird boy",
"male:bisexual",
"male:bite mark",
"male:blackmail",
"male:blind",
"male:blindfold",
@@ -111,8 +114,10 @@ object Male : TagList {
"male:cockslapping",
"male:collar",
"male:condom",
"male:confinement",
"male:conjoined",
"male:coprophagia",
"male:corpse",
"male:corruption",
"male:corset",
"male:cosplaying",
@@ -157,6 +162,7 @@ object Male : TagList {
"male:drill hair",
"male:drugs",
"male:drunk",
"male:ear fuck",
"male:eel",
"male:eggs",
"male:electric shocks",
@@ -198,6 +204,7 @@ object Male : TagList {
"male:frog",
"male:frog boy",
"male:frottage",
"male:full tour",
"male:fundoshi",
"male:furry",
"male:gag",
@@ -273,6 +280,7 @@ object Male : TagList {
"male:kimono",
"male:kindergarten uniform",
"male:kissing",
"male:kodomo doushi",
"male:kunoichi",
"male:lab coat",
"male:lactation",
@@ -291,6 +299,7 @@ object Male : TagList {
"male:long tongue",
"male:low bestiality",
"male:low guro",
"male:low incest",
"male:low scat",
"male:low shotacon",
"male:low smegma",
@@ -329,6 +338,7 @@ object Male : TagList {
"male:multiple assjob",
"male:multiple footjob",
"male:multiple handjob",
"male:multiple nipples",
"male:multiple orgasms",
"male:multiple penises",
"male:multiple straddling",
@@ -336,6 +346,7 @@ object Male : TagList {
"male:muscle growth",
"male:mute",
"male:nakadashi",
"male:navel birth",
"male:navel fuck",
"male:nazi",
"male:necrophilia",
@@ -496,6 +507,7 @@ object Male : TagList {
"male:tracksuit",
"male:trampling",
"male:transformation",
"male:transparent clothing",
"male:triple anal",
"male:triple penetration",
"male:tube",
+2
View File
@@ -10,6 +10,8 @@ object Mixed : TagList {
"mixed:group",
"mixed:incest",
"mixed:inseki",
"mixed:kodomo doushi",
"mixed:low incest",
"mixed:mmf threesome",
"mixed:mmt threesome",
"mixed:mtf threesome",
+3 -2
View File
@@ -4,7 +4,6 @@ object Other : TagList {
override fun getTags1(): List<String> = listOf(
"other:3d",
"other:3d imageset",
"other:ai generated",
"other:already uploaded",
"other:anaglyph",
"other:animated",
@@ -20,21 +19,23 @@ object Other : TagList {
"other:forbidden content",
"other:full censorship",
"other:full color",
"other:game manual",
"other:game sprite",
"other:goudoushi",
"other:hardcore",
"other:how to",
"other:incomplete",
"other:kodomo only",
"other:missing cover",
"other:mosaic censorship",
"other:multi-work series",
"other:multipanel sequence",
"other:no penetration",
"other:non-h game manual",
"other:non-h imageset",
"other:non-nude",
"other:novel",
"other:nudity only",
"other:object insertion only",
"other:out of order",
"other:paperchild",
"other:realporn",
+64 -9
View File
@@ -36,6 +36,7 @@ object Parody : TagList {
"parody:a vampyre story",
"parody:a.d.police",
"parody:a.i. ga tomaranai",
"parody:abashiri ikka",
"parody:abenobashi mahou shoutengai",
"parody:acca 13-ku kansatsu-ka",
"parody:accel world",
@@ -243,7 +244,6 @@ object Parody : TagList {
"parody:battle royale",
"parody:battle spirits",
"parody:beast wars",
"parody:beat angel escalayer",
"parody:beat blades haruka",
"parody:beatmania",
"parody:beauty and the beast",
@@ -348,6 +348,7 @@ object Parody : TagList {
"parody:burst angel",
"parody:busou renkin",
"parody:busou shoujo machiavellianism",
"parody:buta no gotoki",
"parody:buzz lightyear of star command",
"parody:c cube",
"parody:c the money of soul and possibility control",
@@ -382,18 +383,21 @@ object Parody : TagList {
"parody:childs play",
"parody:chio-chan no tsuugakuro",
"parody:chip n dale rescue rangers",
"parody:chiyu mahou no machigatta tsukaikata",
"parody:cho aniki",
"parody:chobits",
"parody:chogattai majutsu robot ginguiser",
"parody:chokotto sister",
"parody:chou dokyuu shoujo 4946",
"parody:chou kuse ni narisou",
"parody:chou-tantei jikenbo rain code",
"parody:choudenshi bioman",
"parody:chouja raideen",
"parody:choujikuu kidan southern cross",
"parody:choujin koukousei-tachi wa isekai demo yoyuu de ikinuku you desu",
"parody:choujuu kishin dancougar",
"parody:choukou shinki ixseal",
"parody:choukou tenshi escalayer",
"parody:chronicles of the going home club",
"parody:chrono cross",
"parody:chrono crusade",
@@ -554,6 +558,7 @@ object Parody : TagList {
"parody:dororon enma-kun",
"parody:dosanko gal wa namaramenkoi",
"parody:doubutsu banchou",
"parody:doubutsu nee-chan",
"parody:doubutsu no oishasan",
"parody:doukyuusei 2",
"parody:douluo continent",
@@ -565,6 +570,7 @@ object Parody : TagList {
"parody:dragon age",
"parody:dragon ball",
"parody:dragon ball gt",
"parody:dragon ball heroes",
"parody:dragon ball super",
"parody:dragon ball z",
"parody:dragon half",
@@ -628,6 +634,7 @@ object Parody : TagList {
"parody:elemental gelade",
"parody:elf-san wa yaserarenai.",
"parody:elfen lied",
"parody:elfquest",
"parody:emma a victorian romance",
"parody:endless frontier",
"parody:enen no shouboutai",
@@ -705,12 +712,14 @@ object Parody : TagList {
"parody:final romance",
"parody:fire emblem",
"parody:fire emblem awakening",
"parody:fire emblem fates",
"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 path of radiance",
"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 three houses",
"parody:fist of the north star",
@@ -773,6 +782,7 @@ object Parody : TagList {
"parody:gakkou gurashi",
"parody:gakkou no kaidan",
"parody:gakuen alice",
"parody:gakuen babysitters",
"parody:gakuen heaven",
"parody:gakusen toshi asterisk",
"parody:galactic drifter vifam",
@@ -807,6 +817,7 @@ object Parody : TagList {
"parody:genroh",
"parody:genshiken",
"parody:genshin impact",
"parody:gensou suikoden",
"parody:getbackers",
"parody:getsumen to heiki mina",
"parody:getter robo",
@@ -830,6 +841,7 @@ object Parody : TagList {
"parody:gochuumon wa usagi desu ka",
"parody:god eater",
"parody:god of war",
"parody:goddess of victory nikke",
"parody:gogo sentai boukenger",
"parody:gokudou-kun manyuuki",
"parody:gokujou seitokai",
@@ -839,6 +851,7 @@ object Parody : TagList {
"parody:golden sun",
"parody:goldfish warning",
"parody:goof troop",
"parody:gormiti",
"parody:gosenzo san-e",
"parody:goshujin-sama to kemonomimi no shoujo mel",
"parody:goshuushou-sama ninomiya-kun",
@@ -911,12 +924,14 @@ object Parody : TagList {
"parody:hakushon daimaou",
"parody:half-life",
"parody:halo",
"parody:hametsu no oukoku",
"parody:hamtaro",
"parody:hana no joshi announcer newscaster etsuko",
"parody:hanamaru youchien",
"parody:hanasaku iroha",
"parody:hanaukyo maid tai",
"parody:hand maid may",
"parody:hantsu x trash",
"parody:hanzasky",
"parody:happiness",
"parody:happinesscharge precure",
@@ -932,11 +947,13 @@ object Parody : TagList {
"parody:hataage kemono michi",
"parody:hataraku onii-san",
"parody:hataraku saibou",
"parody:hatena no tou",
"parody:hateshinaku aoi kono sora no shita de...",
"parody:hatsukoi limited",
"parody:hayate no gotoku",
"parody:hayate x blade",
"parody:hazun de catch",
"parody:hazure waku",
"parody:he is my master",
"parody:he-man and the masters of the universe",
"parody:heartcatch precure",
@@ -969,6 +986,7 @@ object Parody : TagList {
"parody:hime kishi lilia",
"parody:hime-chans ribbon",
"parody:himegoto",
"parody:himiko-den",
"parody:himitsu no akko-chan",
"parody:himitsu sentai metamor v",
"parody:hinabita",
@@ -1004,6 +1022,7 @@ object Parody : TagList {
"parody:how the grinch stole christmas",
"parody:how to train your dragon",
"parody:howls moving castle",
"parody:hp himepara",
"parody:hugtto precure",
"parody:hulu xiongdi",
"parody:hunter x hunter",
@@ -1092,6 +1111,7 @@ object Parody : TagList {
"parody:jewelpet tinkle",
"parody:jibaku shounen hanako-kun",
"parody:jigoku shoujo",
"parody:jiisan baasan wakagaeru",
"parody:jijou wo shiranai tenkousei ga guigui kuru.",
"parody:jikkyou powerful pro yakyuu",
"parody:jikuu senshi spielban",
@@ -1142,6 +1162,7 @@ object Parody : TagList {
"parody:kageki shojo",
"parody:kagihime monogatari eikyuu alice rondo",
"parody:kagura reimeiki",
"parody:kagurabachi",
"parody:kaguya-sama wa kokurasetai",
"parody:kaichou wa maid-sama",
"parody:kaifuku jutsushi no yarinaoshi",
@@ -1187,12 +1208,15 @@ object Parody : TagList {
"parody:kappa no kaikata",
"parody:kara no kyoukai",
"parody:kara no naka no kotori",
"parody:karakai jouzu no takagi-san",
"parody:karakuri kiden",
"parody:kare kano",
"parody:kashimashi",
"parody:kasumin",
"parody:katawa shoujo",
"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:katte ni kaizou",
"parody:kawaii dake ja nai shikimori-san",
@@ -1293,6 +1317,7 @@ object Parody : TagList {
"parody:kozure ookami",
"parody:kubo-san wa mob o yurusanai",
"parody:kumo desu ga nani ka",
"parody:kung fu cooking girls",
"parody:kung fu panda",
"parody:kunoichi",
"parody:kuon no kizuna",
@@ -1394,6 +1419,8 @@ object Parody : TagList {
"parody:lupin iii",
"parody:lux-pain",
"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:macademi wasshoi",
"parody:machikado mazoku",
@@ -1458,10 +1485,12 @@ object Parody : TagList {
"parody:makai kishi ingrid",
"parody:makai tenshi jibril",
"parody:makai toushi saga",
"parody:make heroine ga oosugiru",
"parody:maken-ki",
"parody:makyou gaiden le deus",
"parody:mama is a 4th grader",
"parody:mama wa poyopoyo saurus ga osuki",
"parody:mamahaha no tsurego ga motokano datta",
"parody:mamono musume zukan",
"parody:mamoru-kun",
"parody:mamoru-kun ni megami no shukufuku wo",
@@ -1471,6 +1500,7 @@ object Parody : TagList {
"parody:mangaka-san to assistant-san to",
"parody:manyuu hikenchou",
"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:maoujou de oyasumi",
"parody:maoyuu maou yuusha",
@@ -1520,6 +1550,7 @@ object Parody : TagList {
"parody:megami paradise",
"parody:megami-ryou no ryoubo-kun.",
"parody:megamind",
"parody:mehime no toriko",
"parody:meiken lassie",
"parody:meili xinshijie i",
"parody:melon-chan no seichouki",
@@ -1611,6 +1642,7 @@ object Parody : TagList {
"parody:my life as a teenage robot",
"parody:my little pony friendship is magic",
"parody:my neighbor totoro",
"parody:my wife is a demon queen",
"parody:myriad colors phantom world",
"parody:myst",
"parody:na lesnoy trope",
@@ -1679,6 +1711,7 @@ object Parody : TagList {
"parody:nogizaka haruka no himitsu",
"parody:non anonymous instruction",
"parody:non non biyori",
"parody:nora to toki no koubou kiri no mori no majo",
"parody:nozoki ana",
"parody:nurarihyon no mago",
"parody:nurse angel ririka sos",
@@ -1733,7 +1766,9 @@ object Parody : TagList {
"parody:ookiku furikabutte",
"parody:oounabara to wadanohara",
"parody:ooyasan wa shishunki",
"parody:operators side",
"parody:ore dake haireru kakushi dungeon",
"parody:ore ga suki nano wa imouto dakedo imouto ja nai",
"parody:ore monogatari",
"parody:ore no imouto ga konna ni kawaii wake ga nai",
"parody:ore no kanojo to osananajimi ga shuraba sugiru",
@@ -1894,6 +1929,9 @@ object Parody : TagList {
"parody:resident evil",
"parody:resonance of fate",
"parody:ressha sentai toqger",
"parody:return to shironagasu island",
"parody:revelation online",
"parody:reverse 1999",
"parody:revevolution",
"parody:revolutionary girl utena",
"parody:rewrite",
@@ -1941,6 +1979,7 @@ object Parody : TagList {
"parody:saenai heroine no sodatekata",
"parody:saga frontier",
"parody:saijaku muhai no bahamut",
"parody:saijaku tamer wa gomi hiroi no tabi o hajimemashita.",
"parody:saijou no meii",
"parody:saikano",
"parody:saikin yatotta maid ga ayashii",
@@ -1963,6 +2002,9 @@ object Parody : TagList {
"parody:samurai 7",
"parody:samurai champloo",
"parody:samurai pizza cats",
)
override fun getTags2(): List<String> = listOf(
"parody:samurai sentai shinkenger",
"parody:samurai spirits",
"parody:samurai warriors",
@@ -1989,6 +2031,7 @@ object Parody : TagList {
"parody:scott pilgrim",
"parody:scp foundation",
"parody:sd gundam sangokuden",
"parody:seer",
"parody:sei juushi bismark",
"parody:sei senshi yariman 12",
"parody:seijo no maryoku wa bannou desu",
@@ -2002,9 +2045,6 @@ object Parody : TagList {
"parody:seirei no moribito",
"parody:seishoujo sentai lakers",
"parody:seishun buta yarou wa bunny girl senpai no yume o minai",
)
override fun getTags2(): List<String> = listOf(
"parody:seito kaichou hikaru",
"parody:seitokai no ichizon",
"parody:seitokai yakuindomo",
@@ -2185,6 +2225,7 @@ object Parody : TagList {
"parody:star wars",
"parody:star-myu",
"parody:starcraft",
"parody:stargate",
"parody:starry sky",
"parody:station memories",
"parody:steam detectives",
@@ -2199,7 +2240,6 @@ object Parody : TagList {
"parody:strike witches",
"parody:sucker punch",
"parody:suigetsu",
"parody:suikoden",
"parody:suikoden v",
"parody:suisei no gargantia",
"parody:suite precure",
@@ -2297,7 +2337,9 @@ object Parody : TagList {
"parody:tenkuu senki shurato",
"parody:tenkuu shinpan",
"parody:tensai ryouri shounen ajinosuke",
"parody:tensei kizoku kantei skill de nariagaru",
"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:tenshi na konamaiki",
"parody:tenshi ni narumon",
@@ -2360,6 +2402,7 @@ object Parody : TagList {
"parody:the legend of heroes",
"parody:the legend of korra",
"parody:the legend of luo xiaohei",
"parody:the legend of the condor heroes",
"parody:the legend of the legendary heroes",
"parody:the legend of zelda",
"parody:the life and times of juniper lee",
@@ -2386,6 +2429,7 @@ object Parody : TagList {
"parody:the princess and the frog",
"parody:the proud family",
"parody:the queen of duellist",
"parody:the queens gambit",
"parody:the ren and stimpy show",
"parody:the replacements",
"parody:the rescuers",
@@ -2435,7 +2479,7 @@ object Parody : TagList {
"parody:togainu no chi",
"parody:toheart2",
"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:tokusatsu gagaga",
"parody:tokusou sentai dekaranger",
@@ -2453,6 +2497,7 @@ object Parody : TagList {
"parody:tonagura",
"parody:tonari no kaibutsu-kun",
"parody:tonari no kyuuketsuki-san",
"parody:tongari boushi",
"parody:tongari boushi no atelier",
"parody:tonikaku kawaii",
"parody:toradora",
@@ -2503,6 +2548,7 @@ object Parody : TagList {
"parody:uchuu no kishi tekkaman",
"parody:uchuu no stellvia",
"parody:uchuu senshi baldios",
"parody:uchuu show e youkoso",
"parody:uchuujin tanaka tarou",
"parody:ufo princess valkyrie",
"parody:ukagaka",
@@ -2512,6 +2558,7 @@ object Parody : TagList {
"parody:uma musume pretty derby",
"parody:umi ga kikoeru",
"parody:umi monogatari",
"parody:umibe no etranger",
"parody:umineko no naku koro ni",
"parody:un-go",
"parody:unbalance x unbalance",
@@ -2541,6 +2588,7 @@ object Parody : TagList {
"parody:vandread",
"parody:vanitas no carte",
"parody:variable geo",
"parody:various",
"parody:vatican kiseki chousakan",
"parody:venus and braves",
"parody:venus blood -ragnarok-",
@@ -2550,7 +2598,9 @@ object Parody : TagList {
"parody:video girl ai",
"parody:viewtiful joe",
"parody:vindictus",
"parody:violated heroine",
"parody:violinist of hameln",
"parody:viorate no atelier",
"parody:viper",
"parody:viper ctr",
"parody:viper f40",
@@ -2565,6 +2615,7 @@ object Parody : TagList {
"parody:vocaloid",
"parody:voiceroid",
"parody:voltage fighter gowcaizer",
"parody:vshojo",
"parody:w.i.t.c.h.",
"parody:wagaya no oinari-sama",
"parody:waka okami wa shougakusei",
@@ -2583,6 +2634,7 @@ object Parody : TagList {
"parody:warship girls",
"parody:warzard",
"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 ni tenshi ga maiorita",
"parody:watashi no ashinaga ojisan",
@@ -2599,6 +2651,7 @@ object Parody : TagList {
"parody:white album",
"parody:wild arms",
"parody:wild arms 2",
"parody:windbreaker",
"parody:wingman",
"parody:wings of honneamise",
"parody:winnie the pooh",
@@ -2657,15 +2710,18 @@ object Parody : TagList {
"parody:yoake mae yori ruriiro na",
"parody:yofukashi no uta",
"parody:yokohama kaidashi kikou",
"parody:yoku wakaru gendai mahou",
"parody:yomawari",
"parody:yondemasuyo azazel-san",
"parody:yongbi bulpae",
"parody:yoroiden samurai troopers",
"parody:yoru ga kuru",
"parody:yoru no kurage wa oyogenai",
"parody:yoru no yatterman",
"parody:yoshinaga-san chi no gargoyle",
"parody:yosuga no sora",
"parody:yotsubato",
"parody:you shou yan",
"parody:youjo senki",
"parody:youjuu senki a.d. 2048",
"parody:youkai hyakkitan",
@@ -2714,7 +2770,6 @@ object Parody : TagList {
"parody:zettai muteki raijin-oh",
"parody:zettai shougeki platonic heart",
"parody:zettai zetsumei toshi 3",
"parody:zhongfan weilai 1999",
"parody:zoids",
"parody:zoids genesis",
"parody:zoids new century",
@@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import okhttp3.FormBody
import okhttp3.Request
import tachiyomi.core.common.i18n.stringResource
@@ -64,18 +65,18 @@ class FavoritesSyncHelper(val context: Context) {
?: EHentai(0, true, context)
}
private val storage = LocalFavoritesStorage()
private val storage by lazy { LocalFavoritesStorage() }
private val galleryAdder = GalleryAdder()
private val galleryAdder by lazy { GalleryAdder() }
private val throttleManager = EHentaiThrottleManager()
private val throttleManager by lazy { EHentaiThrottleManager() }
private var wifiLock: WifiManager.WifiLock? = null
private var wakeLock: PowerManager.WakeLock? = null
private val logger = xLog()
private val logger by lazy { xLog() }
val status: MutableStateFlow<FavoritesSyncStatus> = MutableStateFlow(FavoritesSyncStatus.Idle(context))
val status: MutableStateFlow<FavoritesSyncStatus> = MutableStateFlow(FavoritesSyncStatus.Idle)
@Synchronized
fun runSync(scope: CoroutineScope) {
@@ -83,7 +84,7 @@ class FavoritesSyncHelper(val context: Context) {
return
}
status.value = FavoritesSyncStatus.Initializing(context)
status.value = FavoritesSyncStatus.Initializing
scope.launch(Dispatchers.IO) { beginSync() }
}
@@ -91,13 +92,12 @@ class FavoritesSyncHelper(val context: Context) {
private suspend fun beginSync() {
// Check if logged in
if (!prefs.enableExhentai().get()) {
status.value = FavoritesSyncStatus.Error(context.stringResource(SYMR.strings.please_login))
status.value = FavoritesSyncStatus.SyncError.NotLoggedInSyncError
return
}
// Validate library state
status.value =
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_verifying_library))
status.value = FavoritesSyncStatus.Processing.VerifyingLibrary
val libraryManga = getLibraryManga.await()
val seenManga = HashSet<Long>(libraryManga.size)
libraryManga.forEach { (manga) ->
@@ -106,7 +106,7 @@ class FavoritesSyncHelper(val context: Context) {
if (manga.id in seenManga) {
val inCategories = getCategories.await(manga.id)
status.value = FavoritesSyncStatus.BadLibraryState
.MangaInMultipleCategories(manga, inCategories, context)
.MangaInMultipleCategories(manga.id, manga.title, inCategories.map { it.name })
logger.w(context.stringResource(SYMR.strings.favorites_sync_gallery_multiple_categories_error, manga.id))
return
@@ -117,17 +117,15 @@ class FavoritesSyncHelper(val context: Context) {
// Download remote favorites
val favorites = try {
status.value =
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_downloading))
status.value = FavoritesSyncStatus.Processing.DownloadingFavorites
exh.fetchFavorites()
} catch (e: Exception) {
status.value =
FavoritesSyncStatus.Error(context.stringResource(SYMR.strings.favorites_sync_failed_to_featch))
status.value = FavoritesSyncStatus.SyncError.FailedToFetchFavorites
logger.e(context.stringResource(SYMR.strings.favorites_sync_could_not_fetch), e)
return
}
val errorList = mutableListOf<String>()
val errorList = mutableListOf<FavoritesSyncStatus.SyncError.GallerySyncError>()
try {
// Take wake + wifi locks
@@ -157,23 +155,17 @@ class FavoritesSyncHelper(val context: Context) {
// Do not update galleries while syncing favorites
EHentaiUpdateWorker.cancelBackground(context)
status.value = FavoritesSyncStatus.Processing(
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes),
)
status.value = FavoritesSyncStatus.Processing.CalculatingRemoteChanges
val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
val localChanges = if (prefs.exhReadOnlySync().get()) {
null // Do not build local changes if they are not going to be applied
} else {
status.value = FavoritesSyncStatus.Processing(
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes),
)
status.value = FavoritesSyncStatus.Processing.CalculatingLocalChanges
storage.getChangedDbEntries()
}
// Apply remote categories
status.value = FavoritesSyncStatus.Processing(
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names),
)
status.value = FavoritesSyncStatus.Processing.SyncingCategoryNames
applyRemoteCategories(favorites.second)
// Apply change sets
@@ -182,8 +174,7 @@ class FavoritesSyncHelper(val context: Context) {
applyChangeSetToRemote(errorList, localChanges)
}
status.value =
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_cleaning_up))
status.value = FavoritesSyncStatus.Processing.CleaningUp
storage.snapshotEntries()
withUIContext {
@@ -194,9 +185,7 @@ class FavoritesSyncHelper(val context: Context) {
logger.w(context.stringResource(SYMR.strings.favorites_sync_ignoring_exception), e)
return
} catch (e: Exception) {
status.value = FavoritesSyncStatus.Error(
context.stringResource(SYMR.strings.favorites_sync_unknown_error, e.message.orEmpty()),
)
status.value = FavoritesSyncStatus.SyncError.UnknownSyncError(e.message.orEmpty())
logger.e(context.stringResource(SYMR.strings.favorites_sync_sync_error), e)
return
} finally {
@@ -215,7 +204,7 @@ class FavoritesSyncHelper(val context: Context) {
}
if (errorList.isEmpty()) {
status.value = FavoritesSyncStatus.Idle(context)
status.value = FavoritesSyncStatus.Idle
} else {
status.value = FavoritesSyncStatus.CompleteWithErrors(errorList)
}
@@ -249,7 +238,7 @@ class FavoritesSyncHelper(val context: Context) {
}
}
private suspend fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) {
private suspend fun addGalleryRemote(errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>, gallery: FavoriteEntry) {
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
val request = POST(
@@ -263,13 +252,16 @@ class FavoritesSyncHelper(val context: Context) {
)
if (!explicitlyRetryExhRequest(10, request)) {
val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!"
val error = FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote(
gallery.title,
gallery.gid,
)
if (prefs.exhLenientSync().get()) {
errorList += errorString
errorList += error
} else {
status.value = FavoritesSyncStatus.Error(errorString)
throw IgnoredException(errorString)
status.value = error
throw IgnoredException(error)
}
}
}
@@ -293,12 +285,13 @@ class FavoritesSyncHelper(val context: Context) {
return success
}
private suspend fun applyChangeSetToRemote(errorList: MutableList<String>, changeSet: ChangeSet) {
private suspend fun applyChangeSetToRemote(
errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>,
changeSet: ChangeSet,
) {
// Apply removals
if (changeSet.removed.isNotEmpty()) {
status.value = FavoritesSyncStatus.Processing(
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, changeSet.removed.size),
)
status.value = FavoritesSyncStatus.Processing.RemovingRemoteGalleries(changeSet.removed.size)
val formBody = FormBody.Builder()
.add("ddact", "delete")
@@ -315,13 +308,11 @@ class FavoritesSyncHelper(val context: Context) {
)
if (!explicitlyRetryExhRequest(10, request)) {
val errorString = context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
if (prefs.exhLenientSync().get()) {
errorList += errorString
errorList += FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote
} else {
status.value = FavoritesSyncStatus.Error(errorString)
throw IgnoredException(errorString)
status.value = FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote
throw IgnoredException(FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote)
}
}
}
@@ -329,10 +320,10 @@ class FavoritesSyncHelper(val context: Context) {
// Apply additions
throttleManager.resetThrottle()
changeSet.added.forEachIndexed { index, it ->
status.value = FavoritesSyncStatus.Processing(
message = context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, index + 1, changeSet.added.size),
isThrottle = needWarnThrottle(),
context = context,
status.value = FavoritesSyncStatus.Processing.AddingGalleryToRemote(
index = index + 1,
total = changeSet.added.size,
isThrottling = needWarnThrottle(),
title = it.title,
)
@@ -342,14 +333,17 @@ class FavoritesSyncHelper(val context: Context) {
}
}
private suspend fun applyChangeSetToLocal(errorList: MutableList<String>, changeSet: ChangeSet) {
private suspend fun applyChangeSetToLocal(
errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>,
changeSet: ChangeSet,
) {
val removedManga = mutableListOf<Manga>()
// Apply removals
changeSet.removed.forEachIndexed { index, it ->
status.value = FavoritesSyncStatus.Processing(
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, index + 1, changeSet.removed.size),
title = it.title,
status.value = FavoritesSyncStatus.Processing.RemovingGalleryFromLocal(
index = index + 1,
total = changeSet.removed.size,
)
val url = it.getUrl()
@@ -379,10 +373,10 @@ class FavoritesSyncHelper(val context: Context) {
// Apply additions
throttleManager.resetThrottle()
changeSet.added.forEachIndexed { index, it ->
status.value = FavoritesSyncStatus.Processing(
message = context.stringResource(SYMR.strings.favorites_sync_add_to_local, index + 1, changeSet.added.size),
isThrottle = needWarnThrottle(),
context = context,
status.value = FavoritesSyncStatus.Processing.AddingGalleryToLocal(
index = index + 1,
total = changeSet.added.size,
isThrottling = needWarnThrottle(),
title = it.title,
)
@@ -405,24 +399,23 @@ class FavoritesSyncHelper(val context: Context) {
return@forEachIndexed
}
val errorString = context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
when (result) {
is GalleryAddEvent.Fail.Error -> context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, result.logMessage,
)
is GalleryAddEvent.Fail.UnknownType -> context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, result.galleryUrl,
)
is GalleryAddEvent.Fail.UnknownSource -> context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, result.galleryUrl,
)
}
val error = when (result) {
is GalleryAddEvent.Fail.Error -> FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail(
it.title, result.logMessage,
)
is GalleryAddEvent.Fail.UnknownType -> FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail(
it.title, result.galleryUrl,
)
is GalleryAddEvent.Fail.UnknownSource -> FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail(
it.title, result.galleryUrl,
)
}
if (prefs.exhLenientSync().get()) {
errorList += errorString
errorList += error
} else {
status.value = FavoritesSyncStatus.Error(errorString)
throw IgnoredException(errorString)
status.value = error
throw IgnoredException(error)
}
} else if (result is GalleryAddEvent.Success) {
insertedMangaCategories += categories[it.category].id to result.manga
@@ -438,59 +431,85 @@ class FavoritesSyncHelper(val context: Context) {
private fun needWarnThrottle() =
throttleManager.throttleTime >= THROTTLE_WARN
class IgnoredException(message: String) : RuntimeException(message)
class IgnoredException(message: FavoritesSyncStatus.SyncError.GallerySyncError) : RuntimeException(message.toString())
companion object {
private val THROTTLE_WARN = 1.seconds
}
}
sealed class FavoritesSyncStatus() {
abstract val message: String
@Serializable
sealed class FavoritesSyncStatus {
@Serializable
sealed class SyncError : FavoritesSyncStatus() {
@Serializable
data object NotLoggedInSyncError : SyncError()
data class Error(override val message: String) : FavoritesSyncStatus()
data class Idle(override val message: String) : FavoritesSyncStatus() {
constructor(context: Context) : this(context.stringResource(SYMR.strings.favorites_sync_waiting_for_start))
}
sealed class BadLibraryState : FavoritesSyncStatus() {
data class MangaInMultipleCategories(
val manga: Manga,
val categories: List<Category>,
override val message: String,
) : BadLibraryState() {
constructor(manga: Manga, categories: List<Category>, context: Context) :
this(
manga = manga,
categories = categories,
message = context.stringResource(
SYMR.strings.favorites_sync_gallery_in_multiple_categories, manga.title,
categories.joinToString {
it.name
},
),
)
@Serializable
data object FailedToFetchFavorites : SyncError()
@Serializable
data class UnknownSyncError(val message: String) : SyncError()
@Serializable
sealed class GallerySyncError : SyncError() {
@Serializable
data class UnableToAddGalleryToRemote(val title: String, val gid: String) : GallerySyncError()
@Serializable
data object UnableToDeleteFromRemote : GallerySyncError()
@Serializable
data class GalleryAddFail(val title: String, val reason: String) : GallerySyncError()
@Serializable
data class InvalidGalleryFail(val title: String, val url: String) : GallerySyncError()
}
}
data class Initializing(override val message: String) : FavoritesSyncStatus() {
constructor(context: Context) : this(context.stringResource(SYMR.strings.favorites_sync_initializing))
}
data class Processing(
override val message: String,
val title: String? = null,
) : FavoritesSyncStatus() {
constructor(message: String, isThrottle: Boolean, context: Context, title: String?) :
this(
if (isThrottle) {
context.stringResource(SYMR.strings.favorites_sync_processing_throttle, message)
} else {
message
},
title,
)
val delayedMessage get() = if (title != null) this.message + "\n\n" + title else null
@Serializable
data object Idle : FavoritesSyncStatus()
@Serializable
sealed class BadLibraryState : FavoritesSyncStatus() {
@Serializable
data class MangaInMultipleCategories(
val mangaId: Long,
val mangaTitle: String,
val categories: List<String>,
) : BadLibraryState()
}
data class CompleteWithErrors(val messages: List<String>) : FavoritesSyncStatus() {
override val message: String = messages.joinToString("\n")
@Serializable
data object Initializing : FavoritesSyncStatus()
@Serializable
sealed class Processing : FavoritesSyncStatus() {
data object VerifyingLibrary : Processing()
data object DownloadingFavorites : Processing()
data object CalculatingRemoteChanges : Processing()
data object CalculatingLocalChanges : Processing()
data object SyncingCategoryNames : Processing()
data class RemovingRemoteGalleries(val galleryCount: Int) : Processing()
data class AddingGalleryToRemote(
val index: Int,
val total: Int,
val isThrottling: Boolean,
val title: String,
) : Processing()
data class RemovingGalleryFromLocal(
val index: Int,
val total: Int,
) : Processing()
data class AddingGalleryToLocal(
val index: Int,
val total: Int,
val isThrottling: Boolean,
val title: String,
) : Processing()
data object CleaningUp : Processing()
}
@Serializable
data class CompleteWithErrors(val messages: List<SyncError.GallerySyncError>) : FavoritesSyncStatus()
}
@@ -10,7 +10,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -25,6 +24,7 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import exh.ui.ifSourcesLoaded
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.i18n.sy.SYMR
@@ -65,11 +65,9 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
SnackbarHost(hostState = snackbarHostState)
},
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@@ -128,6 +128,36 @@ class MangaHandler(
}
}
suspend fun getMangaMetadata(
track: Track,
sourceId: Long,
coverQuality: String,
tryUsingFirstVolumeCover: Boolean,
altTitlesInDesc: Boolean,
): SManga? {
return withIOContext {
val mangaId = MdUtil.getMangaId(track.tracking_url)
val response = service.viewManga(mangaId)
val coverFileName = if (tryUsingFirstVolumeCover) {
service.fetchFirstVolumeCover(response)
} else {
null
}
apiMangaParser.parseToManga(
SManga.create().apply {
url = track.tracking_url
},
sourceId,
response,
emptyList(),
null,
coverFileName,
coverQuality,
altTitlesInDesc,
)
}
}
private suspend fun getSimpleChapters(manga: SManga): List<String> {
return runCatching { service.aggregateChapters(MdUtil.getMangaId(manga.url), lang) }
.onFailure {
@@ -3,11 +3,8 @@ package exh.md.similar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -16,6 +13,7 @@ import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import exh.ui.ifSourcesLoaded
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.Scaffold
@@ -52,11 +50,9 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = false,
@@ -3,11 +3,8 @@ package exh.recs
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
@@ -17,6 +14,7 @@ import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
import exh.ui.ifSourcesLoaded
import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.LoadingScreen
@@ -51,11 +49,9 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
BrowseSourceContent(
source = screenModel.source,
mangaList = pagingFlow.collectAsLazyPagingItems(),
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY -->
ehentaiBrowseDisplayMode = false,
@@ -76,7 +76,7 @@ class BatchAddScreen : Screen() {
text = stringResource(SYMR.strings.eh_batch_add_description),
)
},
keyboardOptions = KeyboardOptions(autoCorrect = false),
keyboardOptions = KeyboardOptions(autoCorrectEnabled = false),
textStyle = MaterialTheme.typography.bodyLarge,
)
+26 -10
View File
@@ -130,24 +130,40 @@
android:layout_marginBottom="12dp" />
<Button
android:id="@+id/reset_tags"
android:id="@+id/autofill_from_tracker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/reset_tags"
android:text="@string/fill_from_tracker"
android:textAllCaps="false" />
<Button
android:id="@+id/reset_info"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/reset_info"
android:textAllCaps="false" />
android:orientation="horizontal">
<Button
android:id="@+id/reset_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/reset_tags"
android:textAllCaps="false" />
<Button
android:id="@+id/reset_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/reset_info"
android:textAllCaps="false" />
</LinearLayout>
<View
android:layout_width="match_parent"
@@ -64,6 +64,7 @@ object DeviceUtil {
val invalidDefaultBrowsers = listOf(
"android",
"com.hihonor.android.internal.app",
"com.huawei.android.internal.app",
"com.zui.resolver",
)
@@ -6,7 +6,7 @@ import javax.microedition.khronos.egl.EGLContext
import kotlin.math.max
object GLUtil {
val maxTextureSize: Int by lazy {
val DEVICE_TEXTURE_LIMIT: Int by lazy {
// Get EGL Display
val egl = EGLContext.getEGL() as EGL10
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
@@ -38,10 +38,23 @@ object GLUtil {
// Release
egl.eglTerminate(display)
// Return largest texture size found, or default
max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION)
// Return largest texture size found (after making it a multiplier of [Multiplier]), or default
max(maximumTextureSize, SAFE_TEXTURE_LIMIT)
}
const val SAFE_TEXTURE_LIMIT: Int = 2048
val CUSTOM_TEXTURE_LIMIT_OPTIONS: List<Int> by lazy {
val steps = DEVICE_TEXTURE_LIMIT / MULTIPLIER
buildList(steps) {
add(DEVICE_TEXTURE_LIMIT)
for (step in steps downTo 2) {
val value = step * MULTIPLIER
if (value >= DEVICE_TEXTURE_LIMIT) continue
add(value)
}
}
}
}
// Safe minimum default size
private const val IMAGE_MAX_BITMAP_DIMENSION = 2048
private const val MULTIPLIER: Int = 1024
@@ -360,9 +360,21 @@ object ImageUtil {
val bottomOffset = topOffset + splitHeight
}
fun canUseCoilToDecode(imageSource: BufferedSource): Boolean {
val options = extractImageOptions(imageSource)
return maxOf(options.outWidth, options.outHeight) <= GLUtil.maxTextureSize
fun canUseHardwareBitmap(bitmap: Bitmap): Boolean {
return canUseHardwareBitmap(bitmap.width, bitmap.height)
}
fun canUseHardwareBitmap(imageSource: BufferedSource): Boolean {
return with(extractImageOptions(imageSource)) {
canUseHardwareBitmap(outWidth, outHeight)
}
}
var hardwareBitmapThreshold: Int = GLUtil.SAFE_TEXTURE_LIMIT
private fun canUseHardwareBitmap(width: Int, height: Int): Boolean {
if (HARDWARE_BITMAP_UNSUPPORTED) return false
return maxOf(width, height) <= hardwareBitmapThreshold
}
/**
@@ -629,6 +641,121 @@ object ImageUtil {
private val optimalImageHeight = getDisplayMaxHeightInPx * 2
/**
* Taken from Coil
* (https://github.com/coil-kt/coil/blob/1674d3516f061aeacbe749a435b1924f9648fd41/coil-core/src/androidMain/kotlin/coil3/util/hardwareBitmaps.kt)
* ---
* Maintains a list of devices with broken/incomplete/unstable hardware bitmap implementations.
*
* Model names are retrieved from
* [Google's official device list](https://support.google.com/googleplay/answer/1727131?hl=en).
*
*/
val HARDWARE_BITMAP_UNSUPPORTED = when (Build.VERSION.SDK_INT) {
26 -> run {
val model = Build.MODEL ?: return@run false
// Samsung Galaxy (ALL)
if (model.removePrefix("SAMSUNG-").startsWith("SM-")) return@run true
val device = Build.DEVICE ?: return@run false
return@run device in arrayOf(
"nora", "nora_8917", "nora_8917_n", // Moto E5
"james", "rjames_f", "rjames_go", "pettyl", // Moto E5 Play
"hannah", "ahannah", "rhannah", // Moto E5 Plus
"ali", "ali_n", // Moto G6
"aljeter", "aljeter_n", "jeter", // Moto G6 Play
"evert", "evert_n", "evert_nt", // Moto G6 Plus
"G3112", "G3116", "G3121", "G3123", "G3125", // Xperia XA1
"G3412", "G3416", "G3421", "G3423", "G3426", // Xperia XA1 Plus
"G3212", "G3221", "G3223", "G3226", // Xperia XA1 Ultra
"BV6800Pro", // BlackView BV6800Pro
"CatS41", // Cat S41
"Hi9Pro", // CHUWI Hi9 Pro
"manning", // Lenovo K8 Note
"N5702L", // NUU Mobile G3
)
}
27 -> run {
val device = Build.DEVICE ?: return@run false
return@run device in arrayOf(
"mcv1s", // LG Tribute Empire
"mcv3", // LG K11
"mcv5a", // LG Q7
"mcv7a", // LG Stylo 4
"A30ATMO", // T-Mobile REVVL 2
"A70AXLTMO", // T-Mobile REVVL 2 PLUS
"A3A_8_4G_TMO", // Alcatel 9027W
"Edison_CKT", // Alcatel ONYX
"EDISON_TF", // Alcatel TCL XL2
"FERMI_TF", // Alcatel A501DL
"U50A_ATT", // Alcatel TETRA
"U50A_PLUS_ATT", // Alcatel 5059R
"U50A_PLUS_TF", // Alcatel TCL LX
"U50APLUSTMO", // Alcatel 5059Z
"U5A_PLUS_4G", // Alcatel 1X
"RCT6513W87DK5e", // RCA Galileo Pro
"RCT6873W42BMF9A", // RCA Voyager
"RCT6A03W13", // RCA 10 Viking
"RCT6B03W12", // RCA Atlas 10 Pro
"RCT6B03W13", // RCA Atlas 10 Pro+
"RCT6T06E13", // RCA Artemis 10
"A3_Pro", // Umidigi A3 Pro
"One", // Umidigi One
"One_Max", // Umidigi One Max
"One_Pro", // Umidigi One Pro
"Z2", // Umidigi Z2
"Z2_PRO", // Umidigi Z2 Pro
"Armor_3", // Ulefone Armor 3
"Armor_6", // Ulefone Armor 6
"Blackview", // Blackview BV6000
"BV9500", // Blackview BV9500
"BV9500Pro", // Blackview BV9500Pro
"A6L-C", // Nuu A6L-C
"N5002LA", // Nuu A7L
"N5501LA", // Nuu A5L
"Power_2_Pro", // Leagoo Power 2 Pro
"Power_5", // Leagoo Power 5
"Z9", // Leagoo Z9
"V0310WW", // Blu VIVO VI+
"V0330WW", // Blu VIVO XI
"A3", // BenQ A3
"ASUS_X018_4", // Asus ZenFone Max Plus M1 (ZB570TL)
"C210AE", // Wiko Life
"fireball", // DROID Incredible 4G LTE
"ILA_X1", // iLA X1
"Infinix-X605_sprout", // Infinix NOTE 5 Stylus
"j7maxlte", // Samsung Galaxy J7 Max
"KING_KONG_3", // Cubot King Kong 3
"M10500", // Packard Bell M10500
"S70", // Altice ALTICE S70
"S80Lite", // Doogee S80Lite
"SGINO6", // SGiNO 6
"st18c10bnn", // Barnes and Noble BNTV650
"TECNO-CA8", // Tecno CAMON X Pro,
"SHIFT6m", // SHIFT 6m
)
}
else -> false
}
// SY -->
fun mergeBitmaps(
imageBitmap: Bitmap,
+5 -5
View File
@@ -1,7 +1,7 @@
[versions]
agp_version = "8.7.1"
agp_version = "8.7.2"
lifecycle_version = "2.8.7"
paging_version = "3.3.2"
paging_version = "3.3.4"
interpolator_version = "1.0.0"
[libraries]
@@ -11,17 +11,17 @@ annotation = "androidx.annotation:annotation:1.9.1"
appcompat = "androidx.appcompat:appcompat:1.7.0"
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.0"
corektx = "androidx.core:core-ktx:1.13.1"
corektx = "androidx.core:core-ktx:1.15.0"
splashscreen = "androidx.core:core-splashscreen:1.0.1"
recyclerview = "androidx.recyclerview:recyclerview:1.3.2"
viewpager = "androidx.viewpager:viewpager:1.1.0-beta01"
viewpager = "androidx.viewpager:viewpager:1.1.0-rc01"
profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1"
lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" }
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" }
lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" }
workmanager = "androidx.work:work-runtime:2.9.1"
workmanager = "androidx.work:work-runtime:2.10.0"
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
compose-bom = "2024.10.00"
compose-bom = "2024.10.01"
[libraries]
activity = "androidx.activity:activity-compose:1.9.3"
+1 -1
View File
@@ -1,7 +1,7 @@
[versions]
kotlin_version = "2.0.21"
serialization_version = "1.7.3"
xml_serialization_version = "0.90.2"
xml_serialization_version = "0.90.3"
[libraries]
reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" }
+5 -5
View File
@@ -9,11 +9,11 @@ sqldelight = "2.0.2"
sqlite = "2.4.0"
voyager = "1.0.0"
spotless = "7.0.0.BETA4"
ktlint-core = "1.4.0"
ktlint-core = "1.4.1"
firebase-bom = "33.5.1"
[libraries]
desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
desugar = "com.android.tools:desugar_jdk_libs:2.1.3"
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"
rxjava = "io.reactivex:rxjava:1.3.8"
@@ -28,7 +28,7 @@ conscrypt-android = "org.conscrypt:conscrypt-android:2.5.3"
quickjs-android = "app.cash.quickjs:quickjs-android:0.9.2"
jsoup = "org.jsoup:jsoup:1.18.1"
jsoup = "org.jsoup:jsoup:1.18.2"
disklrucache = "com.jakewharton:disklrucache:2.0.2"
unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc"
@@ -42,13 +42,13 @@ preferencektx = "androidx.preference:preference-ktx:1.2.1"
injekt-core = "com.github.null2264:injekt-koin:ee267b2e27"
coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.0-rc02" }
coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.4" }
coil-core = { module = "io.coil-kt.coil3:coil" }
coil-gif = { module = "io.coil-kt.coil3:coil-gif" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" }
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:b8e1b0ed2b"
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:66e0db195d"
image-decoder = "com.github.tachiyomiorg:image-decoder:41c059e540"
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="num_lock_times">
<item quantity="one">%d তালাবন্ধ সময়</item>
<item quantity="other">%d তালাবন্ধ সময়সমূহ</item>
</plurals>
<plurals name="migrate_entry">
<item quantity="one">%1$d%2$s প্ৰৱেশ প্ৰব্ৰজন কৰা হব নেকি?</item>
<item quantity="other">%1$d%2$s প্ৰবিষ্টসমূহ প্ৰব্ৰজন কৰা হব নেকি?</item>
</plurals>
<plurals name="copy_entry">
<item quantity="one">%1$d%2$s প্ৰৱেশ কপি কৰিবনে?</item>
<item quantity="other">%1$d%2$s প্ৰৱেশসমূহ কপি কৰিবনে?</item>
</plurals>
<plurals name="entry_migrated">
<item quantity="one">%d প্ৰৱেশ স্থানান্তৰিত হৈছে</item>
<item quantity="other">%d প্ৰৱেশসমূহ স্থানান্তৰিত হৈছে</item>
</plurals>
<plurals name="num_pages">
<item quantity="one">%1$d পৃষ্ঠা</item>
<item quantity="other">%1$d পৃষ্ঠাসমূহ</item>
</plurals>
<plurals name="browse_language_and_pages">
<item quantity="one">%2$s, %1$d পৃষ্ঠা</item>
<item quantity="other">%2$s, %1$d পৃষ্ঠাসমূহ</item>
</plurals>
<plurals name="humanize_year">
<item quantity="one">%1$d বৰ্ষ পূৰ্বে</item>
<item quantity="other">%1$d বৰ্ষৰ পূৰ্বে</item>
</plurals>
<plurals name="humanize_month">
<item quantity="one">%1$d মাহ পূৰ্বে</item>
<item quantity="other">%1$d মাহৰ পূৰ্বে</item>
</plurals>
<plurals name="humanize_week">
<item quantity="one">%1$d সপ্তাহ পূৰ্বে</item>
<item quantity="other">%1$d সপ্তাহৰ পূৰ্বে</item>
</plurals>
<plurals name="humanize_day">
<item quantity="one">%1$d দিন পূৰ্বে</item>
<item quantity="other">%1$d দিনৰ পূৰ্বে</item>
</plurals>
<plurals name="humanize_hour">
<item quantity="one">%1$d ঘণ্টা পূৰ্বে</item>
<item quantity="other">%1$d ঘণ্টাৰ পূৰ্বে</item>
</plurals>
<plurals name="humanize_minute">
<item quantity="one">%1$d মিনিট পূৰ্বে</item>
<item quantity="other">%1$d মিনিটৰ পূৰ্বে</item>
</plurals>
<plurals name="humanize_second">
<item quantity="one">%1$d ছেকেণ্ড পূৰ্বে</item>
<item quantity="other">%1$d ছেকেণ্ডৰ পূৰ্বে</item>
</plurals>
<plurals name="row_count">
<item quantity="one">%d পংক্তি</item>
<item quantity="other">%d পংক্তিসমূহ</item>
</plurals>
<plurals name="cleanup_done">
<item quantity="one">চাফাই কাম কৰা হ’ল। %d ফোল্ডাৰ আঁতৰোৱা হৈছে</item>
<item quantity="other">চাফাই কাম কৰা হ’ল। %d ফোল্ডাৰসমূহ আঁতৰোৱা হৈছে</item>
</plurals>
<plurals name="eh_retry_toast">
<item quantity="one">পুনৰ চেষ্টা কৰিছে %1$d বিফল পৃষ্ঠা…</item>
<item quantity="other">পুনৰ চেষ্টা কৰিছে %1$d বিফল পৃষ্ঠাসমূহ…</item>
</plurals>
<plurals name="pref_tag_sorting_desc">
<item quantity="one">%1$d টা টেগ শৃংখলাৰ সূচীত আছে। ই গ্ৰন্থাগাৰত এটা বিকল্প যোগ কৰে যাৰ সহায়ত অগ্ৰাধিকাৰ ভিত্তিত টেগ অনুসৰি শৃংখলা কৰিব পৰা যায়, অৰ্থাৎ আপুনি বিচৰা টেগৰ প্ৰাধান্য থকা বস্তুবোৰ সৰ্বপ্রথমে থাকিব।</item>
<item quantity="other">%1$d টেগ শৃংখলাৰ সূচীত আছে। ই গ্ৰন্থাগাৰত এটা বিকল্প যোগ কৰে যাৰ সহায়ত অগ্ৰাধিকাৰ ভিত্তিত টেগ অনুসৰি শৃংখলা কৰিব পৰা যায়, অৰ্থাৎ আপুনি বিচৰা টেগৰ প্ৰাধান্য থকা বস্তুবোৰ সৰ্বপ্রথমে থাকিব।</item>
</plurals>
</resources>
@@ -129,16 +129,7 @@
<string name="gallery_updater_statistics">গ্যালাৰী আপডেটাৰ পৰিসংখ্যা</string>
<string name="gallery_updater_not_ran_yet">আপডেটাৰ এতিয়াও চলোৱা হোৱা নাই।</string>
<string name="gallery_updater_stats_text">আপডেটাৰ সৰ্বশেষ %1$s ত চলিছিল, আৰু %2$d গ্যালাৰীৰ ভিতৰত %3$d গ্যালাৰী পৰীক্ষা কৰিছিল যি পৰীক্ষাৰ বাবে প্রস্তুত আছিল।</string>
<string name="gallery_updater_stats_time">"
\nসর্বশেষ পৰীক্ষিত গ্যালাৰীৰ সংখ্যা:
\n- ১ ঘণ্টা: %1$d
\n- ৬ ঘণ্টা: %2$d
\n- ১২ ঘণ্টা: %3$d
\n- দিন: %4$d
\n- ২ দিন: %5$d
\n- সপ্তাহ: %6$d
\n- মাহ: %7$d
\n- বছৰ: %8$d"</string>
<string name="gallery_updater_stats_time">\nশেষত পৰীক্ষা কৰা গেলেৰীসমূহ:\n- ঘন্টা: %1$d\n- ৬ ঘন্টা: %2$d\n- ১২ ঘন্টা: %3$d\n- দিন: %4$d\n- ২ দিন: %5$d\n- সপ্তাহ: %6$d\n- মাহ: %7$d\n- বছৰ: %8$d</string>
<string name="settings_profile_note">ছেটিংছ প্ৰফাইল নোট</string>
<string name="settings_profile_note_message">এপ্পে এতিয়া E-Hentai আৰু ExHentai ত নতুন ছেটিংছ প্ৰফাইল যোগ কৰিব যাতে এপ্পৰ কাৰ্যক্ষমতা উন্নত হয়। অনুগ্ৰহ কৰি নিশ্চিত কৰক যে আপোনাৰ দুইটা স্থানতে তিনিটা প্ৰফাইলৰ পৰা কম আছে।
\n
@@ -164,7 +155,7 @@
<string name="enable_source_blacklist_summary">%1$s সँग অনুপযোগী এক্সটেনচনসমূহ/উৎসসমূহ লুকোৱা। পৰিবৰ্তনৰ পিছত এপ্প পুনৰ আৰম্ভ কৰক।</string>
<string name="open_debug_menu">ডিবাগ মেনু খোলক</string>
<string name="clean_up_downloaded_chapters">ডাউনলোড কৰা অধ্যায়সমূহ পৰিস্কাৰ কৰক</string>
<string name="open_debug_menu_summary">এই মেনুটি স্পর্শ নকৰিব যদি আপুনি কি কৰি আছে সেয়া জানেনে! <font color="red">এটি আপোনাৰ লাইব্ৰেৰী ক্ৰিয়া কৰিব পাৰে!</font></string>
<string name="open_debug_menu_summary"><![CDATA[এই মেনুটি স্পর্শ নকৰিব যদি আপুনি কি কৰি আছে সেয়া জানেনে! <font color=\'red\'>এটি আপোনাৰ লাইব্ৰেৰী ক্ৰিয়া কৰিব পাৰে!</font>]]></string>
<string name="delete_unused_chapters">অস্থিত্বৰ, আংশিকভাৱে ডাউনলোড কৰা, আৰু পঢ়া অধ্যায়ৰ ফোল্ডাৰ মচি পেলাওক</string>
<string name="no_folders_to_cleanup">পৰিস্কাৰ কৰিবলৈ কোনো ফোল্ডাৰ নাই</string>
<string name="clean_orphaned_downloads">অৰ্পিত ডাউনলোডসমূহ পৰিস্কাৰ কৰক</string>
@@ -495,10 +486,7 @@
<string name="batch_add">বেটছ যোগ</string>
<string name="batch_add_ok">[ঠিক]</string>
<string name="batch_add_error">[ভুল]</string>
<string name="batch_add_summary">"
\nসাৰাংশ:
\nযোগ কৰা হৈছে: %1$d গেছবুক
\nব্যৰ্থ: %2$d গেছবুক"</string>
<string name="batch_add_summary">\nসাৰাংশ:\nযোগ কৰা হৈছে: %1$d গেলেৰী(সমূহ)\nব্যৰ্থ: %2$d গেলেৰী(সমূহ)</string>
<string name="batch_add_success_log_message">যোগ কৰা গেছবুক: %1$s</string>
<string name="batch_add_unknown_type_log_message">গেছবুকৰ বাবে অজানা প্ৰৱিষ্ট প্ৰকাৰ: %1$s</string>
<string name="batch_add_unknown_source_log_message">গেছবুকৰ বাবে অজানা উৎস: %1$s</string>
@@ -403,6 +403,9 @@
<string name="artist_hint">Artist: %1$s</string>
<string name="thumbnail_url_hint">Thumbnail Url: %1$s</string>
<string name="multi_tags_comma_separated">Enter tag(s), seperated by commas.</string>
<string name="select_tracker">Select a tracker</string>
<string name="entry_not_tracked">Entry is not tracked.</string>
<string name="fill_from_tracker">Fill from tracker</string>
<!-- Browse -->
<!-- Sources Tab -->
@@ -515,6 +518,7 @@
<string name="favorites_sync_removing_galleries">Removing %1$d galleries from remote server</string>
<string name="favorites_sync_unable_to_delete">Unable to delete galleries from the remote servers!</string>
<string name="favorites_sync_adding_to_remote">Adding gallery %1$d of %2$d to remote server</string>
<string name="favorites_sync_unable_to_add_to_remote">Unable to add gallery to remote server: '%1$s' (GID: %2$s)!</string>
<string name="favorites_sync_remove_from_local">Removing gallery %1$d of %2$d from local library</string>
<string name="favorites_sync_add_to_local">Adding gallery %1$d of %2$d to local library</string>
<string name="favorites_sync_remote_not_exist">Remote gallery does not exist, skipping: %1$s!</string>
@@ -60,7 +60,7 @@
<string name="tag_watching_threshhold">Umbral de Monitoreo de Etiquetas</string>
<string name="tag_watching_threshhold_summary">Las galerías recientemente subidas se incluirán en la pantalla de observación si tienen al menos una etiqueta observada con peso positivo, y la suma de los pesos de sus etiquetas observadas alcanza este valor o es mayor. Este umbral se puede establecer entre 0 y 9999. Actualmente: %1$d</string>
<string name="watched_list_default">Estado Predeterminado del Filtro de la Lista Observada</string>
<string name="watched_list_state_summary">Al navegar por ExHentai/E-Hentai, ¿debería estar activado por defecto el filtro de la lista vigilada?</string>
<string name="watched_list_state_summary">Al navegar en ExHentai/E-Hentai, ¿debería estar habilitado el filtro de la lista de seguimiento de forma predeterminada?</string>
<string name="pref_enhanced_e_hentai_view_summary">Habilitar/Deshabilitar el menú de navegación mejorado hecho para E/ExHentai</string>
<string name="favorites_sync">Sincronización de Favoritos de E-Hentai</string>
<string name="tag_filtering_threshhold_summary">Puedes filtrar suavemente las etiquetas añadiéndolas a la página Mis Etiquetas de E/ExHentai con un peso negativo. Si una galería tiene etiquetas que suman un peso inferior a este valor, se filtra de la vista. Este umbral se puede establecer entre -9999 y 0. Actualmente: %1$d</string>
@@ -268,7 +268,7 @@
<string name="friday">Biyernes</string>
<string name="encrypt_database">I-encrypt ang database</string>
<string name="encrypt_database_subtitle">Kinakailangang mag-restart ng app upang magkabisa</string>
<string name="encrypt_database_message">&lt;font color=\'red\'&gt;ANG PAGPAPAGANA NITO AY GAGAWA NG BAGONG DATABASE. GAMITIN ANG MGA HAKBANG NA ITO UPANG MAPANATILI ANG IYONG DATA&lt;br&gt;1. MGA SETTING -&gt; BACKUP -&gt; GUMAWA&lt;br&gt;2. MGA SETTING NG SISTEMA -&gt; I-CLEAR ANG APP DATA&lt;br&gt;3. BUKSAN ANG APP AT PAGANAHIN ITO&lt;br&gt;4. MGA SETTING NG SISTEMA -&gt; PILITING MAG-RESTART&lt;br&gt;5. MGA SETTING -&gt; BACKUP -&gt; I-RESTORE&lt;/font&gt;</string>
<string name="encrypt_database_message"><![CDATA[<font color='red'>ANG PAGPAPAGANA NITO AY GAGAWA NG BAGONG DATABASE. GAMITIN ANG MGA HAKBANG NA ITO UPANG MAPANATILI ANG IYONG DATA<br>1. MGA SETTING -> BACKUP -> GUMAWA<br>2. MGA SETTING NG SISTEMA -> I-CLEAR ANG APP DATA<br>3. BUKSAN ANG APP AT PAGANAHIN ITO<br>4. MGA SETTING NG SISTEMA -> PILITING MAG-RESTART<br>5. MGA SETTING -> BACKUP -> I-RESTORE</font>]]></string>
<string name="set_cbz_zip_password">I-set ang archive password ng CBZ</string>
<string name="password_protect_downloads">Protektahan ng password ang mga download</string>
<string name="delete_cbz_archive_password">Alisin ang password ng CBZ archive</string>
@@ -498,9 +498,9 @@
<string name="community_recommendations">Mga rekomendasyon ng komunidad</string>
<string name="select_scanlators">Ipapakitang Scanlator group</string>
<string name="similar">Katulad sa %1$s</string>
<string name="relation_similar">Katulad</string>
<string name="relation_similar">Katulad nito</string>
<string name="humanize_fallback">ilang sandali ang nakalipas</string>
<string name="favorites_sync_notes_message">1. Ang mga pagbabago sa pangalan ng kategorya sa app ay &lt;b&gt;HINDI&lt;/b&gt; nai-sync! Mangyaring &lt;i&gt;palitan ang pangalan ng kategorya sa ExHentai sa halip&lt;/i&gt;. Ang mga pangalan ng kategorya ay kokopyahin mula sa ExHentai servers sa bawat pag-sync.&lt;br&gt;&lt;br&gt;2. Ang mga paboritong kategorya sa ExHentai ay tumutukoy sa &lt;b&gt;unang 10 kategorya sa app&lt;/b&gt; (hindi kasama ang \'Default\' na kategorya). &lt;i&gt;Ang mga gallery sa ibang mga kategorya ay &lt;b&gt;HINDI&lt;/b&gt; nai-sync!&lt;/i&gt;&lt;br&gt;&lt;br&gt;3. &lt;font color=\'red\'&gt;&lt;b&gt;PANATALIHING MAY MALAKAS NA KONEKSYON NG INTERNET KAPAG NAGPRPROSESO NG PAG-SYNC!&lt;/b&gt;&lt;/font&gt; Kung mawawalan ng koneksyon sa internet habang nag-sync ang app, ang iyong mga paborito ay maaaring maiwan sa isang &lt;i&gt;bahagyang nai-sync na estado&lt;/i&gt;.&lt;br&gt;&lt;br&gt;4. Panatilihing bukas ang app habang nag-sync ang mga paborito. Ang Android ay minsang nagsasara ng mga app na nasa background at maaaring hindi maganda kung mangyari ito habang nag-sisync ang app.&lt;br&gt;&lt;br&gt;5. &lt;b&gt;Huwag ilagay ang mga paborito sa maraming kategorya&lt;/b&gt; (suportado ito ng app). Maaari malilito ang algorithm ng pag-sync dahil pinapayagan lang ng ExH na ang bawat paborito ay nasa isang kategorya.&lt;br&gt;&lt;br&gt; Ang dialog na ito ay lilitaw lamang ng isang beses. Maaari mong basahin muli ang mga tala na ito sa pamamagitan ng pagpunta sa \'Mga Setting &gt; E-Hentai &gt; Ipakita ang sync note ng mga paborito\'.</string>
<string name="favorites_sync_notes_message"><![CDATA[1. Ang mga pagbabago sa pangalan ng kategorya sa app ay <b>HINDI</b> nai-sync! Mangyaring <i>palitan ang pangalan ng kategorya sa ExHentai sa halip</i>. Ang mga pangalan ng kategorya ay kokopyahin mula sa ExHentai servers sa bawat pag-sync.<br><br>2. Ang mga paboritong kategorya sa ExHentai ay tumutukoy sa <b>unang 10 kategorya sa app</b> (hindi kasama ang 'Default' na kategorya). <i>Ang mga gallery sa ibang mga kategorya ay <b>HINDI</b> nai-sync!</i><br><br>3. <font color='red'><b>PANATALIHING MAY MALAKAS NA KONEKSYON NG INTERNET KAPAG NAGPRPROSESO NG PAG-SYNC!</b></font> Kung mawawalan ng koneksyon sa internet habang nag-sync ang app, ang iyong mga paborito ay maaaring maiwan sa isang <i>bahagyang nai-sync na estado</i>.<br><br>4. Panatilihing bukas ang app habang nag-sync ang mga paborito. Ang Android ay minsang nagsasara ng mga app na nasa background at maaaring hindi maganda kung mangyari ito habang nag-sisync ang app.<br><br>5. <b>Huwag ilagay ang mga paborito sa maraming kategorya</b> (suportado ito ng app). Maaari malilito ang algorithm ng pag-sync dahil pinapayagan lang ng ExH na ang bawat paborito ay nasa isang kategorya.<br><br> Ang dialog na ito ay lilitaw lamang ng isang beses. Maaari mong basahin muli ang mga tala na ito sa pamamagitan ng pagpunta sa 'Mga Setting > E-Hentai > Ipakita ang sync note ng mga paborito'.]]></string>
<string name="batch_add_unknown_type_log_message">Di-kilalang uri ng entry para sa gallery: %1$s</string>
<string name="batch_add_summary">"
\nBuod:
@@ -1,2 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<plurals name="num_pages">
<item quantity="one">%1$d stranica</item>
<item quantity="few">%1$d stranice</item>
<item quantity="other">%1$d stranica</item>
</plurals>
<plurals name="browse_language_and_pages">
<item quantity="one">%2$s, %1$d stranica</item>
<item quantity="few">%2$s, %1$d stranice</item>
<item quantity="other">%2$s, %1$d stranica</item>
</plurals>
<plurals name="cleanup_done">
<item quantity="one">Čišćenje je gotovo. Uklonjena je %d mapa</item>
<item quantity="few">Čišćenje je gotovo. Uklonjene su %d mape</item>
<item quantity="other">Čišćenje je gotovo. Uklonjeno je %d mapa</item>
</plurals>
<plurals name="num_lock_times">
<item quantity="one">%d vrijeme zaključavanja</item>
<item quantity="few">%d vremena zaključavanja</item>
<item quantity="other">%d vremena zaključavanja</item>
</plurals>
<plurals name="migrate_entry">
<item quantity="one">Premjestiti %1$d%2$s unos?</item>
<item quantity="few">Premjestiti %1$d%2$s unosa?</item>
<item quantity="other">Premjestiti %1$d%2$s unosa?</item>
</plurals>
<plurals name="eh_retry_toast">
<item quantity="one">Ponovni pokušaj %1$d neuspjele stranice…</item>
<item quantity="few">Ponovni pokušaj %1$d neuspjele stranice…</item>
<item quantity="other">Ponovni pokušaj %1$d neuspjelih stranica…</item>
</plurals>
<plurals name="pref_tag_sorting_desc">
<item quantity="one">%1$d oznaka na popisu za razvrstavanje. Ovo dodaje opciju u biblioteci za razvrstavanje prema popisu oznaka temeljenom na prioritetu, što znači da će se unosi razvrstati na način da daju prioritet onima s oznakama koje želiš</item>
<item quantity="few">%1$d oznake na popisu za razvrstavanje. Ovo dodaje opciju u biblioteci za razvrstavanje prema popisu oznaka temeljenom na prioritetu, što znači da će se unosi razvrstati na način da daju prioritet onima s oznakama koje želiš</item>
<item quantity="other">%1$d oznaka na popisu za razvrstavanje. Ovo dodaje opciju u biblioteci za razvrstavanje prema popisu oznaka temeljenom na prioritetu, što znači da će se unosi razvrstati na način da daju prioritet onima s oznakama koje želiš</item>
</plurals>
<plurals name="copy_entry">
<item quantity="one">Kopirati %1$d%2$s unos?</item>
<item quantity="few">Kopirati %1$d%2$s unosa?</item>
<item quantity="other">Kopirati %1$d%2$s unosa?</item>
</plurals>
<plurals name="entry_migrated">
<item quantity="one">%d unos je premješten</item>
<item quantity="few">%d unosa su premještena</item>
<item quantity="other">%d unosa je premješteno</item>
</plurals>
<plurals name="humanize_day">
<item quantity="one">Prije %1$d dan</item>
<item quantity="few">Prije %1$d dana</item>
<item quantity="other">Prije %1$d dana</item>
</plurals>
<plurals name="humanize_hour">
<item quantity="one">Prije %1$d sat</item>
<item quantity="few">Prije %1$d sata</item>
<item quantity="other">Prije %1$d sati</item>
</plurals>
<plurals name="humanize_minute">
<item quantity="one">Prije %1$d minute</item>
<item quantity="few">Prije %1$d minute</item>
<item quantity="other">Prije %1$d minuta</item>
</plurals>
<plurals name="humanize_second">
<item quantity="one">Prije %1$d sekunde</item>
<item quantity="few">Prije %1$d sekunde</item>
<item quantity="other">Prije %1$d sekundi</item>
</plurals>
<plurals name="row_count">
<item quantity="one">%d redak</item>
<item quantity="few">%d retka</item>
<item quantity="other">%d redaka</item>
</plurals>
<plurals name="humanize_year">
<item quantity="one">Prije %1$d godine</item>
<item quantity="few">Prije %1$d godine</item>
<item quantity="other">Prije %1$d godina</item>
</plurals>
<plurals name="humanize_month">
<item quantity="one">Prije %1$d mjesec</item>
<item quantity="few">Prije %1$d mjeseca</item>
<item quantity="other">Prije %1$d mjeseci</item>
</plurals>
<plurals name="humanize_week">
<item quantity="one">Prije %1$d tjedan</item>
<item quantity="few">Prije %1$d tjedna</item>
<item quantity="other">Prije %1$d tjedna</item>
</plurals>
</resources>
@@ -51,4 +51,6 @@
<string name="time_between_batches_24_hours">24 sati</string>
<string name="time_between_batches_48_hours">48 sati</string>
<string name="gallery_updater_statistics_collection">Sakupljanje statisktike …</string>
<string name="all_read_entries">Svi pročitani unosi</string>
<string name="random">Nasumce</string>
</resources>
@@ -2,8 +2,8 @@
<resources>
<!-- Actions -->
<string name="action_skip_entry">Jangan pindahkan</string>
<string name="action_search_manually">Cari manual</string>
<string name="action_migrate_now">Pindahkan</string>
<string name="action_search_manually">Cari secara manual</string>
<string name="action_migrate_now">Pindahkan sekarang</string>
<string name="action_copy_now">Salin</string>
<string name="action_clean_titles">Hapus judul</string>
<string name="action_start_reading">Mulai baca</string>
@@ -33,17 +33,17 @@
<string name="use_original_images">Use original images</string>
<string name="use_original_images_on">Currently using original images</string>
<string name="use_original_images_off">Currently using resampled images</string>
<string name="watched_tags">Watched Tags</string>
<string name="watched_tags_summary">Opens a webview to your E/ExHentai watched tags page</string>
<string name="watched_tags_exh">ExHentai Watched Tags</string>
<string name="tag_filtering_threshold">Tag Filtering Threshold</string>
<string name="tag_filtering_threshhold_error">Must be between -9999 and 0!</string>
<string name="tag_filtering_threshhold_summary">You can soft filter tags by adding them to the "My Tags" E/ExHentai page with a negative weight. If a gallery has tags that add up to weight below this value, it is filtered from view. This threshold can be set between -9999 and 0. Currently: %1$d</string>
<string name="tag_watching_threshhold">Tag Watching Threshold</string>
<string name="tag_watching_threshhold_error">Must be between 0 and 9999!</string>
<string name="tag_watching_threshhold_summary">Recently uploaded galleries will be included on the watched screen if it has at least one watched tag with positive weight, and the sum of weights on its watched tags add up to this value or higher. This threshold can be set between 0 and 9999. Currently: %1$d</string>
<string name="watched_tags">Tag yang telah dibaca</string>
<string name="watched_tags_summary">Membuka tampilan web ke halaman tag yang telah Anda tonton di E/ExHentai</string>
<string name="watched_tags_exh">Tag yang Telah Ditonton di ExHentai</string>
<string name="tag_filtering_threshold">Batasan Pemfilteran Tag</string>
<string name="tag_filtering_threshhold_error">Harus diantara -9999 dan 0!</string>
<string name="tag_filtering_threshhold_summary">Anda dapat memfilter tag secara halus dengan menambahkannya ke halaman My Tags E/ExHentai dengan bobot negatif. Jika sebuah galeri memiliki tag yang jumlah bobotnya di bawah nilai ini, galeri tersebut akan disaring dari tampilan. Batasan ini dapat diatur antara -9999 dan 0. Saat ini: %1$d</string>
<string name="tag_watching_threshhold">Batasan Pengawasan Tag</string>
<string name="tag_watching_threshhold_error">Harus diantara 0 dan 9999!</string>
<string name="tag_watching_threshhold_summary">Galeri yang baru diunggah akan ditampilkan di layar yang dilihat jika memiliki setidaknya satu tag yang dilihat dengan bobot positif, dan jumlah bobot pada tag yang dilihat mencapai nilai ini atau lebih tinggi. Batasan ini dapat diatur antara 0 dan 9999. Saat ini: %1$d</string>
<string name="language_filtering">Language Filtering</string>
<string name="language_filtering_summary">If you wish to hide galleries in certain languages from the gallery list and searches, select them in the dialog that will popup.\nNote that matching galleries will never appear regardless of your search query.\nTldr checkmarked = exclude</string>
<string name="language_filtering_summary">Jika Anda ingin menyembunyikan galeri dalam bahasa tertentu dari daftar galeri dan pencarian, pilih bahasa-bahasa tersebut dalam dialog yang akan muncul.\nPerhatikan bahwa galeri itu tidak akan pernah muncul terlepas dari kueri pencarian Anda.\nTL;DR: Centang = kecualikan.</string>
<string name="frong_page_categories">Front Page Categories</string>
<string name="fromt_page_categories_summary">What categories would you like to show by default on the front page and in searches? They can still be enabled by enabling their filters</string>
<string name="watched_list_default">Watched List Filter Default State</string>
@@ -65,22 +65,22 @@
<string name="show_favorite_sync_notes_summary">Show some information regarding the favorites sync feature</string>
<string name="please_login">Silahkan login!</string>
<string name="ignore_sync_errors">Ignore sync errors when possible</string>
<string name="ignore_sync_errors_summary">Do not abort immediately when encountering errors during the sync process. Errors will still be displayed when the sync is complete. Can cause loss of favorites in some cases. Useful when syncing large libraries.</string>
<string name="force_sync_state_reset">Force sync state reset</string>
<string name="force_sync_state_reset_summary">Performs a full resynchronization on the next sync. Removals will not be synced. All favorites in the app will be re-uploaded to ExHentai and all favorites on ExHentai will be re-downloaded into the app. Useful for repairing sync after sync has been interrupted.</string>
<string name="sync_state_reset">Sync state reset</string>
<string name="ignore_sync_errors_summary">Jangan langsung batalkan ketika menemui kesalahan selama proses sinkronisasi. Kesalahan tetap akan ditampilkan setelah sinkronisasi selesai. Dapat menyebabkan kehilangan favorit dalam beberapa kasus. Berguna saat menyinkronkan pustaka besar.</string>
<string name="force_sync_state_reset">Paksa reset status sinkronisasi</string>
<string name="force_sync_state_reset_summary">Melakukan resinkronisasi penuh pada sinkronisasi berikutnya. Penghapusan tidak akan disinkronkan. Semua favorit di aplikasi akan diunggah ulang ke ExHentai dan semua favorit di ExHentai akan diunduh ulang ke aplikasi. Berguna untuk memperbaiki sinkronisasi setelah sinkronisasi terhenti.</string>
<string name="sync_state_reset">Pengaturan ulang status sinkronisasi</string>
<string name="gallery_update_checker">Gallery update checker</string>
<string name="auto_update_restrictions">Auto update restrictions</string>
<string name="time_between_batches">Time between update batches</string>
<string name="time_between_batches_never">Never update galleries</string>
<string name="time_between_batches_1_hour">1 hour</string>
<string name="time_between_batches_2_hours">2 hours</string>
<string name="time_between_batches_3_hours">3 hours</string>
<string name="time_between_batches_6_hours">6 hours</string>
<string name="time_between_batches_12_hours">12 hours</string>
<string name="time_between_batches_24_hours">24 hours</string>
<string name="time_between_batches_48_hours">48 hours</string>
<string name="time_between_batches_summary_1">%1$s will currently never check galleries in your library for updates.</string>
<string name="time_between_batches_1_hour">1 jam</string>
<string name="time_between_batches_2_hours">2 jam</string>
<string name="time_between_batches_3_hours">3 jam</string>
<string name="time_between_batches_6_hours">6 jam</string>
<string name="time_between_batches_12_hours">12 jam</string>
<string name="time_between_batches_24_hours">24 jam</string>
<string name="time_between_batches_48_hours">48 jam</string>
<string name="time_between_batches_summary_1">%1$s saat ini tidak akan pernah memeriksa galeri di pustakamu untuk pembaruan.</string>
<string name="time_between_batches_summary_2">%1$s checks/updates galleries in batches. This means it will wait %2$d hour(s), check %3$d galleries, wait %2$d hour(s), check %3$d and so on…</string>
<string name="show_updater_statistics">Show updater statistics</string>
<string name="gallery_updater_statistics_collection">Collecting statistics…</string>
@@ -98,11 +98,11 @@
<string name="eh_settings_uploading_to_server_message">Please wait, this may take some time…</string>
<string name="eh_settings_out_of_slots_error">You are out of profile slots on %1$s, please delete a profile!</string>
<!-- EH Settings Login Activity -->
<string name="recheck_login_status">Recheck login status</string>
<string name="alternative_login_page">Alternative login page</string>
<string name="skip_page_restyling">Skip page restyling</string>
<string name="custom_igneous_cookie">Custom igneous cookie</string>
<string name="custom_igneous_cookie_message">Some users cannot access ExHentai the normal way, and have to pass in a specific igneous cookie value, this option is for those users.</string>
<string name="recheck_login_status">Periksa ulang status login</string>
<string name="alternative_login_page">Halaman login alternatif</string>
<string name="skip_page_restyling">Lewati penataan ulang halaman</string>
<string name="custom_igneous_cookie">Cookie igneous kustom</string>
<string name="custom_igneous_cookie_message">Beberapa pengguna tidak dapat mengakses ExHentai dengan cara biasa, dan harus memasukkan nilai cookie igneous tertentu, opsi ini adalah untuk pengguna tersebut.</string>
<!-- Advanced Settings -->
<string name="developer_tools">Opsi Pengembang</string>
<string name="toggle_hentai_features">Aktifkan fitur hentai terintegrasi</string>
@@ -220,7 +220,7 @@
<string name="pref_force_horz_seekbar_summary">Menghapus seekbar vertikal sepenuhnya dan menggantikanya dengan seekbar horizontal</string>
<!-- Reader -->
<!-- Reader Actions -->
<string name="eh_autoscroll">Gulir otomatis</string>
<string name="eh_autoscroll">Gulir Otomatis</string>
<string name="eh_retry_all">Muat ulang</string>
<string name="eh_boost_page">Percepat halaman</string>
<string name="eh_autoscroll_help">Panduan Gulir Otomatis</string>
@@ -269,8 +269,8 @@
<string name="merged_already">Manga ini sudah tergabung dengan manga saat ini!</string>
<string name="merge_duplicate">Manga gabungan ini adalah duplikat!</string>
<!-- Manga Info Edit -->
<string name="reset_tags">Reset Tagar</string>
<string name="add_tag">Tambah Tagar</string>
<string name="reset_tags">Reset Tag</string>
<string name="add_tag">Tambahkan Tag</string>
<string name="title_hint">Judul: %1$s</string>
<string name="description_hint">Deskripsi: %1$s</string>
<string name="author_hint">Author: %1$s</string>
@@ -298,12 +298,12 @@
<string name="no_source_categories">Tak ada kategori sumber yang tersedia</string>
<string name="invalid_category_name">Nama kategori tidak valid</string>
<!-- Sort by tags -->
<string name="pref_tag_sorting">Penyortir Tagar</string>
<string name="tag_sorting">Menyortir tagar</string>
<string name="action_add_tags_message">Baca ini! Tagar harus sama persis,maka tidak akan berjalan semestinya!</string>
<string name="action_edit_tags">Edit tagar</string>
<string name="pref_tag_sorting">Penyortir Tag</string>
<string name="tag_sorting">Menyortir Tag</string>
<string name="action_add_tags_message">Baca ini! Tag harus sama persis, tidak ada pencocokan parsial, Anda tidak dapat menggunakan netorare untuk memfilter \"female:netorare\" atau tag serupa!\nGaya untuk tag namespace adalah\n\"female: sole female\" \ntanpa tanda kutip!\nMenambahkan beberapa varian tag yang sama diperbolehkan, jadi jangan ragu untuk menggunakan \"tag: netorare\" untuk NHentai dan \"female: netorare\" untuk E-Hentai!</string>
<string name="action_edit_tags">Edit tag</string>
<string name="information_empty_tags">Kamu tidak memiliki tagar. Ketuk tombol tambah untuk membuatnya</string>
<string name="error_tag_exists">Tagar ini sudah ada!</string>
<string name="error_tag_exists">Tag ini sudah ada!</string>
<!-- Extension section -->
<string name="ext_redundant">Duplikat</string>
<string name="redundant_extension_message">Ekstensi ini duplikat dan tak akan digunakan di dalam versi Tachiyomi ini.</string>
@@ -319,13 +319,13 @@
<string name="use_most_chapters">Pilih sumber dengan bab terbanyak (lambat)</string>
<string name="use_first_source">Pilih sumber yang pertama terdeteksi</string>
<string name="skip_this_step_next_time">Lewati langkah ini di lain waktu</string>
<string name="hide_not_found_entries">Sembunyikan manga yang tak ditemukan</string>
<string name="hide_not_found_entries">Sembunyikan komik yang tak ditemukan</string>
<string name="search_parameter">Parameter penelusuran (cth. language:english)</string>
<string name="latest_">Terbaru: %1$s</string>
<string name="migrating_to">Pindahkan ke</string>
<string name="match_pinned_sources">Gunakan sumber yang disematkan</string>
<string name="match_enabled_sources">Gunakan sumber yang diaktifkan</string>
<string name="no_chapters_found_for_migration">Bab tak ditemukan, manga ini tak bisa dipindahkan ke sini</string>
<string name="no_chapters_found_for_migration">Bab tak ditemukan, komik ini tak bisa dipindahkan ke sini</string>
<string name="no_alternatives_found">Tidak Ditemukan</string>
<string name="stop_migrating">Hentikan migrasi?</string>
<string name="skipping_">(melewati %1$d)</string>
@@ -560,9 +560,9 @@
<string name="google_drive_sync_data_not_found">Tidak ada data sinkronisasi yang ditemukan di Google Drive</string>
<string name="google_drive_sync_data_purge_error">Kesalahan membersihkan data sinkronisasi dari Google Drive, Coba masuk lagi.</string>
<string name="google_drive_login_success">Masuk ke Google Drive</string>
<string name="pref_hide_feed">Sembunyikan tab Umpan</string>
<string name="pref_feed_position">Posisi tab Umpan</string>
<string name="pref_feed_position_summery">Apakah Anda ingin tab Umpan menjadi tab pertama dalam penjelajahan? Ini akan menjadikannya tab default saat membuka penjelajahan, tidak disarankan jika Anda menggunakan data atau jaringan terukur</string>
<string name="pref_hide_feed">Sembunyikan tab Daftar Konten</string>
<string name="pref_feed_position">Posisi tab Daftar Konten</string>
<string name="pref_feed_position_summery">Apakah Anda ingin tab Daftar Konten menjadi tab pertama dalam penjelajahan? Ini akan menjadikannya tab default saat membuka penjelajahan, tidak disarankan jika Anda menggunakan data atau jaringan terukur</string>
<string name="error_uploading_sync_data">Terjadi kesalahan saat mengunggah data sinkronisasi ke Google Drive</string>
<string name="google_drive_login_failed">Gagal masuk ke Google Drive: %s</string>
<string name="google_drive_not_signed_in">Belum masuk ke Google Drive</string>
@@ -570,4 +570,79 @@
<string name="error_deleting_google_drive_lock_file">Kesalahan saat menghapus file kunci Google Drive</string>
<string name="error_before_sync_gdrive">Gagal sebelum sinkron: %s</string>
<string name="pref_purge_confirmation_title">Konfirmasi pembersihan</string>
<string name="sync_on_chapter_read">Sinkronisasi pada Bab Baca</string>
<string name="sync_on_chapter_open">Sinkronisasi pada Bab Terbuka</string>
<string name="sync_on_app_resume">Sinkronisasi pada saat Melanjutkan Aplikasi</string>
<string name="sync_library">Sinkronkan pustaka</string>
<string name="delete_time_range_confirmation">Apakah Anda ingin menghapus rentang waktu %s?</string>
<string name="encrypt_database_subtitle">Membutuhkan restart aplikasi untuk menerapkannya</string>
<string name="set_cbz_zip_password">Tetapkan kata sandi arsip CBZ</string>
<string name="pref_sync_options_summ">Dapat digunakan untuk mengatur pemicu sinkronisasi</string>
<string name="encrypt_database">Enkripsi database</string>
<string name="pref_purge_confirmation_message">Menghapus data sinkronisasi akan menghapus semua data sinkronisasi dari Google Drive. Apakah Anda yakin ingin melanjutkan?</string>
<string name="sync_on_app_start">Sinkronisasi pada saat Aplikasi Dibuka</string>
<string name="delete_time_range">Hapus rentang waktu</string>
<string name="encrypt_database_message"><![CDATA[<font color=\'red\'>MENGAKTIFKAN INI AKAN MEMBUAT DATABASE BARU. GUNAKAN LANGKAH-LANGKAH INI UNTUK MENYIMPAN DATA ANDA<br>1. PENGATURAN -> CADANGAN -> BUAT<br>2. PENGATURAN SISTEM -> HAPUS DATA APLIKASI<br>3. BUKA APLIKASI DAN AKTIFKAN INI<br>4. PENGATURAN SISTEM -> PAKSA MULAI ULANG<br>5. PENGATURAN -> CADANGAN -> PULIHKAN</font>]]></string>
<string name="favorites_sync_conformation_message">Apakah Anda yakin untuk menyinkronkan favorit Anda dengan E-Hentai?</string>
<string name="relation_similar">Mirip</string>
<string name="relation_prequel">Prekuel</string>
<string name="alt_titles">Judul-Judul Alternatif</string>
<string name="encryption_type">Tipe enkripsi</string>
<string name="aes_256">AES 256</string>
<string name="relation_adapted_from">Diadaptasi dari</string>
<string name="relation_based_on">Berdasarkan dari</string>
<string name="relation_side_story">Cerita Sampingan</string>
<string name="relation_colored">Berwarna</string>
<string name="relation_serialization">Serialisasi</string>
<string name="action_copy_second_page">Salin halaman kedua</string>
<string name="action_copy_combined_page">Salin halaman gabungan</string>
<string name="action_copy_first_page">Salin halaman pertama</string>
<string name="page_preview_page_go_to">Pergi ke</string>
<string name="aes_128">AES 128</string>
<string name="community_recommendations">Rekomendasi komunitas</string>
<string name="relation_monochrome">Monokrom</string>
<string name="relation_main_story">Cerita Utama</string>
<string name="relation_doujinshi">Doujinshi</string>
<string name="relation_alternate_version">Versi Alternatif</string>
<string name="standard_zip_encryption">Enkripsi zip standar (cepat tetapi kurang aman)</string>
<string name="center_margin_double_and_wide_page">Tambahkan ke keduanya</string>
<string name="delete_tag">Hapus tag</string>
<string name="action_stop">Berhenti</string>
<string name="relation_sequel">Sekuel</string>
<string name="relation_alternate_story">Cerita alternatif</string>
<string name="feed_tab_empty">Anda tidak memiliki sumber apapun di daftar konten Anda, navigasikan ke kanan atas untuk menambahkan satu</string>
<string name="only_show_updated_entries">Tampilkan hanyak komik dengan bab baru</string>
<string name="archive_mode_cache_to_disk">Salin ke disk penyimpanan</string>
<string name="pref_archive_reader_mode">Mode pembaca arsip</string>
<string name="add_tags">Tambahkan Tag</string>
<string name="delete_cbz_archive_password">Hapus kata sandi arsip CBZ</string>
<string name="cbz_archive_password">Kata sandi arsip CBZ</string>
<string name="center_margin_none">Tidak Ada</string>
<string name="archive_mode_load_from_file">Muat dari berkas</string>
<string name="archive_mode_load_into_memory">Muatkan ke memori</string>
<string name="password_protect_downloads">Lindungi unduhan dengan kata sandi</string>
<string name="page_previews">Pratinjau halaman</string>
<string name="multi_tags_comma_separated">Masukkan tag, dipisahkan dengan koma.</string>
<string name="include_all_read_entries">Sertakan semua komik yang telah dibaca</string>
<string name="ignore_non_library_entries">Abaikan komik yang bukan bagian dari pustaka</string>
<string name="pref_clear_page_preview_cache">Hapus data yang tersimpan untuk pratinjau halaman</string>
<string name="password_protect_downloads_summary">Mengenkripsi unduhan arsip CBZ dengan kata sandi yang diberikan.\nPERINGATAN: DATA YANG ADA DI DALAM ARSIP AKAN HILANG SELAMANYA JIKA ANDA LUPA KATA SANDI</string>
<string name="feed_add">Tambahkan %1$s ke daftar konten?</string>
<string name="more_previews">Pratinjau lebih banyak</string>
<string name="pref_smooth_scroll">Gulir Otomatis Halus</string>
<string name="eh_autoscroll_freq_invalid">Frekuensi tidak valid</string>
<string name="save_search_invalid_name">Nama pencarian yang disimpan tidak valid</string>
<string name="relation_same_franchise">Franchise yang sama</string>
<string name="wrong_cbz_archive_password">Kata sandi arsip CBZ salah</string>
<string name="delete_tag_confirmation">Apakah Anda ingin menghapus tag %s?</string>
<string name="reset_info">Atur Ulang Info</string>
<string name="pref_archive_reader_mode_summary">Cara gambar di dalam arsip, seperti CBZ atau CBR, dimuat</string>
<string name="feed_delete">Hapus sumber dari daftar konten?</string>
<string name="pref_center_margin">Tipe margin tengah</string>
<string name="center_margin">Margin Tengah</string>
<string name="pref_center_margin_summary">Tambahkan pengisi ruang untuk mengakomodasi ruang kosong pada perangkat lipat.</string>
<string name="center_margin_double_page">Tambahkan ke Halaman ganda</string>
<string name="center_margin_wide_page">Tambahkan ke Halaman lebar</string>
<string name="feed">Daftar Konten</string>
<string name="too_many_in_feed">Terlalu banyak sumber di daftar konten Anda, tidak dapat menambahkan lebih dari 10</string>
</resources>
@@ -4,4 +4,17 @@
<string name="action_search_manually">Cerca manualmente</string>
<string name="action_migrate_now">Migra ora</string>
<string name="action_copy_now">Copia ora</string>
<string name="entry_type_webtoon">Webtoon</string>
<string name="entry_type_comic">Fumetto</string>
<string name="entry_type_manhua">Manhua</string>
<string name="pref_category_mangadex">MangaDex</string>
<string name="pref_category_fork">Impostazioni Fork</string>
<string name="action_start_reading">Inizia a leggere</string>
<string name="action_clean_titles">Titoli puliti</string>
<string name="changelog_version">Versione %1$s</string>
<string name="pref_mangadex_summary">Accesso MangaDex, sincronizzazione dei seguiti</string>
<string name="pref_ehentai_summary">Accesso E/ExHentai, sincronizzazione galleria</string>
<string name="action_edit_info">Modifica info</string>
<string name="pref_category_eh">E-Hentai</string>
<string name="pref_category_all_sources">Tutte le Fonti</string>
</resources>
@@ -277,7 +277,7 @@
<string name="pref_tag_sorting">Tags de ordenação de tag</string>
<string name="tag_sorting">Ordenação de tag</string>
<string name="action_add_tags_message">Leia isto! Tags devem ser exatas, não há combinação parcial, não pode fazer netorare filtrar female:netorare ou similar!\nO estilo para tags é\n\"female: sole female\"\nsem aspas!\nAdicionar variantes da mesma tag é suportado, então pode-se fazer \"tag: netorare\" para NHentai e \"female: netorare\" para E-Hentai!</string>
<string name="action_edit_tags">Edit\r tags</string>
<string name="action_edit_tags">Editar tags</string>
<string name="information_empty_tags">Você não tem tags. Toque no botão Adicionar para criar um e ordenar sua biblioteca por tags</string>
<string name="error_tag_exists">Esta tag já existe!</string>
<!-- Extension section -->
@@ -501,4 +501,138 @@
<string name="update_1hour">A cada hora</string>
<string name="pref_hide_feed">Ocultar a aba do feed</string>
<string name="pref_library_mark_duplicate_chapters">Marque os novos capítulos duplicados como lidos</string>
<string name="last_synchronization">Última Sincronização: %1$s</string>
<string name="bandwidth_data_saver_server">Servidor Proxy Bandwidth Hero</string>
<string name="sync_error">Falha ao sincronizar biblioteca</string>
<string name="sync_complete">Sincronização de biblioteca completa</string>
<string name="pref_sync_host">Host</string>
<string name="pref_sync_api_key">Chave API</string>
<string name="pref_sync_api_key_summ">Coloque sua chave API para sincronizar sua biblioteca</string>
<string name="pref_choose_what_to_sync">Escolha o que sincronizar</string>
<string name="syncyomi">SyncYomi</string>
<string name="google_drive">Google Drive</string>
<string name="pref_google_drive_sign_in">Login</string>
<string name="pref_google_drive_purge_sync_data">Limpar dados sincronizados do Google Drive</string>
<string name="google_drive_sync_data_purged">Sincronizar dados purgados do Google Drive</string>
<string name="google_drive_login_success">Login no Google Drive feito com sucesso</string>
<string name="google_drive_not_signed_in">Não está logado no Google Drive</string>
<string name="error_uploading_sync_data">Erro enviando dados de sincronia para o Google Drive</string>
<string name="error_before_sync_gdrive">Erro antes de sincronia: %s</string>
<string name="pref_purge_confirmation_message">Limpar dados sincronizados irá deletar todos os seus dados de sincronia do Google Drive. Você quer continuar?</string>
<string name="google_drive_sync_data_not_found">Não foram encontrados dados para sincronia no Google Drive</string>
<string name="google_drive_login_failed">Falha ao fazer login no Google Drive: %s</string>
<string name="pref_sync_service_category">Sincronização</string>
<string name="label_sync">Sincronizar</string>
<string name="pref_sync_host_summ">Coloque o endereço de hospedagem para sincronizar sua biblioteca</string>
<string name="pref_sync_service">Serviço</string>
<string name="sync_in_progress">Sincronização está em progresso</string>
<string name="google_drive_sync_data_purge_error">Erro purgando dados de sincronia do Google Drive. Tente fazer login novamente.</string>
<string name="data_saver_downloader">Usar economia de dados no baixador</string>
<string name="bandwidth_hero">Bandwidth Hero (Requer um servidor proxy Bandwidth Hero)</string>
<string name="wsrv">wsrv.nl</string>
<string name="put_merge_in_overflow">Mesclar em transborde</string>
<string name="put_merge_in_overflow_summary">Colocar o botão de mesclar no menu de transborde ao invés da pagina inicial</string>
<string name="pref_feed_position_summery">Você quer que a aba Feed seja a primeira aba em Navegar? Isso irá fazer dela a aba principal ao abrir Navegar, não é recomendado se você estiver usando uma rede medida ou dados móveis</string>
<string name="label_triggers">Gatilhos</string>
<string name="pref_sync_now_group_title">Sincronizar Ações</string>
<string name="pref_sync_now">Sincronizar agora</string>
<string name="pref_sync_now_subtitle">Iniciar sincronização imediata de seus dados</string>
<string name="pref_sync_automatic_category">Sincronização Automática</string>
<string name="pref_sync_interval">Frequência de sincronização</string>
<string name="error_deleting_google_drive_lock_file">Error ao Deletar Ficheiro de Bloqueio no Google Drive</string>
<string name="pref_sync_options_summ">Pode ser usado para criar gatilhos de sincronização</string>
<string name="sync_on_chapter_read">Sincronizar após Leitura de Capitulo</string>
<string name="pref_purge_confirmation_title">Confirmação de Limpeza</string>
<string name="pref_sync_options">Criar gatilhos de sincronização</string>
<string name="sync_on_chapter_open">Sincronizar após Capítulo Aberto</string>
<string name="sync_on_app_start">Sincronizar na Abertura da Aplicação</string>
<string name="pref_ehentai_summary">E/ExHentai login, sincronizar galeria</string>
<string name="pref_mangadex_summary">MangaDex login, segue sincronização</string>
<string name="sync_library">Sincronizar biblioteca</string>
<string name="delete_time_range">Excluir intervalo de tempo</string>
<string name="encrypt_database">Criptografar banco de dados</string>
<string name="encrypt_database_subtitle">Requer reinicialização do aplicativo para confirmar as alterações</string>
<string name="password_protect_downloads">Proteja os downloads com senha</string>
<string name="sync_on_app_resume">Sincronizar no aplicativo ao Retomar</string>
<string name="set_cbz_zip_password">Definir senha de arquivo CBZ</string>
<string name="action_copy_first_page">Copie a primeira página</string>
<string name="center_margin">Margem central</string>
<string name="thumbnail_url_hint">URL da miniatura: %1$s</string>
<string name="dedupe_most_chapters">Mostrar fonte com mais capítulos</string>
<string name="favorites_sync_conformation_message">Tem certeza de que deseja sincronizar seus favoritos com o E-Hentai?</string>
<string name="relation_same_franchise">Mesma franquia</string>
<string name="multi_tags_comma_separated">Insira as tag(s), separadas por vírgulas.</string>
<string name="delete_time_range_confirmation">Você deseja excluir o intervalo de tempo %s?</string>
<string name="artist_hint">Artista: %1$s</string>
<string name="delete_tag_confirmation">Você deseja excluir a tag %s?</string>
<string name="page_previews">Pré-visualizações de página</string>
<string name="pref_clear_page_preview_cache">Limpar cache de visualização de página</string>
<string name="action_copy_combined_page">Copiar página combinada</string>
<string name="action_copy_second_page">Copie a segunda página</string>
<string name="add_tags">Adicionar tags</string>
<string name="alt_titles">Títulos Alternativos</string>
<string name="relation_monochrome">Monocromático</string>
<string name="relation_colored">Colorido</string>
<string name="relation_serialization">Serialização</string>
<string name="include_all_read_entries">Incluir todas as entradas lidas</string>
<string name="ignore_non_library_entries">Ignorar entradas que não sejam da biblioteca</string>
<string name="community_recommendations">Recomendações da comunidade</string>
<string name="page_preview_page_go_to">Vá para</string>
<string name="relation_based_on">Baseado em</string>
<string name="relation_similar">Semelhante</string>
<string name="relation_main_story">História principal</string>
<string name="relation_side_story">História paralela</string>
<string name="relation_doujinshi">Doujinshi</string>
<string name="relation_alternate_story">História alternativa</string>
<string name="eh_boost_page_invalid">Esta página não pode ser impulsionada (página inválida)!</string>
<string name="eh_boost_page_downloading">Esta página já está sendo baixada!</string>
<string name="eh_boost_page_downloaded">Esta página já foi baixada!</string>
<string name="center_margin_none">Nenhum</string>
<string name="center_margin_wide_page">Adicionar à página ampla</string>
<string name="pref_center_margin">Tipo de margem central</string>
<string name="merge_duplicate">Esta entrada mesclada é uma duplicata!</string>
<string name="hide_not_found_entries">Ocultar resultados não encontrados</string>
<string name="more_previews">Mais prévias</string>
<string name="dedupe_highest_chapter">Mostrar fonte com maior número de capítulo</string>
<string name="mangadex_similar">MangaDex semelhante</string>
<string name="relation_shared_universe">Universo compartilhado</string>
<string name="relation_alternate_version">Versão alternativa</string>
<string name="merge_unknown_entry">ID de entrada desconhecida: %1$d</string>
<string name="only_show_updated_entries">Mostrar apenas resultados com novos capítulos</string>
<string name="relation_preserialization">Pré-serialização</string>
<string name="entry_merged">Entrada mesclada!</string>
<string name="eh_autoscroll_freq_invalid">Frequência inválida</string>
<string name="delete_cbz_archive_password">Excluir senha do arquivo CBZ</string>
<string name="center_margin_double_page">Adicionar à página dupla</string>
<string name="eh_boost_boosted">Página atual impulsionada!</string>
<string name="wrong_cbz_archive_password">Senha de arquivo CBZ errada</string>
<string name="feed_tab_empty">Você não tem nenhuma fonte em seu feed, navegue até o canto superior direito para adicionar uma</string>
<string name="data_saver_exclude">Excluir da proteção de dados</string>
<string name="relation_spin_off">Spin off</string>
<string name="relation_adapted_from">Adaptado de</string>
<string name="automatic_search_error">Erro ao executar pesquisa automática!</string>
<string name="password_protect_downloads_summary">Criptografa downloads de arquivo CBZ com a senha fornecida.\nAVISO: OS DADOS DENTRO DOS ARQUIVOS SERÃO PERDIDOS PARA SEMPRE SE VOCÊ ESQUECER A SENHA</string>
<string name="cbz_archive_password">Senha do arquivo CBZ</string>
<string name="encryption_type">Tipo de criptografia</string>
<string name="standard_zip_encryption">Criptografia zip padrão (rápida, mas insegura)</string>
<string name="pref_smooth_scroll">Rolagem automática suave</string>
<string name="eh_boost_page_errored">A página não carregou, pressione o botão de tentar novamente!</string>
<string name="center_margin_double_and_wide_page">Adicionar a ambos</string>
<string name="archive_mode_load_into_memory">Carregar na memória</string>
<string name="archive_mode_cache_to_disk">Copiar para o disco</string>
<string name="pref_archive_reader_mode">Modo de leitor de arquivo</string>
<string name="archive_mode_load_from_file">Carregar do arquivo</string>
<string name="merged_already">Esta entrada já foi mesclada com a entrada atual!</string>
<string name="reset_info">Redefinir informações</string>
<string name="title_hint">Título: %1$s</string>
<string name="description_hint">Descrição: %1$s</string>
<string name="author_hint">Autor: %1$s</string>
<string name="could_not_find_entry">Não foi possível encontrar o resultado na fonte!</string>
<string name="save_search_invalid">Pesquisa salva inválida, filtros foram alterados</string>
<string name="save_search_invalid_name">Nome de pesquisa salva inválido</string>
<string name="feed_delete">Excluir item do feed?</string>
<string name="feed_add">Adicionar %1$s ao feed?</string>
<string name="delete_tag">Excluir tag</string>
<string name="too_many_in_feed">Muitas fontes no seu feed, não é possível adicionar mais de 10</string>
<string name="action_stop">Parar</string>
</resources>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_clean_titles">Limpar Títulos</string>
<string name="action_edit_info">Editar informação</string>
<string name="entry_type_manhua">Manhua</string>
<string name="entry_type_comic">Banda Desenhada</string>
<string name="entry_type_webtoon">Webtoon</string>
<string name="pref_category_all_sources">Todas as fontes</string>
<string name="pref_category_eh">E-Hentai</string>
<string name="action_skip_entry">Não migrar</string>
<string name="action_migrate_now">Migrar agora</string>
<string name="action_search_manually">Procurar Manualmente</string>
<string name="entry_type_manga">Manga</string>
<string name="entry_type_manhwa">Manhwa</string>
<string name="action_copy_now">Copiar agora</string>
<string name="action_start_reading">Começar a ler</string>
<string name="ehentai_prefs_account_settings">Definições de Conta E-Hentai Website</string>
<string name="requires_login">Login Necessário</string>
<string name="use_hentai_at_home">Utilizar Hentai@Home Network</string>
<string name="pref_ehentai_summary">E/ExHentai login, sincronização da galeria</string>
<string name="pref_mangadex_summary">MangaDex login, sincronização de seguidores</string>
<string name="changelog_version">Versão %1$s</string>
<string name="enable_exhentai">Ativar ExHentai</string>
<string name="watched_tags_exh">ExHentai Categorias Visualizadas</string>
</resources>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_search_manually">El ile ara</string>
<string name="action_search_manually">Manuel arama</string>
<string name="action_copy_now">Şimdi kopyala</string>
<string name="action_edit_info">Bilgi düzenle</string>
<string name="action_edit_info">Bilgileri düzenle</string>
<string name="entry_type_manga">Manga</string>
<string name="entry_type_manhwa">Manhwa</string>
<string name="entry_type_manhua">Manhua</string>
@@ -12,7 +12,81 @@
<string name="action_start_reading">Okumaya başla</string>
<string name="changelog_version">Versiyon %1$s</string>
<string name="action_clean_titles">Başlıkları temizle</string>
<string name="pref_category_all_sources">Bütün Kaynaklar</string>
<string name="pref_category_all_sources">Tüm Kaynaklar</string>
<string name="entry_type_webtoon">Webtoon</string>
<string name="action_skip_entry">Taşıma yapma</string>
<string name="action_skip_entry">Taşıma</string>
<string name="use_hentai_at_home_summary">Eğer mümkünse görselleri Hentai@Home ağından yüklemek ieter misiniz? Bu ayarı kapatmak görebileceğiniz sayfa sayısını azaltacaktır.\nAyarlar:\n- Herhangi bir istemci (Önerilen)\n- Sadece varsayılan bağlantı noktası istemcileri (Daha yavaş olabilir. Standart olmayan bağlantı noktalarını engelleyen güvenlik duvarı/ara sunucu mevcutsa kullanın.)</string>
<string name="use_hentai_at_home_option_2">Sadece varsayılan bağlantı noktası istemcileri</string>
<string name="watched_tags_summary">E/ExHentai izlenilen etiketler sayfasını webview ile açar</string>
<string name="tag_filtering_threshhold_summary">E/ExHentai Etiketlerim sayfasında etiketlere negatif ağırlık ekleyerek etiketleri filtreleyebilirsiniz. Eğer bir galerinin etiketlerinin değerleri toplamı bu değerin altına düşerse gösterilmez. Bu sınır -9999 ve 0 arasında bir değer olabilir. Şu anda: %1$d</string>
<string name="show_japanese_titles">Arama sonuçlarında Japonca başlıkları göster</string>
<string name="watched_tags_exh">ExHentai İzlenilen Etiketler</string>
<string name="requires_login">Giriş yapma gerekli</string>
<string name="action_migrate_now">Şimdi taşı</string>
<string name="pref_category_fork">Kopya Ayarları</string>
<string name="pref_ehentai_summary">E/ExHentai giriş, galeri eşitleme</string>
<string name="pref_mangadex_summary">MangaDex giriş, takip edilenleri eşitleme</string>
<string name="ehentai_prefs_account_settings">E-Hentai Web Sitesi Hesap Ayarları</string>
<string name="enable_exhentai">ExHentai Aktif Et</string>
<string name="use_hentai_at_home">Hentai@Home Ağını Kullan</string>
<string name="use_hentai_at_home_option_1">Herhangi bir istemci (Önerilen)</string>
<string name="show_japanese_titles_option_1">Arama sonuçlarında Japonca başlıklar gösteriliyor. Bu ayarı değiştirdikten sonra (Gelişmiş ayarlar kısmından) bölüm önbelleğini temizleyin</string>
<string name="show_japanese_titles_option_2">Arama sonuçlarında İngilizce/Romanize başlıklar gösteriliyor. Bu ayarı değiştirdikten sonra (Gelişmiş ayarlar kısmından) bölüm önbelleğini temizleyin</string>
<string name="use_original_images">Orijinal görselleri kullan</string>
<string name="use_original_images_on">Orijinal görseller kullanılıyor</string>
<string name="use_original_images_off">Tekrar örneklenmiş görseller kullanılıyor</string>
<string name="watched_tags">İzlenilen Etiketler</string>
<string name="tag_filtering_threshold">Etiket Filtreleme Sınırı</string>
<string name="tag_filtering_threshhold_error">-9999 ve 0 arasında bir değer olmalı!</string>
<string name="tag_watching_threshhold_summary">Yeni güncellenen galeriler herhangi bir izlenen etikete sahipse ve izlenen etiketlerinin değerlerinin toplamı bu değere eşit ve daha fazlaysa izlenilenler ekranında gösterilecektir. Bu sınır 0 ve 9999 arasında bir değer olabilir. Şu anda: %1$d</string>
<string name="language_filtering_summary">Belirli dillerdeki galerileri galeri listesinde ve aramalarda gizlemek isterseniz açılacak menüden bunları seçin.\nEşleşen galeriler aramanıza bakılmaksızın gösterilmeyecektir.\nKısaca işaretli olanlar hariç tutulacaktır</string>
<string name="eh_image_quality_1280">1280x</string>
<string name="time_between_batches_3_hours">3 saat</string>
<string name="gallery_update_checker">Galeri güncelleme kontrolcüsü</string>
<string name="auto_update_restrictions">Otomatik güncelleme kısıtlamaları</string>
<string name="pref_enhanced_e_hentai_view">Gelişmiş E/ExHentai araması</string>
<string name="tag_watching_threshhold">Etiket İzleme Sınırı</string>
<string name="tag_watching_threshhold_error">0 ve 9999 arasında bir değer olmalı!</string>
<string name="language_filtering">Dil Filtreleme</string>
<string name="frong_page_categories">Ana Sayfa Kategorileri</string>
<string name="fromt_page_categories_summary">Ana sayfada ve aramalarda varsayılan olarak gösterilmesini istediğiniz kategoriler hangileri? Filtrelerini etkinleştirilerek aktif edilebilirler</string>
<string name="watched_list_default">İzlenenler Listesi Filtresi Varsayılan Durumu</string>
<string name="watched_list_state_summary">ExHentai/E-Hentai üzerinde gezinirken izlenenler listesi filtrelerinin varsayılan olarak aktif edileceğini kontrol eder</string>
<string name="eh_image_quality_summary">İndirilen görsellerin kalitesi</string>
<string name="eh_image_quality">Görsel kalitesi</string>
<string name="eh_image_quality_auto">Otomatik</string>
<string name="eh_image_quality_2400">2400x</string>
<string name="eh_image_quality_1600">1600x</string>
<string name="eh_image_quality_980">980x</string>
<string name="eh_image_quality_780">780x</string>
<string name="pref_enhanced_e_hentai_view_summary">E/ExHentai için gelişmiş arama menüsü modunu açın/kapatın</string>
<string name="favorites_sync">E-Hentai Favorileri eşitleme</string>
<string name="disable_favorites_uploading">Favorilerin gönderilmesini kapat</string>
<string name="disable_favorites_uploading_summary">Favoriler yalnızca ExHentai üzerinden indirilir. Uygulama favorilere yapılan değişiklikler gönderilmeyecektir. ExHentai üzerinde favorilerin yanlışlıkla kaybolmasını önler. Silinenlerin yine de indirileceğini unutmayın (ExHentai üzerinde bir favoriyi silersemiz uygulamada da silinecektir).</string>
<string name="show_favorite_sync_notes">Favorilerin eşitleme notlarını göster</string>
<string name="show_favorite_sync_notes_summary">Favori eşitleme özelliği ile ilgili çeşitli bilgiler gösterir</string>
<string name="please_login">Lütfen giriş yapın!</string>
<string name="ignore_sync_errors_summary">Eşitleme sırasında bir hata oluşursa işlemin hemen durdurulmasını önler. Eşitleme tamamlandığında hatalar gösterilecektir. Bazı durumlarda favorilerin kaybolmasına neden olabilir. Büyük kitaplıkların eşitlenmesinde fayda sağlar.</string>
<string name="force_sync_state_reset">Zorla eşitleme durumunu sıfırla</string>
<string name="force_sync_state_reset_summary">Gelecek eşitlemede kapsamlı bir yeniden senkronizasyon yapar. Silinenler eşitlenmeyecektir. Uygulamadaki bütün favoriler tekrar ExHentai üzerine gönderilecektir ve ExHentai üzerindeki bütün favoriler tekrar uygulamaya indirilecektir. Eşitleme kesildikten sonra düzeltimesine fayda sağlar.</string>
<string name="sync_state_reset">Eşitleme durumunu sıfırla</string>
<string name="time_between_batches">Toplu güncellemeler arasındaki süre</string>
<string name="time_between_batches_never">Galerileri asla güncelleme</string>
<string name="time_between_batches_1_hour">1 saat</string>
<string name="time_between_batches_2_hours">2 saat</string>
<string name="time_between_batches_6_hours">6 saat</string>
<string name="time_between_batches_24_hours">24 saat</string>
<string name="time_between_batches_48_hours">48 saat</string>
<string name="time_between_batches_12_hours">12 saat</string>
<string name="time_between_batches_summary_1">%1$s şu anda kitaplığınızdaki galerileri güncellemeler için asla kontrol etmeyecektir.</string>
<string name="time_between_batches_summary_2">Galeriler için toplu %1$s kontrol/güncelleme. Bu; %2$d saat beklenilip %3$d galerinin kontrol edileceği, %2$d saat beklenilip %3$d galerinin kontrol edileceği ve böyle devam edileceği anlamına gelir</string>
<string name="show_updater_statistics">Güncelleyici istatistiklerini göster</string>
<string name="gallery_updater_statistics_collection">İstatistikler toplanıyor…</string>
<string name="gallery_updater_statistics">Galeri güncelleyici istatistikleri</string>
<string name="gallery_updater_stats_text">Güncelleyici %1$s çalıştı, kontrol edilebilecek %3$d galeriden %2$d tanesini kontrol etti.</string>
<string name="gallery_updater_not_ran_yet">Güncelleyici henüz kullanılmadı.</string>
<string name="gallery_updater_stats_time">\nKontrol edilen galeriler:\n- son saat içinde: %1$d\n- son 6 saat içinde: %2$d\n- son 12 saat içinde: %3$d\n- son gün içinde: %4$d\n- son 2 gün içinde: %5$d\n- son hafta içinde: %6$d\n- son ay içinde: %7$d\n- son yıl içinde: %8$d</string>
<string name="ignore_sync_errors">Eşitleme hatalarını mümkün olduğunda görmezden gel</string>
<string name="eh_settings_successfully_uploaded">Ayarlar başarılı bir şekilde yüklendi!</string>
<string name="eh_settings_configuration_failed">Ayarlarlana başarısız!</string>
</resources>
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
@@ -10,7 +10,7 @@
<item quantity="other">重試 %1$d 個失敗的頁面……</item>
</plurals>
<plurals name="pref_tag_sorting_desc">
<item quantity="other">%1$d 個標籤在排序列表中, 這在書櫃中增加了一個選項,以基於優先的標籤列表進行排序,這意味著作品將以你想要的標籤優先的方式進行排序</item>
<item quantity="other">%1$d 個標籤在排序列表中, 這在書櫃中增加了一個選項,以基於優先的標籤列表進行排序,這意味著作品將以你想要的標籤優先的方式進行排序</item>
</plurals>
<plurals name="migrate_entry">
<item quantity="other">遷移 %1$d%2$s 作品?</item>
@@ -89,7 +89,7 @@
<string name="gallery_updater_stats_time">\n上一次檢查的畫廊:\n- 1 小時: %1$d\n- 6 小時: %2$d\n- 12 小時: %3$d\n- 1 天: %4$d\n- 2 天: %5$d\n- 1 周: %6$d\n- 1 個月: %7$d\n- 1 年: %8$d</string>
<!-- EH Settings Upload Dialogs -->
<string name="settings_profile_note">建立設定檔說明</string>
<string name="settings_profile_note_message">本應用程式現在將在 E-Hentai 和 ExHentai 上建立一個新的設定檔,以最佳化應用程式的能。請確保您在這兩個網站上擁有的設定檔少於三個\n\n如果你不知道什麼是設定檔,那麼對你無影響,只需點擊「確定」</string>
<string name="settings_profile_note_message">本應用程式現在將在 E-Hentai 和 ExHentai 上建立一個新的設定檔,以最佳化應用程式的能。請確保您在這兩個網站上擁有的設定檔少於三個\n\n如果你不知道什麼是設定檔,那麼對你無影響,只需點擊「確定」</string>
<string name="eh_settings_successfully_uploaded">設定檔上傳成功!</string>
<string name="eh_settings_configuration_failed">設定失敗!</string>
<string name="eh_settings_configuration_failed_message">在設定過程中出現錯誤:%1$s</string>
@@ -121,16 +121,16 @@
<string name="clean_orphaned_downloads">清理孤立的下載</string>
<string name="clean_read_downloads">清理已讀</string>
<string name="clean_read_entries_not_in_library">清理不在書櫃中的作品</string>
<string name="data_saver">數據節省模式</string>
<string name="data_saver">資料節省模式</string>
<string name="data_saver_summary">在閱讀器下載圖片前壓縮</string>
<string name="data_saver_downloader">在下載器中使用數據節省模式</string>
<string name="data_saver_downloader">在下載器中使用資料節省模式</string>
<string name="data_saver_ignore_jpeg">忽略 JPEG 圖片</string>
<string name="data_saver_ignore_gif">忽略 Gif 動畫</string>
<string name="data_saver_image_quality">圖片品質</string>
<string name="data_saver_image_quality_summary">更高值代表更佳的儲存的圖片品質,但這代表更大的檔案大小,80% 可以很好地平衡圖片品質和檔案大小</string>
<string name="data_saver_image_format">壓縮為 JPEG</string>
<string name="data_saver_image_format_summary_on">JPEG 檔案的大小要比 Webp 小得多(代表節省了更多的數據),但它也會使圖片損失更多的品質。\n目前壓縮為 JPEG</string>
<string name="data_saver_image_format_summary_off">JPEG 檔案的大小要比 Webp 小得多(代表節省了更多的數據),但它也會使圖片損失更多的品質。\n目前壓縮為 Webp</string>
<string name="data_saver_image_format_summary_on">JPEG 檔案的大小要比 Webp 小得多(代表節省了更多的資料),但它也會使圖片損失更多的品質。\n目前壓縮為 JPEG</string>
<string name="data_saver_image_format_summary_off">JPEG 檔案的大小要比 Webp 小得多(代表節省了更多的資料),但它也會使圖片損失更多的品質。\n目前壓縮為 Webp</string>
<string name="data_saver_color_bw">轉換為黑白</string>
<string name="bandwidth_hero">Bandwidth Hero(需要一個 Bandwidth Hero 代理伺服器)</string>
<string name="bandwidth_data_saver_server">Bandwidth Hero 代理伺服器</string>
@@ -175,7 +175,7 @@
<string name="pref_feed_position_summery">您是否希望將「動態」標籤設為瀏覽中的第一個標籤?這將使其成為預設的瀏覽標籤,但如果您使用行動數據或有限網路,則不建議這樣做</string>
<string name="pref_source_source_filtering">在目錄中過濾來源</string>
<string name="pref_source_source_filtering_summery">篩選屬於類別的來源,使屬於某個類別的來源不會被放在語言下</string>
<string name="pref_source_navigation">替換最新按鈕</string>
<string name="pref_source_navigation">取代最新按鈕</string>
<string name="pref_source_navigation_summery">用一個包括最新和瀏覽的自訂瀏覽檢視取代最新按鈕</string>
<string name="pref_local_source_hidden_folders">本機來源隱藏資料夾</string>
<string name="pref_local_source_hidden_folders_summery">允許本機源讀取隱藏資料夾</string>
@@ -211,13 +211,13 @@
<string name="google_drive_login_success">成功登入 Google Drive</string>
<string name="google_drive_login_failed">無法登入 Google Drive%s</string>
<string name="google_drive_not_signed_in">未登入 Google Drive</string>
<string name="error_uploading_sync_data">上傳同步數據至 Google Drive 時出錯</string>
<string name="error_uploading_sync_data">上傳同步資料至 Google Drive 時出錯</string>
<string name="error_deleting_google_drive_lock_file">刪除 Google Drive 鎖定文件時出錯</string>
<string name="error_before_sync_gdrive">同步前出錯:%s</string>
<string name="pref_purge_confirmation_title">清除確認</string>
<string name="pref_purge_confirmation_message">清除同步資料將從 Google Drive 中刪除所有同步資料。您確定要繼續嗎?</string>
<string name="pref_sync_options">建同步觸發條件</string>
<string name="pref_sync_options_summ">可用於設同步觸發條件</string>
<string name="pref_sync_options">同步觸發條件</string>
<string name="pref_sync_options_summ">可用於設同步觸發條件</string>
<string name="sync_on_chapter_read">閱讀章節時同步</string>
<string name="sync_on_chapter_open">打開章節時同步</string>
<string name="sync_on_app_start">啟動應用時同步</string>
@@ -260,7 +260,7 @@
<string name="download_threads_summary">較高的值可以明顯加快頁面載入速度,同時容易被停權。建議值為 2 或 3。現在的值為:%s</string>
<string name="aggressively_load_pages">積極地載入頁面</string>
<string name="aggressively_load_pages_summary">在閱讀時緩慢地下載整個章節,而不是只載入你正在閱讀的頁面。</string>
<string name="skip_queue_on_retry">重試時跳過</string>
<string name="skip_queue_on_retry">重試時跳過</string>
<string name="skip_queue_on_retry_summary">通常情況下,在下載失敗時按下重試按鈕,將等待下載器完成最後一頁的下載,然後開始重新下載失敗的頁面。啟用此功能將迫使下載器在你按下重試按鈕後立即開始重新下載失敗的頁面。</string>
<string name="reader_preload_amount">預載入頁面數量</string>
<string name="reader_preload_amount_4_pages">4 頁</string>
@@ -273,7 +273,7 @@
<string name="reader_preload_amount_20_pages">20 頁</string>
<string name="reader_preload_amount_summary">閱讀時預先載入的頁數。較高的數值將提供更流暢的閱讀體驗,但會增加快取使用量。建議在使用較大數值時,增加分配給快取的容量</string>
<string name="reader_cache_size">閱讀器快取大小</string>
<string name="reader_cache_size_summary">閱讀時存於裝置上的圖片數量。較高的數值將提供更流暢的閱讀體驗,但會增加磁碟空間的使用量</string>
<string name="reader_cache_size_summary">閱讀時存於裝置上的圖片數量。較高的數值將提供更流暢的閱讀體驗,但會增加磁碟空間的使用量</string>
<string name="preserve_reading_position">保留已閱讀作品的閱讀位置</string>
<string name="auto_webtoon_mode">自動 Webtoon 模式</string>
<string name="auto_webtoon_mode_summary">檢測到可能為長條形格式的作品時自動使用 Webtoon 模式</string>
@@ -348,11 +348,11 @@
<!-- Entry Info -->
<string name="az_recommends">檢視推薦</string>
<string name="merge">合併</string>
<string name="merge_with_another_source">與其他合併</string>
<string name="merge_with_another_source">和另一個合併</string>
<string name="entry_merged">作品已合併!</string>
<string name="failed_merge">作品合併失敗:%1$s</string>
<string name="merge_unknown_entry">未知的作品 ID: %1$d</string>
<string name="merged_already">此作品已與前作品合併!</string>
<string name="merged_already">此作品已與前作品合併!</string>
<string name="merge_duplicate">此已合併的作品重複了!</string>
<!-- Entry Info Edit -->
<string name="reset_tags">重設標籤</string>
@@ -366,15 +366,15 @@
<!-- Browse -->
<!-- Sources Tab -->
<string name="find_in_another_source">搜尋其他來源</string>
<string name="data_saver_exclude">排除在數據節省模式之外</string>
<string name="data_saver_stop_exclude">停止排除在數據節省模式之外</string>
<string name="data_saver_exclude">排除在資料節省模式之外</string>
<string name="data_saver_stop_exclude">停止排除在資料節省模式之外</string>
<!-- Smart Search -->
<string name="searching_source">搜尋來源中……</string>
<string name="could_not_find_entry">未能在來源中找到此作品!</string>
<string name="automatic_search_error">進行自動搜尋過程中出現錯誤!</string>
<!-- Saved Searches -->
<string name="saved_searches">儲存搜尋</string>
<string name="save_search">儲存前搜尋結果?</string>
<string name="save_search">儲存前搜尋結果?</string>
<string name="save_search_hint">我的搜尋名稱</string>
<string name="save_search_failed_to_load">載入已儲存的搜尋失敗!</string>
<string name="save_search_failed_to_load_message">載入已儲存的搜尋過程中出現問題。</string>
@@ -409,7 +409,7 @@
<string name="migration">遷移</string>
<string name="skip_pre_migration">跳過預遷移</string>
<string name="pre_migration_skip_toast">要重新顯示此頁面,請到設定 -&gt; 書櫃。</string>
<string name="use_intelligent_search">搜尋標題 +標題關鍵字</string>
<string name="use_intelligent_search">搜尋標題 + 標題關鍵字</string>
<string name="data_to_include_in_migration">遷移中包含的資料</string>
<string name="include_extra_search_parameter">搜尋時包含額外的搜尋參數</string>
<string name="use_most_chapters">使用章節數最多的來源(較慢)</string>
@@ -578,7 +578,7 @@
<string name="merge_settings">合併設定</string>
<string name="fetch_chapter_updates">取得章節更新</string>
<string name="delete_merged_entry">你確定嗎?</string>
<string name="delete_merged_entry_desc">這將從合併中移除該項目,使用此功能時也會失對合併項目所做的任何未存更改</string>
<string name="delete_merged_entry_desc">這將從合併中移除該項目,使用此功能時也會失對合併項目所做的任何未存更改</string>
<string name="chapter_updates_merged_entry">變更章節更新</string>
<string name="chapter_updates_merged_entry_desc">變更此項將停用或啟用此已合併的漫畫的章節更新</string>
<string name="download_merged_entry">變更新章節下載</string>
@@ -275,4 +275,4 @@
<string name="backup_restore_missing_trackers">መከታተያዎች አልገቡም:</string>
<string name="backup_restore_missing_sources">የጠፋ ምንጮች:</string>
<string name="invalid_backup_file_missing_manga">ምትኬ ማንኛውንም ማንጋ አልያዘም ፡፡</string>
</resources>
</resources>
@@ -9,12 +9,12 @@
<item quantity="other">%d ৰেপোসমূহ</item>
</plurals>
<plurals name="lock_after_mins">
<item quantity="one">%1$s মিনিট পিছত</item>
<item quantity="one">%1$s মিনিট পিছত</item>
<item quantity="other">%1$s মিনিটৰ পিছত</item>
</plurals>
<plurals name="relative_time">
<item quantity="one">কালি</item>
<item quantity="other">%1$d দিন আগতে</item>
<item quantity="other">%1$d দিন আগতে</item>
</plurals>
<plurals name="upcoming_relative_time">
<item quantity="one">কাইলৈ</item>
@@ -25,11 +25,11 @@
<item quantity="other">%d শিতানসমূহ</item>
</plurals>
<plurals name="next_unread_chapters">
<item quantity="one">ঢ়া হোৱা পৰৱৰ্তী অধ্যা</item>
<item quantity="other">ঢ়া হোৱা %d পৰৱৰ্তী অধ্যাসমূহ</item>
<item quantity="one">পৰৱৰ্তী অপঠিত অধ্যায়</item>
<item quantity="other">পৰৱৰ্তী %d অপঠিত অধ্যায়সমূহ</item>
</plurals>
<plurals name="restore_completed_message">
<item quantity="one">%1$sত %2$s ত্ৰুটি সৈতে সমাপ্ত</item>
<item quantity="one">%1$sত %2$s ত্ৰুটি সৈতে সমাপ্ত</item>
<item quantity="other">%1$sত %2$s ত্ৰুটিৰ সৈতে সমাপ্ত</item>
</plurals>
<plurals name="download_queue_summary">
@@ -58,7 +58,7 @@
<string name="action_create">সৃষ্টি কৰক</string>
<string name="invalid_backup_file_missing_manga">বেকআপত কোনো গ্ৰন্থাগাৰ প্ৰৱিষ্ট নাই।</string>
<string name="restore_completed">পুনৰুদ্ধাৰ সম্পূৰ্ণ</string>
<string name="restore_duration">%02d মিনিট, %02d ছেকেণ্ড</string>
<string name="restore_duration">%1$02d মিনিট, %2$02d ছেকেণ্ড</string>
<string name="missing_storage_permission">সংৰক্ষণ অনুমতি প্ৰদান কৰা হোৱা নাই</string>
<string name="in_library">লাইব্ৰেৰীত</string>
<string name="manga_added_library">লাইব্ৰেৰীলৈ যোগ কৰা হৈছে</string>
@@ -522,7 +522,7 @@
<string name="pref_reset_user_agent_string">ডিফল্ট ইউজাৰ এজেন্ট ষ্ট্ৰিং পুনৰ চমা</string>
<string name="requires_app_restart">কাৰ্য্যকৰী হবলৈ এপ পুনৰ আৰম্ভণি প্ৰয়োজন</string>
<string name="cookies_cleared">কুকি মচা হৈছে</string>
<string name="pref_invalidate_download_cache">ডাউনলোড কেচ অবৈধ কৰ</string>
<string name="pref_invalidate_download_cache">ডাউনলোডসমূহ পুনৰ সূচীভুক্ত কৰ</string>
<string name="download_cache_invalidated">ডাউনলোড কেচ অবৈধ কৰা হৈছে</string>
<string name="pref_invalidate_download_cache_summary">ডাউনলোড কৰা অধ্যায়সমূহ পুনৰ পৰীক্ষা কৰিবলৈ এপটো বাধ্য কৰা</string>
<string name="pref_clear_database">ডাটাবেচ মচা</string>
@@ -835,4 +835,9 @@
<string name="ext_remove">আঁতৰাওক</string>
<string name="ext_confirm_remove">এক্সটেনচন আঁতৰাবনে?</string>
<string name="remove_private_extension_message">আপুনি কি \"%s\" এক্সটেনচনটো আঁতৰাব খুজিছে?</string>
<string name="pref_auto_update_manga_on_mark_read">পঢ়া হিচাপে চিহ্নিত কৰাৰ সময়ত অগ্ৰগতি আপডেইট কৰক</string>
<string name="trackers_updated_summary">ট্ৰেকাৰসমূহ অধ্যায় %d লৈ আপডেইট কৰা হৈছে</string>
<string name="pref_hardware_bitmap_threshold">কাষ্টম হাৰ্ডৱেৰ বিটম্যাপ থ্ৰেশহোল্ড</string>
<string name="pref_hardware_bitmap_threshold_summary">যদি পাঠকে এটা খালী ছবি লোড কৰে ক্ৰমান্বয়ে থ্ৰেছহোল্ড হ্ৰাস কৰক। \nবাচনি কৰা: %s</string>
<string name="pref_hardware_bitmap_threshold_default">ডিফল্ট (%d)</string>
</resources>
@@ -392,7 +392,9 @@
<string name="pref_show_reading_mode">Show reading mode</string>
<string name="pref_show_reading_mode_summary">Briefly show current mode when reader is opened</string>
<string name="pref_display_profile">Custom display profile</string>
<string name="pref_always_use_ssiv_to_decode">Always use SSIV to decode long strip images</string>
<string name="pref_hardware_bitmap_threshold">Custom hardware bitmap threshold</string>
<string name="pref_hardware_bitmap_threshold_default">Default (%d)</string>
<string name="pref_hardware_bitmap_threshold_summary">If reader loads a blank image incrementally reduce the threshold.\nSelected: %s</string>
<string name="pref_crop_borders">Crop borders</string>
<string name="pref_custom_brightness">Custom brightness</string>
<string name="pref_grayscale">Grayscale</string>
@@ -642,4 +642,4 @@
<string name="publishing_finished">Приключено издаване</string>
<string name="update_check_fdroid_migration_info">Достъпна е нова версия от официалните източници. Научете как да мигрирате от неофициалните версии на F-Droid.</string>
<string name="download_notifier_split_page_not_found">Страница %d не беше намерена при разделяне</string>
</resources>
</resources>
@@ -443,4 +443,4 @@
<string name="action_not_now">Dili karon</string>
<string name="information_webview_required">Gikinahanlan ang WebView alang sa Tachiyomi</string>
<string name="information_required_plain">*gikinahanlan</string>
</resources>
</resources>

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