Compare commits

...

34 Commits

Author SHA1 Message Date
arkon b7779ba14f v1.8.5 2022-08-14 15:49:00 -04:00
arkon 9a291e6da4 Fix sources not loading
(cherry picked from commit 1f79444a53)

# Conflicts:
#	app/proguard-rules.pro
2022-08-14 15:41:41 -04:00
Jobobby04 e7423e3715 Add missing mangadex languages, remove language prettyPrint since its not used
(cherry picked from commit 240d821a58)
2022-08-13 15:49:13 -04:00
Jobobby04 0ed26dbc49 More fixes 2022-08-13 15:48:49 -04:00
Jobobby04 a08e4e616d Update EHTags list
(cherry picked from commit 05f2f79e0d)
2022-08-13 15:47:53 -04:00
Jobobby04 655126eaa2 Fixes 2022-08-13 15:46:27 -04:00
CVIUS af070a3f0a Detect identical mangas when long pressing to add to library (#7095)
* Detect identical mangas when long pressing to add to library

* Use extracted duplicate manga dialog to avoid duplication

* Partially revert previous commit

* Review changes

* Review changes part 2

(cherry picked from commit f1afeac0bc)
(cherry picked from commit 431c04e54f)
2022-08-13 15:46:05 -04:00
Jobobby04 2b9d564841 Minor improvements for delegated source id lists
(cherry picked from commit 1d593de654)
2022-08-13 15:40:55 -04:00
arkon d09de07a3f Cleanup 2022-08-13 15:40:28 -04:00
arkon 048587468d Don't allow swiping away app update install notification
Also show the new version number in the notifications.

(cherry picked from commit 4aa5c6107c)
2022-08-13 15:32:29 -04:00
CVIUS 5dcdd3454b Detect identical mangas when long pressing to add to library (#7095)
* Detect identical mangas when long pressing to add to library

* Use extracted duplicate manga dialog to avoid duplication

* Partially revert previous commit

* Review changes

* Review changes part 2

(cherry picked from commit f1afeac0bc)
(cherry picked from commit afd1c3b491)
2022-08-13 15:32:18 -04:00
nicki 5d5678861d Fix Links to Changelog/Readme/Commits for multisrc (#7252)
* Fix Links to Changelog/Readme/Commits for `multisrc`

working basic fix. Needs to be refactored into `createUrl()`

* Refactor back into `createUrl`

hopefully the logic is understandable
there's three cases:
 - when multisrc, if `path` isn't mentioned, then we're trying to open
   commmit history
 - when multisrc, if `path` is mentioned, then its either a changelog or
   a readme to a multisrc extension, the files are stored in the
   `overrides` subfolder
 - when not multisrc, we're looking at a single source where the links
   are constructed in the same way regardless of it being
   changelog/readme/commit history

(cherry picked from commit e7695aef78)
(cherry picked from commit 25e0075041)
2022-08-13 15:31:46 -04:00
arkon 85bd12e731 Actually compare chapter numbers as numbers when sorting (fixes #7247)
(cherry picked from commit da8669c826)
(cherry picked from commit 4b7b710b7c)

# Conflicts:
#	app/src/test/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognitionTest.kt
2022-08-13 15:31:37 -04:00
arkon f322a7e660 Add auto split tall images setting
Also includes some fixes for bad merges in earlier commits

Co-authored-by: Saud-97 <Saud-97@users.noreply.github.com>
Co-authored-by: AntsyLich <AntsyLich@users.noreply.github.com>
(cherry picked from commit 6db2becd30)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
2022-08-13 15:30:47 -04:00
Andreas 5f7b7c652c Log extension loading errors directly (#7716)
(cherry picked from commit 7892cc1519)
(cherry picked from commit 0b7d0f7f67)
2022-08-13 15:29:38 -04:00
Alessandro Jean 214cbed3f0 Add missing Authorization header on MAL refresh token request (#7686)
* Add missing Authorization header on MAL refresh token request.

* Make sure to also close the response when it have failed.

(cherry picked from commit 5315467908)
(cherry picked from commit af1ee662ed)
2022-08-13 15:29:28 -04:00
stevenyomi 71db4eebea Filter out empty genres before saving manga to database (#7655)
(cherry picked from commit 4efb736e56)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
(cherry picked from commit 702fdb054a)
2022-08-13 15:29:15 -04:00
Andreas 9a577e1c69 Remove deprecated LibrarySort (#7659)
* Remove deprecated LibrarySort

* Apply suggestions from code review

(cherry picked from commit 58acf0a8aa)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
(cherry picked from commit 4b87831bdd)

# Conflicts:
#	app/src/main/java/exh/EXHMigrations.kt
2022-08-13 15:29:03 -04:00
MatchaSoba 9a5ea9b507 Fix logic for searchWithGenre (#7559)
(cherry picked from commit b563e85c3b)
(cherry picked from commit b729b7f0aa)
2022-08-13 15:28:04 -04:00
arkon 474eea1c84 Avoid catastrophic failure when cover can't be created in local source (fixes #7577)
(cherry picked from commit d6977e5676)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
(cherry picked from commit cfe78ff907)
2022-08-13 15:27:52 -04:00
arkon 43010e92ac Show better error when trying to open RARv5 file
(cherry picked from commit a843054388)
(cherry picked from commit 53a381ce28)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt
2022-08-13 15:27:44 -04:00
nzoba 38b7240728 Add downloaded icon in TransitionView when chapter is downloaded (#7575)
* Add downloaded icon in TransitionView

* Change icon

(cherry picked from commit e8b7743826)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt
(cherry picked from commit ea37a5a7a1)
2022-08-13 15:26:46 -04:00
AntsyLich d52511d5ce Fix logic of app unlock (#7569)
(cherry picked from commit 8ea05e852e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
(cherry picked from commit 09e5bcaec1)
2022-08-13 15:26:36 -04:00
stevenyomi 06f0817bec Fix image MIME issues that cause download errors (#7562)
* Downloader: ignore non-image MIME to prevent .bin extensions

* ProgressResponseBody: allow null content type

Co-authored-by: anenasa <84259093+anenasa@users.noreply.github.com>

Co-authored-by: anenasa <84259093+anenasa@users.noreply.github.com>
(cherry picked from commit 3547d0142f)
(cherry picked from commit d734993349)
2022-08-13 15:26:28 -04:00
f1998f1998 2ee6d2d902 fix concurrent download (#7552)
* Fix concurrent download

* lower Concurrency

* artist Update app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
(cherry picked from commit b635f02d93)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
(cherry picked from commit c69f53a8f4)
2022-08-13 15:26:11 -04:00
Jobobby04 8df8622dfa Handle new default user agent where SY uses it
(cherry picked from commit f3ffd3b930)
2022-08-13 15:25:34 -04:00
arkon 58ef239959 Make default user agent string configurable
(cherry picked from commit 4ee1d72b6f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
(cherry picked from commit bcf9398987)
2022-08-13 15:21:47 -04:00
arkon a126180ca3 Replace deprecated ACTION_MEDIA_SCANNER_SCAN_FILE intent
(cherry picked from commit 0b4f3f5532)
(cherry picked from commit c7e44aa22f)
2022-08-13 15:21:36 -04:00
arkon ae7a4744bd Configure SQLite
- Turn on `foreign_keys` to cascade on delete properly
- Turn on `journal_mode` and set `synchronous` to NORMAL which may help performance for larger libraries

Based on d977b89af1

Co-authored-by: ghostbear <andreas.everos@gmail.com>
(cherry picked from commit ac4f98e152)
2022-08-13 15:21:24 -04:00
arkon 63cd8f8c07 Use Material3 switches in XML layouts
(cherry picked from commit da7a64b40d)

# Conflicts:
#	app/src/main/res/layout/reader_general_settings.xml
#	app/src/main/res/layout/reader_pager_settings.xml
#	app/src/main/res/layout/reader_webtoon_settings.xml
(cherry picked from commit 72aba18dab)
2022-08-13 15:21:04 -04:00
arkon 2ecd2bce51 Bump dependencies + compile SDK to 33 + linting
(cherry picked from commit 3966a917ee)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2022-08-13 15:20:53 -04:00
arkon c7ecb58c61 Update .editorconfig
(cherry picked from commit be33a57d43)
2022-08-13 15:19:06 -04:00
arkon 422721bb64 Update chapter recognition and related tests
Includes 3e07100dc2

Co-authored-by: Saud-97 <Saud-97@users.noreply.github.com>
(cherry picked from commit 4a71022a60)

# Conflicts:
#	.github/workflows/build_pull_request.yml
#	.github/workflows/build_push.yml
#	app/src/test/java/eu/kanade/tachiyomi/CustomRobolectricGradleTestRunner.kt
2022-08-13 15:18:53 -04:00
arkon 92bc9d8801 Update AGP/Gradle
(cherry picked from commit 34ac39e7e5)

# Conflicts:
#	.github/workflows/build_pull_request.yml
#	.github/workflows/build_push.yml
2022-08-13 15:18:20 -04:00
129 changed files with 2847 additions and 2445 deletions
+3 -1
View File
@@ -2,4 +2,6 @@
indent_size=4
insert_final_newline=true
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
+1 -1
View File
@@ -3,7 +3,7 @@
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v1.8.4)
- To the latest version of the app (stable is v1.8.5)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
+2 -2
View File
@@ -53,7 +53,7 @@ body:
label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**.
placeholder: |
Example: "1.8.4"
Example: "1.8.5"
validations:
required: true
@@ -98,7 +98,7 @@ body:
required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[1.8.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
- label: I have updated the app to version **[1.8.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
+1 -1
View File
@@ -33,7 +33,7 @@ body:
required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have updated the app to version **[1.8.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
- label: I have updated the app to version **[1.8.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
@@ -1,5 +0,0 @@
org.gradle.daemon=false
org.gradle.jvmargs=-Xmx5120m
org.gradle.workers.max=2
kotlin.incremental=false
@@ -37,11 +37,6 @@ jobs:
java-version: 11
distribution: adopt
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Write google-services.json
uses: DamianReeves/write-file-action@v1.0
with:
-5
View File
@@ -33,11 +33,6 @@ jobs:
java-version: 11
distribution: adopt
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app
uses: gradle/gradle-command-action@v2
with:
+22 -16
View File
@@ -1,3 +1,4 @@
import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
@@ -18,6 +19,7 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
shortcutHelper.setFilePath("./shortcuts.xml")
android {
namespace = "eu.kanade.tachiyomi"
compileSdk = AndroidConfig.compileSdk
ndkVersion = AndroidConfig.ndk
@@ -25,8 +27,8 @@ android {
applicationId = "eu.kanade.tachiyomi.sy"
minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk
versionCode = 35
versionName = "1.8.4"
versionCode = 36
versionName = "1.8.5"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -225,13 +227,10 @@ dependencies {
// Tests
testImplementation(libs.junit)
testImplementation(libs.assertj.core)
testImplementation(libs.mockito.core)
testImplementation(libs.bundles.robolectric)
// For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android)
implementation(libs.leakcanary.plumber)
// SY -->
// Changelog
@@ -258,23 +257,30 @@ dependencies {
}
tasks {
withType<Test> {
useJUnitPlatform()
testLogging {
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.Experimental",
"-Xopt-in=kotlin.RequiresOptIn",
"-Xopt-in=kotlin.ExperimentalStdlibApi",
"-Xopt-in=kotlinx.coroutines.FlowPreview",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-Xopt-in=coil.annotation.ExperimentalCoilApi",
"-Xopt-in=kotlin.time.ExperimentalTime",
"-opt-in=kotlin.Experimental",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-opt-in=coil.annotation.ExperimentalCoilApi",
"-opt-in=kotlin.time.ExperimentalTime",
)
}
// Duplicating Hebrew string assets due to some locale code issues on different devices
val copyHebrewStrings = task("copyHebrewStrings", type = Copy::class) {
val copyHebrewStrings by registering(Copy::class) {
from("./src/main/res/values-he")
into("./src/main/res/values-iw")
include("**/*")
+1 -2
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" />
@@ -72,6 +72,7 @@ import uy.kohesive.injekt.injectLazy
import java.io.File
import java.security.Security
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.time.Duration.Companion.days
@@ -182,6 +183,7 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
}
override fun onStop(owner: LifecycleOwner) {
preferences.lastAppClosed().set(Date().time)
if (!AuthenticatorUtil.isAuthenticating && preferences.lockAppAfter().get() >= 0) {
SecureActivityDelegate.locked = true
}
@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.updater.AppUpdateJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
@@ -106,10 +105,9 @@ object Migrations {
// Reset sorting preference if using removed sort by source
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
@Suppress("DEPRECATION")
if (oldSortingMode == LibrarySort.SOURCE) {
if (oldSortingMode == 5 /* SOURCE */) {
prefs.edit {
putInt(PreferenceKeys.librarySortingMode, LibrarySort.ALPHA)
putInt(PreferenceKeys.librarySortingMode, 0 /* ALPHABETICAL */)
}
}
}
@@ -202,16 +200,15 @@ object Migrations {
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
@Suppress("DEPRECATION")
val newSortingMode = when (oldSortingMode) {
LibrarySort.ALPHA -> SortModeSetting.ALPHABETICAL
LibrarySort.LAST_READ -> SortModeSetting.LAST_READ
LibrarySort.LAST_CHECKED -> SortModeSetting.LAST_CHECKED
LibrarySort.UNREAD -> SortModeSetting.UNREAD
LibrarySort.TOTAL -> SortModeSetting.TOTAL_CHAPTERS
LibrarySort.LATEST_CHAPTER -> SortModeSetting.LATEST_CHAPTER
LibrarySort.CHAPTER_FETCH_DATE -> SortModeSetting.DATE_FETCHED
LibrarySort.DATE_ADDED -> SortModeSetting.DATE_ADDED
0 -> SortModeSetting.ALPHABETICAL
1 -> SortModeSetting.LAST_READ
2 -> SortModeSetting.LAST_CHECKED
3 -> SortModeSetting.UNREAD
4 -> SortModeSetting.TOTAL_CHAPTERS
6 -> SortModeSetting.LATEST_CHAPTER
8 -> SortModeSetting.DATE_FETCHED
7 -> SortModeSetting.DATE_ADDED
else -> SortModeSetting.ALPHABETICAL
}
@@ -115,5 +115,14 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
override fun onConfigure(db: SupportSQLiteDatabase) {
db.setForeignKeyConstraintsEnabled(true)
setPragma(db, "foreign_keys = ON")
setPragma(db, "journal_mode = WAL")
setPragma(db, "synchronous = NORMAL")
}
private fun setPragma(db: SupportSQLiteDatabase, pragma: String) {
val cursor = db.query("PRAGMA $pragma")
cursor.moveToFirst()
cursor.close()
}
}
@@ -34,11 +34,6 @@ interface Manga : SManga {
return chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC
}
fun getGenres(): List<String>? {
if (genre.isNullOrBlank()) return null
return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
}
// SY -->
fun getOriginalGenres(): List<String>? {
return originalGenre?.split(", ")?.map { it.trim() }
@@ -275,7 +275,7 @@ class Downloader(
// Start downloader if needed
if (autoStart && wasEmpty) {
val queuedDownloads = queue.filter { it.source !is UnmeteredSource }.count()
val queuedDownloads = queue.count { it.source !is UnmeteredSource }
val maxDownloadsFromSource = queue
.groupBy { it.source }
.filterKeys { it !is UnmeteredSource }
@@ -347,8 +347,8 @@ class Downloader(
// Get all the URLs to the source images, fetch pages if necessary
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
// Start downloading images, consider we can have downloaded images already
// Concurrently do 5 pages at a time
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir, dataSaver) }, 5)
// Concurrently do 2 pages at a time
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir, dataSaver).subscribeOn(Schedulers.io()) }, 2)
.onBackpressureLatest()
// Do when page is downloaded.
.doOnNext { notifier.onProgressChange(download) }
@@ -358,6 +358,7 @@ class Downloader(
.doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) }
// If the page list threw, it will resume here
.onErrorReturn { error ->
logcat(LogPriority.ERROR, error)
download.status = Download.State.ERROR
notifier.onError(error.message, download.chapter.name, download.manga.title)
download
@@ -385,7 +386,7 @@ class Downloader(
tmpFile?.delete()
// Try to find the image file.
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") }
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") || it.name!!.contains("${filename}__001") }
// If the image is already downloaded, do nothing. Otherwise download from network
val pageObservable = when {
@@ -395,8 +396,12 @@ class Downloader(
}
return pageObservable
// When the image is ready, set image path, progress (just in case) and status
// When the page is ready, set page path, progress (just in case) and status
.doOnNext { file ->
val success = splitTallImageIfNeeded(page, tmpDir)
if (success.not()) {
notifier.onError(context.getString(R.string.download_notifier_split_failed), download.chapter.name, download.manga.title)
}
page.uri = file.uri
page.progress = 100
download.downloadedImages++
@@ -407,6 +412,7 @@ class Downloader(
.onErrorReturn {
page.progress = 0
page.status = Page.ERROR
notifier.onError(it.message, download.chapter.name, download.manga.title)
page
}
}
@@ -471,7 +477,7 @@ class Downloader(
*/
private fun getImageExtension(response: Response, file: UniFile): String {
// Read content type if available.
val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" }
val mime = response.body?.contentType()?.run { if (type == "image") "image/$subtype" else null }
// Else guess from the uri.
?: context.contentResolver.getType(file.uri)
// Else read magic numbers.
@@ -480,6 +486,26 @@ class Downloader(
return ImageUtil.getExtensionFromMimeType(mime)
}
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile): Boolean {
if (!preferences.splitTallImages().get()) return true
val filename = String.format("%03d", page.number)
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith(filename) }
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
val imageFilePath = imageFile.filePath
?: throw Error(context.getString(R.string.download_notifier_split_page_path_not_found, page.number))
// check if the original page was previously splitted before then skip.
if (imageFile.name!!.contains("__")) return true
return try {
ImageUtil.splitTallImage(imageFile, imageFilePath)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
false
}
}
/**
* Checks if the download was successful.
*
@@ -495,16 +521,10 @@ class Downloader(
dirname: String,
) {
// Ensure that the chapter folder has all the images.
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") }
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") || (it.name!!.contains("__") && !it.name!!.contains("__001.jpg")) }
download.status = if (downloadedImages.size == download.pages!!.size) {
Download.State.DOWNLOADED
} else {
Download.State.ERROR
}
// Only rename the directory if it's downloaded.
if (download.status == Download.State.DOWNLOADED) {
// Only rename the directory if it's downloaded.
if (preferences.saveChaptersAsCBZ().get()) {
archiveChapter(mangaDir, dirname, tmpDir)
} else {
@@ -513,6 +533,10 @@ class Downloader(
cache.addChapter(dirname, mangaDir, download.manga)
DiskUtil.createNoMediaFile(tmpDir, context)
Download.State.DOWNLOADED
} else {
Download.State.ERROR
}
}
@@ -7,6 +7,7 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -193,7 +194,7 @@ class NotificationReceiver : BroadcastReceiver() {
val file = File(path)
file.delete()
DiskUtil.scanMedia(context, file)
DiskUtil.scanMedia(context, file.toUri())
}
/**
@@ -65,6 +65,8 @@ object PreferenceKeys {
const val dohProvider = "doh_provider"
const val defaultUserAgent = "default_user_agent"
const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
@@ -59,7 +59,7 @@ class PreferencesHelper(val context: Context) {
fun lockAppAfter() = flowPrefs.getInt("lock_app_after", 0)
fun lastAppUnlock() = flowPrefs.getLong("last_app_unlock", 0)
fun lastAppClosed() = flowPrefs.getLong("last_app_closed", 0)
fun secureScreen() = flowPrefs.getEnum("secure_screen_v2", Values.SecureScreenMode.INCOGNITO)
@@ -215,6 +215,8 @@ class PreferencesHelper(val context: Context) {
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true)
fun splitTallImages() = flowPrefs.getBoolean("split_tall_images", false)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt("backup_slots", 2)
@@ -308,6 +310,8 @@ class PreferencesHelper(val context: Context) {
fun dohProvider() = prefs.getInt(Keys.dohProvider, -1)
fun defaultUserAgent() = flowPrefs.getString(Keys.defaultUserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44")
fun lastSearchQuerySearchSettings() = flowPrefs.getString("last_search_query", "")
fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
@@ -8,6 +8,7 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir
@@ -82,7 +83,7 @@ class ImageSaver(
}
}
DiskUtil.scanMedia(context, destFile)
DiskUtil.scanMedia(context, destFile.toUri())
return destFile.getUriCompat(context)
}
@@ -22,6 +22,7 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
@@ -256,13 +257,21 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendPath("my_list_status")
.build()
fun refreshTokenRequest(refreshToken: String): Request {
fun refreshTokenRequest(oauth: OAuth): Request {
val formBody: RequestBody = FormBody.Builder()
.add("client_id", clientId)
.add("refresh_token", refreshToken)
.add("refresh_token", oauth.refresh_token)
.add("grant_type", "refresh_token")
.build()
return POST("$baseOAuthUrl/token", body = formBody)
// Add the Authorization header manually as this particular
// request is called by the interceptor itself so it doesn't reach
// the part where the token is added automatically.
val headers = Headers.Builder()
.add("Authorization", "Bearer ${oauth.access_token}")
.build()
return POST("$baseOAuthUrl/token", body = formBody, headers = headers)
}
private fun getPkceChallengeCode(): String {
@@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.data.track.myanimelist
import kotlinx.serialization.decodeFromString
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.internal.closeQuietly
import uy.kohesive.injekt.injectLazy
import java.io.IOException
@@ -24,11 +25,22 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
}
// Refresh access token if expired
if (oauth != null && oauth!!.isExpired()) {
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use {
if (it.isSuccessful) {
setAuth(json.decodeFromString(it.body!!.string()))
val newOauth = runCatching {
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
if (oauthResponse.isSuccessful) {
oauthResponse.parseAs<OAuth>()
} else {
oauthResponse.closeQuietly()
null
}
}
if (newOauth.getOrNull() == null) {
throw IOException("Failed to refresh the access token")
}
setAuth(newOauth.getOrNull())
}
if (oauth == null) {
throw IOException("No authentication token")
@@ -48,6 +48,7 @@ class AppUpdateChecker {
when (result) {
is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
is AppUpdateResult.NewUpdateFdroidInstallation -> AppUpdateNotifier(context).promptFdroidUpdate()
else -> {}
}
result
@@ -29,6 +29,7 @@ internal class AppUpdateNotifier(private val context: Context) {
fun promptUpdate(release: GithubRelease) {
val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
putExtra(AppUpdateService.EXTRA_DOWNLOAD_TITLE, release.version)
}
val updateIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
@@ -116,6 +117,7 @@ internal class AppUpdateNotifier(private val context: Context) {
setOnlyAlertOnce(false)
setProgress(0, 0, false)
setContentIntent(installIntent)
setOngoing(true)
clearActions()
addAction(
@@ -147,7 +147,7 @@ class AppUpdateService : Service() {
* @param context the application context.
* @param url the url to the new update.
*/
fun start(context: Context, url: String, title: String = context.getString(R.string.app_name)) {
fun start(context: Context, url: String, title: String? = context.getString(R.string.app_name)) {
if (!isRunning(context)) {
val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_TITLE, title)
@@ -4,7 +4,5 @@ sealed class LoadResult {
class Success(val extension: Extension.Installed) : LoadResult()
class Untrusted(val extension: Extension.Untrusted) : LoadResult()
class Error(val message: String? = null) : LoadResult() {
constructor(exception: Throwable) : this(exception.message)
}
object Error : LoadResult()
}
@@ -7,10 +7,12 @@ import android.content.IntentFilter
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import logcat.LogPriority
/**
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only
@@ -52,6 +54,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionInstalled(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
else -> {}
}
}
}
@@ -60,8 +63,8 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
// Not needed as a package can't be upgraded if the signature is different
is LoadResult.Untrusted -> {
}
is LoadResult.Untrusted -> {}
else -> {}
}
}
}
@@ -93,7 +96,10 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
*/
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent)
?: return LoadResult.Error("Package name not found")
if (pkgName == null) {
logcat(LogPriority.WARN) { "Package name not found" }
return LoadResult.Error
}
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
}
@@ -80,10 +80,12 @@ internal object ExtensionLoader {
context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
return LoadResult.Error(error)
logcat(LogPriority.ERROR, error)
return LoadResult.Error
}
if (!isPackageAnExtension(pkgInfo)) {
return LoadResult.Error("Tried to load a package that wasn't a extension")
logcat(LogPriority.WARN) { "Tried to load a package that wasn't a extension ($pkgName)" }
return LoadResult.Error
}
return loadExtension(context, pkgName, pkgInfo)
}
@@ -102,7 +104,8 @@ internal object ExtensionLoader {
pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
return LoadResult.Error(error)
logcat(LogPriority.ERROR, error)
return LoadResult.Error
}
val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
@@ -112,7 +115,7 @@ internal object ExtensionLoader {
if (versionName.isNullOrEmpty()) {
val exception = Exception("Missing versionName for extension $extName")
logcat(LogPriority.WARN, exception)
return LoadResult.Error(exception)
return LoadResult.Error
}
// Validate lib version
@@ -123,13 +126,14 @@ internal object ExtensionLoader {
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed",
)
logcat(LogPriority.WARN, exception)
return LoadResult.Error(exception)
return LoadResult.Error
}
val signatureHash = getSignatureHash(pkgInfo)
if (signatureHash == null) {
return LoadResult.Error("Package $pkgName isn't signed")
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
return LoadResult.Error
} else if (signatureHash !in trustedSignatures) {
val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, signatureHash)
logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" }
@@ -138,7 +142,8 @@ internal object ExtensionLoader {
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
if (!loadNsfwSource && isNsfw) {
return LoadResult.Error("NSFW extension $pkgName not allowed")
logcat(LogPriority.WARN) { "NSFW extension $pkgName not allowed" }
return LoadResult.Error
}
val hasReadme = appInfo.metaData.getInt(METADATA_HAS_README, 0) == 1
@@ -165,7 +170,7 @@ internal object ExtensionLoader {
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" }
return LoadResult.Error(e)
return LoadResult.Error
}
}
@@ -64,4 +64,8 @@ open /* SY <-- */ class NetworkHelper(context: Context) {
.addInterceptor(CloudflareInterceptor(context))
.build()
}
val defaultUserAgent by lazy {
preferences.defaultUserAgent().get()
}
}
@@ -15,8 +15,8 @@ class ProgressResponseBody(private val responseBody: ResponseBody, private val p
source(responseBody.source()).buffer()
}
override fun contentType(): MediaType {
return responseBody.contentType()!!
override fun contentType(): MediaType? {
return responseBody.contentType()
}
override fun contentLength(): Long {
@@ -9,7 +9,6 @@ import android.widget.Toast
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
@@ -109,7 +108,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
webview.settings.userAgentString = request.header("User-Agent")
?: HttpSource.DEFAULT_USER_AGENT
?: networkHelper.defaultUserAgent
webview.webViewClient = object : WebViewClientCompat() {
override fun onPageFinished(view: WebView, url: String) {
@@ -1,10 +1,14 @@
package eu.kanade.tachiyomi.network.interceptor
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.network.NetworkHelper
import okhttp3.Interceptor
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
class UserAgentInterceptor : Interceptor {
private val networkHelper: NetworkHelper by injectLazy()
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@@ -12,7 +16,7 @@ class UserAgentInterceptor : Interceptor {
val newRequest = originalRequest
.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.addHeader("User-Agent", networkHelper.defaultUserAgent)
.build()
chain.proceed(newRequest)
} else {
@@ -20,11 +20,13 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.EpubFile
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import logcat.LogPriority
import rx.Observable
import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo
@@ -286,41 +288,46 @@ class LocalSource(
}
private fun updateCover(chapter: SChapter, manga: SManga): File? {
return when (val format = getFormat(chapter)) {
is Format.Directory -> {
val entry = format.file.listFiles()
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
return try {
when (val format = getFormat(chapter)) {
is Format.Directory -> {
val entry = format.file.listFiles()
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream()) }
}
is Format.Zip -> {
ZipFile(format.file).use { zip ->
val entry = zip.entries().toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream()) }
}
is Format.Zip -> {
ZipFile(format.file).use { zip ->
val entry = zip.entries().toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
entry?.let { updateCover(context, manga, zip.getInputStream(it)) }
}
}
is Format.Rar -> {
Archive(format.file).use { archive ->
val entry = archive.fileHeaders
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
entry?.let { updateCover(context, manga, archive.getInputStream(it)) }
}
}
is Format.Epub -> {
EpubFile(format.file).use { epub ->
val entry = epub.getImagesFromPages()
.firstOrNull()
?.let { epub.getEntry(it) }
entry?.let { updateCover(context, manga, epub.getInputStream(it)) }
entry?.let { updateCover(context, manga, zip.getInputStream(it)) }
}
}
is Format.Rar -> {
Archive(format.file).use { archive ->
val entry = archive.fileHeaders
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
entry?.let { updateCover(context, manga, archive.getInputStream(it)) }
}
}
is Format.Epub -> {
EpubFile(format.file).use { epub ->
val entry = epub.getImagesFromPages()
.firstOrNull()
?.let { epub.getEntry(it) }
entry?.let { updateCover(context, manga, epub.getInputStream(it)) }
}
}
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Error updating cover for ${manga.title}" }
null
}
.also { coverCache.clearMemoryCache() }
}
@@ -398,7 +405,6 @@ class LocalSource(
}
}
// Create a .nomedia file
DiskUtil.createNoMediaFile(UniFile.fromFile(mangaDir), context)
manga.thumbnail_url = coverFile.absolutePath
@@ -253,7 +253,7 @@ open class SourceManager(private val context: Context) {
),
).associateBy { it.originalSourceQualifiedClassName }
val currentDelegatedSources = ListenMutableMap(mutableMapOf<Long, DelegatedSource>(), ::handleSourceLibrary)
val currentDelegatedSources: MutableMap<Long, DelegatedSource> = ListenMutableMap(mutableMapOf(), ::handleSourceLibrary)
data class DelegatedSource(
val sourceName: String,
@@ -264,19 +264,10 @@ open class SourceManager(private val context: Context) {
)
}
class ListenMutableMap<K, V>(private val internalMap: MutableMap<K, V>, val listener: () -> Unit) : MutableMap<K, V> {
override val size: Int
get() = internalMap.size
override fun containsKey(key: K): Boolean = internalMap.containsKey(key)
override fun containsValue(value: V): Boolean = internalMap.containsValue(value)
override fun get(key: K): V? = internalMap[key]
override fun isEmpty(): Boolean = internalMap.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = internalMap.entries
override val keys: MutableSet<K>
get() = internalMap.keys
override val values: MutableCollection<V>
get() = internalMap.values
private class ListenMutableMap<K, V>(
private val internalMap: MutableMap<K, V>,
private val listener: () -> Unit,
) : MutableMap<K, V> by internalMap {
override fun clear() {
val clearResult = internalMap.clear()
listener()
@@ -28,6 +28,11 @@ interface SManga : Serializable {
var initialized: Boolean
fun getGenres(): List<String>? {
if (genre.isNullOrBlank()) return null
return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
}
// SY -->
val originalTitle: String
get() = (this as? MangaImpl)?.ogTitle ?: title
@@ -104,7 +109,7 @@ fun SManga.toMangaInfo(): MangaInfo {
artist = this.artist ?: "",
author = this.author ?: "",
description = this.description ?: "",
genres = this.genre?.split(", ") ?: emptyList(),
genres = this.getGenres() ?: emptyList(),
status = this.status,
cover = this.thumbnail_url ?: "",
)
@@ -99,7 +99,7 @@ abstract class HttpSource : CatalogueSource {
* Headers builder for requests. Implementations can override this method for custom headers.
*/
protected open fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", DEFAULT_USER_AGENT)
add("User-Agent", network.defaultUserAgent)
}
/**
@@ -417,8 +417,4 @@ abstract class HttpSource : CatalogueSource {
this.delegate = delegate
}
// EXH <--
companion object {
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"
}
}
@@ -835,8 +835,8 @@ class EHentai(
)
},
AutoCompleteTags(
EHTags.getNamespaces0Tags().map { "$it:" } + EHTags.getAllTags(),
EHTags.getNamespaces0Tags().map { "$it:" },
EHTags.getNamespaces().map { "$it:" } + EHTags.getAllTags(),
EHTags.getNamespaces().map { "$it:" },
excludePrefix,
),
if (preferences.exhWatchedListDefaultState().get()) {
@@ -121,16 +121,16 @@ class MangaDex(delegate: HttpSource, val context: Context) :
MangaPlusHandler(network.client)
}
private val comikeyHandler by lazy {
ComikeyHandler(network.cloudflareClient)
ComikeyHandler(network.cloudflareClient, network.defaultUserAgent)
}
private val bilibiliHandler by lazy {
BilibiliHandler(network.cloudflareClient)
}
private val azukHandler by lazy {
AzukiHandler(network.client)
AzukiHandler(network.client, network.defaultUserAgent)
}
private val mangaHotHandler by lazy {
MangaHotHandler(network.client)
MangaHotHandler(network.client, network.defaultUserAgent)
}
private val pageHandler by lazy {
PageHandler(
@@ -98,7 +98,7 @@ abstract class DialogController : Controller {
/**
* Dismiss the dialog and pop this controller
*/
private fun dismissDialog() {
protected fun dismissDialog() {
if (dismissed) {
return
}
@@ -59,16 +59,17 @@ abstract class SearchableNucleusController<VB : ViewBinding, P : BasePresenter<*
val searchAutoComplete: SearchView.SearchAutoComplete = searchView.findViewById(
R.id.search_src_text,
)
searchAutoComplete.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
searchAutoComplete.addTextChangedListener(
object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(editable: Editable) {
editable.getSpans(0, editable.length, CharacterStyle::class.java)
.forEach { editable.removeSpan(it) }
}
},
override fun afterTextChanged(editable: Editable) {
editable.getSpans(0, editable.length, CharacterStyle::class.java)
.forEach { editable.removeSpan(it) }
}
},
)
searchView.queryTextEvents()
@@ -134,12 +135,12 @@ abstract class SearchableNucleusController<VB : ViewBinding, P : BasePresenter<*
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
onSearchMenuItemActionExpand(item)
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
val localSearchView = searchItem.actionView as SearchView
// if it is blank the flow event won't trigger so we would stay in a COLLAPSING state
@@ -119,6 +119,6 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
// SY <--
return preferences.lockAppAfter().get() <= 0 ||
Date().time >= preferences.lastAppUnlock().get() + 60 * 1000 * preferences.lockAppAfter().get()
Date().time >= preferences.lastAppClosed().get() + 60 * 1000 * preferences.lockAppAfter().get()
}
}
@@ -247,9 +247,13 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
}
private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String {
return when {
!pkgFactory.isNullOrEmpty() -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory$path"
else -> "$url/src/${pkgName.replace(".", "/")}$path"
return if (!pkgFactory.isNullOrEmpty()) {
when (path.isEmpty()) {
true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
}
} else {
url + "/src/" + pkgName.replace(".", "/") + path
}
}
@@ -474,8 +474,8 @@ class MigrationListController(bundle: Bundle? = null) :
}
private fun MenuItem.setIconTint(enabled: Boolean, color: Int) {
icon.mutate()
icon.setTint(color)
icon?.mutate()
icon?.setTint(color)
isEnabled = enabled
}
@@ -41,6 +41,7 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.AddDuplicateMangaDialog
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
@@ -483,19 +484,20 @@ open class BrowseSourceController(bundle: Bundle) :
* @param genreName the name of the genre
*/
fun searchWithGenre(genreName: String) {
presenter.sourceFilters = presenter.source.getFilterList()
val defaultFilters = presenter.source.getFilterList()
var filterList: FilterList? = null
var genreExists = false
filter@ for (sourceFilter in presenter.sourceFilters) {
filter@ for (sourceFilter in defaultFilters) {
if (sourceFilter is Filter.Group<*>) {
for (filter in sourceFilter.state) {
if (filter is Filter<*> && filter.name.equals(genreName, true)) {
when (filter) {
is Filter.TriState -> filter.state = 1
is Filter.CheckBox -> filter.state = true
else -> {}
}
filterList = presenter.sourceFilters
genreExists = true
break@filter
}
}
@@ -505,19 +507,20 @@ open class BrowseSourceController(bundle: Bundle) :
if (index != -1) {
sourceFilter.state = index
filterList = presenter.sourceFilters
genreExists = true
break
}
}
}
if (filterList != null) {
if (genreExists) {
presenter.sourceFilters = defaultFilters
filterSheet?.setFilters(presenter.filterItems)
showProgressBar()
adapter?.clear()
presenter.restartPager("", filterList)
presenter.restartPager("", defaultFilters)
} else {
searchWithQuery(genreName)
}
@@ -740,6 +743,7 @@ open class BrowseSourceController(bundle: Bundle) :
override fun onItemLongClick(position: Int) {
val activity = activity ?: return
val manga = (adapter?.getItem(position) as? SourceItem?)?.manga ?: return
val duplicateManga = presenter.getDuplicateLibraryManga(manga)
if (manga.favorite) {
MaterialAlertDialogBuilder(activity)
@@ -755,43 +759,53 @@ open class BrowseSourceController(bundle: Bundle) :
}
.show()
} else {
val categories = presenter.getCategories()
val defaultCategoryId = preferences.defaultCategory()
val defaultCategory = categories.find { it.id == defaultCategoryId }
if (duplicateManga != null) {
AddDuplicateMangaDialog(this, duplicateManga) { addToLibrary(manga, position) }
.showDialog(router)
} else {
addToLibrary(manga, position)
}
}
}
when {
// Default category set
defaultCategory != null -> {
presenter.moveMangaToCategory(manga, defaultCategory)
private fun addToLibrary(newManga: Manga, position: Int) {
val activity = activity ?: return
val categories = presenter.getCategories()
val defaultCategoryId = preferences.defaultCategory()
val defaultCategory = categories.find { it.id == defaultCategoryId }
presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position)
activity.toast(activity.getString(R.string.manga_added_library))
}
when {
// Default category set
defaultCategory != null -> {
presenter.moveMangaToCategory(newManga, defaultCategory)
// Automatic 'Default' or no categories
defaultCategoryId == 0 || categories.isEmpty() -> {
presenter.moveMangaToCategory(manga, null)
presenter.changeMangaFavorite(newManga)
adapter?.notifyItemChanged(position)
activity.toast(activity.getString(R.string.manga_added_library))
}
presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position)
activity.toast(activity.getString(R.string.manga_added_library))
}
// Automatic 'Default' or no categories
defaultCategoryId == 0 || categories.isEmpty() -> {
presenter.moveMangaToCategory(newManga, null)
// Choose a category
else -> {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = categories.map {
if (it.id in ids) {
QuadStateTextView.State.CHECKED.ordinal
} else {
QuadStateTextView.State.UNCHECKED.ordinal
}
}.toTypedArray()
presenter.changeMangaFavorite(newManga)
adapter?.notifyItemChanged(position)
activity.toast(activity.getString(R.string.manga_added_library))
}
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
}
// Choose a category
else -> {
val ids = presenter.getMangaCategoryIds(newManga)
val preselected = categories.map {
if (it.id in ids) {
QuadStateTextView.State.CHECKED.ordinal
} else {
QuadStateTextView.State.UNCHECKED.ordinal
}
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(newManga), categories, preselected)
.showDialog(router)
}
}
}
@@ -435,6 +435,10 @@ open class BrowseSourcePresenter(
return db.getCategories().executeAsBlocking()
}
fun getDuplicateLibraryManga(manga: Manga): Manga? {
return db.getDuplicateLibraryManga(manga).executeAsBlocking()
}
/**
* Gets the category id's the manga is in, if the manga is not in a category, returns the default id.
*
@@ -28,8 +28,7 @@ class DownloadHeaderHolder(view: View, adapter: FlexibleAdapter<*>) : Expandable
override fun onItemReleased(position: Int) {
super.onItemReleased(position)
binding.container.isDragged = false
mAdapter as DownloadAdapter
mAdapter.expandAll()
mAdapter.downloadItemListener.onItemReleased(position)
(mAdapter as DownloadAdapter).downloadItemListener.onItemReleased(position)
}
}
@@ -442,7 +442,7 @@ class LibraryController(
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
createOptionsMenu(menu, inflater, R.menu.library, R.id.action_search)
// Mutate the filter icon because it needs to be tinted and the resource is shared.
menu.findItem(R.id.action_filter).icon.mutate()
menu.findItem(R.id.action_filter).icon?.mutate()
// SY -->
menu.findItem(R.id.action_sync_favorites).isVisible = preferences.isHentaiEnabled().get()
@@ -472,7 +472,7 @@ class LibraryController(
// Tint icon if there's a filter active
if (settingsSheet.filters.hasActiveFilters()) {
val filterColor = activity!!.getResourceColor(R.attr.colorFilterActive)
filterItem.icon.setTint(filterColor)
filterItem.icon?.setTint(filterColor)
}
}
@@ -451,6 +451,7 @@ class LibrarySettingsSheet(
unreadBadge -> preferences.unreadBadge().set((item.checked))
localBadge -> preferences.localBadge().set((item.checked))
languageBadge -> preferences.languageBadge().set((item.checked))
else -> {}
}
adapter.notifyItemChanged(item)
}
@@ -473,6 +474,7 @@ class LibrarySettingsSheet(
item.checked = !item.checked
when (item) {
startReadingButton -> preferences.startReadingButton().set((item.checked))
else -> Unit
}
adapter.notifyItemChanged(item)
}
@@ -498,6 +500,7 @@ class LibrarySettingsSheet(
when (item) {
showTabs -> preferences.categoryTabs().set(item.checked)
showNumberOfItems -> preferences.categoryNumberOfItems().set(item.checked)
else -> {}
}
adapter.notifyItemChanged(item)
}
@@ -1,23 +0,0 @@
package eu.kanade.tachiyomi.ui.library
@Deprecated("Deprecated in favor for SortModeSetting")
object LibrarySort {
const val ALPHA = 0
const val LAST_READ = 1
const val LAST_CHECKED = 2
const val UNREAD = 3
const val TOTAL = 4
const val LATEST_CHAPTER = 6
const val CHAPTER_FETCH_DATE = 10
const val DATE_ADDED = 8
// SY -->
const val DRAG_AND_DROP = 7
const val TAG_LIST = 9
// SY <--
@Deprecated("Removed in favor of searching by source")
const val SOURCE = 5
}
@@ -520,7 +520,7 @@ class MainActivity : BaseActivity() {
// Binding sometimes isn't actually instantiated yet somehow
nav?.setOnItemSelectedListener(null)
binding?.toolbar.setNavigationOnClickListener(null)
binding?.toolbar?.setNavigationOnClickListener(null)
}
override fun onBackPressed() {
@@ -0,0 +1,48 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Dialog
import android.os.Bundle
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import uy.kohesive.injekt.injectLazy
class AddDuplicateMangaDialog(bundle: Bundle? = null) : DialogController(bundle) {
private val sourceManager: SourceManager by injectLazy()
private lateinit var libraryManga: Manga
private lateinit var onAddToLibrary: () -> Unit
constructor(
target: Controller,
libraryManga: Manga,
onAddToLibrary: () -> Unit,
) : this() {
targetController = target
this.libraryManga = libraryManga
this.onAddToLibrary = onAddToLibrary
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val source = sourceManager.getOrStub(libraryManga.source)
return MaterialAlertDialogBuilder(activity!!)
.setMessage(activity?.getString(R.string.confirm_manga_add_duplicate, source.name))
.setPositiveButton(activity?.getString(R.string.action_add)) { _, _ ->
onAddToLibrary()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(activity?.getString(R.string.action_show_manga)) { _, _ ->
dismissDialog()
router.pushController(MangaController(libraryManga.id!!).withFadeTransaction())
}
.setCancelable(true)
.create()
}
}
@@ -673,18 +673,8 @@ class MangaController :
private fun showAddDuplicateDialog(newManga: Manga, libraryManga: Manga) {
activity?.let {
val source = sourceManager.getOrStub(libraryManga.source)
MaterialAlertDialogBuilder(it).apply {
setMessage(activity?.getString(R.string.confirm_manga_add_duplicate, source.name))
setPositiveButton(activity?.getString(R.string.action_add)) { _, _ ->
addToLibrary(newManga)
}
setNegativeButton(activity?.getString(R.string.action_cancel)) { _, _ -> }
setNeutralButton(activity?.getString(R.string.action_show_manga)) { _, _ ->
router.pushController(MangaController(libraryManga).withFadeTransaction())
}
setCancelable(true)
}.create().show()
AddDuplicateMangaDialog(this, libraryManga) { addToLibrary(newManga) }
.showDialog(router)
}
}
@@ -161,6 +161,7 @@ class ChaptersSettingsSheet(
downloaded -> presenter.setDownloadedFilter(newState)
unread -> presenter.setUnreadFilter(newState)
bookmarked -> presenter.setBookmarkedFilter(newState)
else -> {}
}
initModels()
@@ -19,6 +19,7 @@ class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundl
constructor(update: AppUpdateResult.NewUpdate) : this(
bundleOf(
BODY_KEY to update.release.info,
VERSION_KEY to update.release.version,
RELEASE_URL_KEY to update.release.releaseLink,
DOWNLOAD_URL_KEY to update.release.getDownloadLink(),
),
@@ -36,7 +37,8 @@ class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundl
applicationContext?.let { context ->
// Start download
val url = args.getString(DOWNLOAD_URL_KEY)!!
AppUpdateService.start(context, url)
val version = args.getString(VERSION_KEY)
AppUpdateService.start(context, url, version)
}
}
.setNeutralButton(R.string.update_check_open) { _, _ ->
@@ -55,5 +57,6 @@ class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundl
}
private const val BODY_KEY = "NewUpdateDialogController.body"
private const val VERSION_KEY = "NewUpdateDialogController.version"
private const val RELEASE_URL_KEY = "NewUpdateDialogController.release_url"
private const val DOWNLOAD_URL_KEY = "NewUpdateDialogController.download_url"
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.reader.loader
import android.content.Context
import com.github.junrar.exception.UnsupportedRarV5Exception
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
@@ -116,7 +117,11 @@ class ChapterLoader(
when (format) {
is LocalSource.Format.Directory -> DirectoryPageLoader(format.file)
is LocalSource.Format.Zip -> ZipPageLoader(format.file)
is LocalSource.Format.Rar -> RarPageLoader(format.file)
is LocalSource.Format.Rar -> try {
RarPageLoader(format.file)
} catch (e: UnsupportedRarV5Exception) {
error(context.getString(R.string.loader_rar5_error))
}
is LocalSource.Format.Epub -> EpubPageLoader(format.file)
}
}
@@ -10,9 +10,9 @@ data class ReaderChapter(val chapter: Chapter) {
var state: State =
State.Wait
set(value) {
field = value
stateRelay.call(value)
}
field = value
stateRelay.call(value)
}
private val stateRelay by lazy { BehaviorRelay.create(state) }
@@ -34,27 +34,28 @@ class ReaderSettingsSheet(
behavior.halfExpandedRatio = 0.25f
val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
override fun onTabSelected(tab: TabLayout.Tab?) {
val isFilterTab = tab?.position == filterTabIndex
binding.tabs.addOnTabSelectedListener(
object : SimpleTabSelectedListener() {
override fun onTabSelected(tab: TabLayout.Tab?) {
val isFilterTab = tab?.position == filterTabIndex
// Remove dimmed backdrop so color filter changes can be previewed
backgroundDimAnimator.run {
if (isFilterTab) {
if (animatedFraction < 1f) {
start()
// Remove dimmed backdrop so color filter changes can be previewed
backgroundDimAnimator.run {
if (isFilterTab) {
if (animatedFraction < 1f) {
start()
}
} else if (animatedFraction > 0f) {
reverse()
}
} else if (animatedFraction > 0f) {
reverse()
}
// Hide toolbars
if (activity.menuVisible != !isFilterTab) {
activity.setMenuVisibility(!isFilterTab)
}
}
// Hide toolbars
if (activity.menuVisible != !isFilterTab) {
activity.setMenuVisibility(!isFilterTab)
}
}
},
},
)
if (showColorFilterSettings) {
@@ -249,6 +249,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
ZoomStartPosition.LEFT -> setScaleAndCenter(scale, PointF(0F, 0F))
ZoomStartPosition.RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0F))
ZoomStartPosition.CENTER -> setScaleAndCenter(scale, center.also { it?.y = 0F })
else -> {}
}
}
@@ -310,7 +311,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
return true
}
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
this@ReaderPageImageView.onViewClicked()
return super.onSingleTapConfirmed(e)
}
@@ -1,15 +1,24 @@
package eu.kanade.tachiyomi.ui.reader.viewer
import android.content.Context
import android.text.SpannableStringBuilder
import android.text.style.ImageSpan
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.util.system.dpToPx
import kotlin.math.roundToInt
class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) {
@@ -21,10 +30,11 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
}
fun bind(transition: ChapterTransition) {
fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) {
manga ?: return
when (transition) {
is ChapterTransition.Prev -> bindPrevChapterTransition(transition)
is ChapterTransition.Next -> bindNextChapterTransition(transition)
is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga)
is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga)
}
missingChapterWarning(transition)
}
@@ -32,20 +42,30 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
/**
* Binds a previous chapter transition on this view and subscribes to the page load status.
*/
private fun bindPrevChapterTransition(transition: ChapterTransition) {
val prevChapter = transition.to
private fun bindPrevChapterTransition(
transition: ChapterTransition,
downloadManager: DownloadManager,
manga: Manga,
) {
val prevChapter = transition.to?.chapter
val hasPrevChapter = prevChapter != null
binding.lowerText.isVisible = hasPrevChapter
if (hasPrevChapter) {
binding.lowerText.isVisible = prevChapter != null
if (prevChapter != null) {
binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START
val isPrevDownloaded = downloadManager.isChapterDownloaded(
prevChapter,
manga,
)
val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader
binding.upperText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_previous)) }
append("\n${prevChapter!!.chapter.name}")
append("\n${prevChapter.name}")
if (isPrevDownloaded) addDLImageSpan()
}
binding.lowerText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_current)) }
append("\n${transition.from.chapter.name}")
if (isCurrentDownloaded) addDLImageSpan()
}
} else {
binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER
@@ -56,20 +76,30 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
/**
* Binds a next chapter transition on this view and subscribes to the load status.
*/
private fun bindNextChapterTransition(transition: ChapterTransition) {
val nextChapter = transition.to
private fun bindNextChapterTransition(
transition: ChapterTransition,
downloadManager: DownloadManager,
manga: Manga,
) {
val nextChapter = transition.to?.chapter
val hasNextChapter = nextChapter != null
binding.lowerText.isVisible = hasNextChapter
if (hasNextChapter) {
binding.lowerText.isVisible = nextChapter != null
if (nextChapter != null) {
binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START
val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader
val isNextDownloaded = downloadManager.isChapterDownloaded(
nextChapter,
manga,
)
binding.upperText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_finished)) }
append("\n${transition.from.chapter.name}")
if (isCurrentDownloaded) addDLImageSpan()
}
binding.lowerText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_next)) }
append("\n${nextChapter!!.chapter.name}")
append("\n${nextChapter.name}")
if (isNextDownloaded) addDLImageSpan()
}
} else {
binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER
@@ -77,6 +107,17 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
}
}
private fun SpannableStringBuilder.addDLImageSpan() {
val icon = ContextCompat.getDrawable(context, R.drawable.ic_offline_pin_24dp)?.mutate()
?.apply {
val size = binding.lowerText.textSize + 4.dpToPx
setTint(binding.lowerText.currentTextColor)
setBounds(0, 0, size.roundToInt(), size.roundToInt())
} ?: return
append(" ")
inSpans(ImageSpan(icon)) { append("image") }
}
private fun missingChapterWarning(transition: ChapterTransition) {
if (transition.to == null) {
binding.warning.isVisible = false
@@ -24,6 +24,7 @@ import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.util.concurrent.TimeUnit
@@ -332,7 +333,7 @@ class PagerPageHolder(
.subscribe({}, {})
}
private fun process(page: ReaderPage, imageStream: InputStream): InputStream {
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
if (!viewer.config.dualPageSplit) {
return imageStream
}
@@ -341,7 +342,7 @@ class PagerPageHolder(
return splitInHalf(imageStream)
}
val isDoublePage = ImageUtil.isDoublePage(imageStream)
val isDoublePage = ImageUtil.isWideImage(imageStream)
if (!isDoublePage) {
return imageStream
}
@@ -61,7 +61,7 @@ class PagerTransitionHolder(
addView(transitionView)
addView(pagesContainer)
transitionView.bind(transition)
transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga)
transition.to?.let { observeStatus(it) }
}
@@ -11,6 +11,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.viewpager.widget.ViewPager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
@@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import uy.kohesive.injekt.injectLazy
import kotlin.math.min
/**
@@ -29,6 +31,8 @@ import kotlin.math.min
@Suppress("LeakingThis")
abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
val downloadManager: DownloadManager by injectLazy()
val scope = MainScope()
/**
@@ -52,7 +52,7 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
* Scale listener used to delegate events to the recycler view.
*/
inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
recycler?.onScaleBegin()
return true
}
@@ -71,13 +71,13 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
* Fling listener used to delegate events to the recycler view.
*/
inner class FlingListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float,
): Boolean {
@@ -23,6 +23,7 @@ import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.io.BufferedInputStream
import java.io.InputStream
import java.util.concurrent.TimeUnit
@@ -272,12 +273,12 @@ class WebtoonPageHolder(
addSubscription(readImageHeaderSubscription)
}
private fun process(imageStream: InputStream): InputStream {
private fun process(imageStream: BufferedInputStream): InputStream {
if (!viewer.config.dualPageSplit) {
return imageStream
}
val isDoublePage = ImageUtil.isDoublePage(imageStream)
val isDoublePage = ImageUtil.isWideImage(imageStream)
if (!isDoublePage) {
return imageStream
}
@@ -63,7 +63,7 @@ class WebtoonTransitionHolder(
* Binds the given [transition] with this view holder, subscribing to its state.
*/
fun bind(transition: ChapterTransition) {
transitionView.bind(transition)
transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga)
transition.to?.let { observeStatus(it, transition) }
}
@@ -11,6 +11,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.WebtoonLayoutManager
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
@@ -24,6 +25,7 @@ import kotlinx.coroutines.cancel
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import kotlin.math.max
import kotlin.math.min
@@ -32,6 +34,8 @@ import kotlin.math.min
*/
class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true, private val tapByPage: Boolean = false) : BaseViewer {
val downloadManager: DownloadManager by injectLazy()
private val scope = MainScope()
/**
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
import java.util.Date
/**
* Blank activity with a BiometricPrompt.
@@ -39,7 +38,6 @@ class UnlockActivity : BaseActivity() {
) {
super.onAuthenticationSucceeded(activity, result)
SecureActivityDelegate.locked = false
preferences.lastAppUnlock().set(Date().time)
finish()
}
},
@@ -219,6 +219,28 @@ class SettingsAdvancedController : SettingsController() {
true
}
}
editTextPreference {
key = Keys.defaultUserAgent
titleRes = R.string.pref_user_agent_string
text = preferences.defaultUserAgent().get()
summary = network.defaultUserAgent
onChange {
activity?.toast(R.string.requires_app_restart)
true
}
}
if (preferences.defaultUserAgent().isSet()) {
preference {
key = "pref_reset_user_agent"
titleRes = R.string.pref_reset_user_agent_string
onClick {
preferences.defaultUserAgent().delete()
activity?.toast(R.string.requires_app_restart)
}
}
}
}
preferenceCategory {
@@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.util.preference.multiSelectListPreference
import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.toast
@@ -72,6 +73,12 @@ class SettingsDownloadController : SettingsController() {
bindTo(preferences.saveChaptersAsCBZ())
titleRes = R.string.save_chapter_as_cbz
}
switchPreference {
bindTo(preferences.splitTallImages())
titleRes = R.string.split_tall_images
summaryRes = R.string.split_tall_images_summary
}
preferenceCategory {
titleRes = R.string.pref_category_delete_chapters
@@ -121,13 +121,13 @@ class SettingsMainController : SettingsController() {
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
preferences.lastSearchQuerySearchSettings().set("") // reset saved search query
router.pushController(SettingsSearchController().withFadeTransaction())
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
return true
}
},
@@ -74,11 +74,11 @@ class SettingsSearchController :
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
router.popCurrentController()
return false
}
@@ -166,12 +166,12 @@ class WebViewActivity : BaseActivity() {
menu.findItem(R.id.action_web_back).apply {
isEnabled = binding.webview.canGoBack()
icon.setTint(if (binding.webview.canGoBack()) iconTintColor else translucentIconTintColor)
icon?.setTint(if (binding.webview.canGoBack()) iconTintColor else translucentIconTintColor)
}
menu.findItem(R.id.action_web_forward).apply {
isEnabled = binding.webview.canGoForward()
icon.setTint(if (binding.webview.canGoForward()) iconTintColor else translucentIconTintColor)
icon?.setTint(if (binding.webview.canGoForward()) iconTintColor else translucentIconTintColor)
}
return super.onPrepareOptionsMenu(menu)
@@ -46,8 +46,8 @@ object ChapterRecognition {
// Get chapter title with lower case
var name = chapter.name.lowercase()
// Remove comma's from chapter.
name = name.replace(',', '.')
// Remove comma's or hyphens.
name = name.replace(',', '.').replace('-', '.')
// Remove unwanted white spaces.
unwantedWhiteSpace.findAll(name).let {
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.util.chapter
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending()): (Chapter, Chapter) -> Int {
return when (manga.sorting) {
@@ -11,13 +10,13 @@ fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending(
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
}
Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending) {
true -> { c1, c2 -> c2.chapter_number.toString().compareToCaseInsensitiveNaturalOrder(c1.chapter_number.toString()) }
false -> { c1, c2 -> c1.chapter_number.toString().compareToCaseInsensitiveNaturalOrder(c2.chapter_number.toString()) }
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
}
Manga.CHAPTER_SORTING_UPLOAD_DATE -> when (sortDescending) {
true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) }
false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
}
else -> throw NotImplementedError("Unimplemented sorting method")
else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
}
}
@@ -1,12 +1,11 @@
package eu.kanade.tachiyomi.util.storage
import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Environment
import android.os.StatFs
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.lang.Hash
import java.io.File
@@ -74,21 +73,11 @@ object DiskUtil {
}
}
/**
* Scans the given file so that it can be shown in gallery apps, for example.
*/
fun scanMedia(context: Context, file: File) {
scanMedia(context, file.toUri())
}
/**
* Scans the given file so that it can be shown in gallery apps, for example.
*/
fun scanMedia(context: Context, uri: Uri) {
val action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
val mediaScanIntent = Intent(action)
mediaScanIntent.data = uri
context.sendBroadcast(mediaScanIntent)
MediaScannerConnection.scanFile(context, arrayOf(uri.path), null, null)
}
/**
@@ -47,6 +47,7 @@ import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import kotlin.math.max
import kotlin.math.roundToInt
private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720
@@ -166,6 +167,9 @@ fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermissio
}
}
val getDisplayMaxHeightInPx: Int
get() = Resources.getSystem().displayMetrics.let { max(it.heightPixels, it.widthPixels) }
/**
* Converts to dp.
*/
@@ -258,7 +262,7 @@ fun Context.openInBrowser(uri: Uri, forceDefaultBrowser: Boolean = false) {
}
fun Context.defaultBrowserPackageName(): String? {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
val browserIntent = Intent(Intent.ACTION_VIEW, "http://".toUri())
return packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
?.activityInfo?.packageName
?.takeUnless { it in DeviceUtil.invalidDefaultBrowsers }
@@ -315,8 +319,8 @@ fun Context.isNightMode(): Boolean {
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=348;drc=e28752c96fc3fb4d3354781469a1af3dbded4898
*/
fun Context.createReaderThemeContext(): Context {
val prefs = Injekt.get<PreferencesHelper>()
val isDarkBackground = when (prefs.readerTheme().get()) {
val preferences = Injekt.get<PreferencesHelper>()
val isDarkBackground = when (preferences.readerTheme().get()) {
1, 2 -> true // Black, Gray
3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default
else -> false // White
@@ -329,7 +333,7 @@ fun Context.createReaderThemeContext(): Context {
val wrappedContext = ContextThemeWrapper(this, R.style.Theme_Tachiyomi)
wrappedContext.applyOverrideConfiguration(overrideConf)
ThemingDelegate.getThemeResIds(prefs.appTheme().get(), prefs.themeDarkAmoled().get())
ThemingDelegate.getThemeResIds(preferences.appTheme().get(), preferences.themeDarkAmoled().get())
.forEach { wrappedContext.theme.applyStyle(it, true) }
return wrappedContext
}
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
@@ -17,16 +18,23 @@ import androidx.core.graphics.alpha
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.blue
import androidx.core.graphics.createBitmap
import androidx.core.graphics.get
import androidx.core.graphics.green
import androidx.core.graphics.red
import com.hippo.unifile.UniFile
import logcat.LogPriority
import tachiyomi.decoder.Format
import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.net.URLConnection
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
object ImageUtil {
@@ -76,8 +84,7 @@ object ImageUtil {
Format.Webp -> type.isAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
else -> false
}
} catch (e: Exception) {
}
} catch (e: Exception) { /* Do Nothing */ }
return false
}
@@ -109,19 +116,12 @@ object ImageUtil {
}
/**
* Check whether the image is a double-page spread
* Check whether the image is wide (which we consider a double-page spread).
*
* @return true if the width is greater than the height
*/
fun isDoublePage(imageStream: InputStream): Boolean {
imageStream.mark(imageStream.available() + 1)
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
imageStream.reset()
fun isWideImage(imageStream: BufferedInputStream): Boolean {
val options = extractImageOptions(imageStream)
return options.outWidth > options.outHeight
}
@@ -188,6 +188,111 @@ object ImageUtil {
RIGHT, LEFT
}
/**
* Check whether the image is considered a tall image.
*
* @return true if the height:width ratio is greater than 3.
*/
private fun isTallImage(imageStream: InputStream): Boolean {
val options = extractImageOptions(imageStream, resetAfterExtraction = false)
return (options.outHeight / options.outWidth) > 3
}
/**
* Splits tall images to improve performance of reader
*/
fun splitTallImage(imageFile: UniFile, imageFilePath: String): Boolean {
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
return true
}
val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { inJustDecodeBounds = false }
// Values are stored as they get modified during split loop
val imageHeight = options.outHeight
val imageWidth = options.outWidth
val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
// -1 so it doesn't try to split when imageHeight = getDisplayHeightInPx
val partCount = (imageHeight - 1) / splitHeight + 1
val optimalSplitHeight = imageHeight / partCount
val splitDataList = (0 until partCount).fold(mutableListOf<SplitData>()) { list, index ->
list.apply {
// Only continue if the list is empty or there is image remaining
if (isEmpty() || imageHeight > last().bottomOffset) {
val topOffset = index * optimalSplitHeight
var outputImageHeight = min(optimalSplitHeight, imageHeight - topOffset)
val remainingHeight = imageHeight - (topOffset + outputImageHeight)
// If remaining height is smaller or equal to 1/3th of
// optimal split height then include it in current page
if (remainingHeight <= (optimalSplitHeight / 3)) {
outputImageHeight += remainingHeight
}
add(SplitData(index, topOffset, outputImageHeight))
}
}
}
val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BitmapRegionDecoder.newInstance(imageFile.openInputStream())
} else {
@Suppress("DEPRECATION")
BitmapRegionDecoder.newInstance(imageFile.openInputStream(), false)
}
if (bitmapRegionDecoder == null) {
logcat { "Failed to create new instance of BitmapRegionDecoder" }
return false
}
logcat {
"Splitting image with height of $imageHeight into $partCount part " +
"with estimated ${optimalSplitHeight}px height per split"
}
return try {
splitDataList.forEach { splitData ->
val splitPath = splitImagePath(imageFilePath, splitData.index)
val region = Rect(0, splitData.topOffset, imageWidth, splitData.bottomOffset)
FileOutputStream(splitPath).use { outputStream ->
val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
splitBitmap.recycle()
}
logcat {
"Success: Split #${splitData.index + 1} with topOffset=${splitData.topOffset} " +
"height=${splitData.outputImageHeight} bottomOffset=${splitData.bottomOffset}"
}
}
imageFile.delete()
true
} catch (e: Exception) {
// Image splits were not successfully saved so delete them and keep the original image
splitDataList
.map { splitImagePath(imageFilePath, it.index) }
.forEach { File(it).delete() }
logcat(LogPriority.ERROR, e)
false
} finally {
bitmapRegionDecoder.recycle()
}
}
private fun splitImagePath(imageFilePath: String, index: Int) =
imageFilePath.substringBeforeLast(".") + "__${"%03d".format(index + 1)}.jpg"
data class SplitData(
val index: Int,
val topOffset: Int,
val outputImageHeight: Int,
) {
val bottomOffset = topOffset + outputImageHeight
}
/**
* Algorithm for determining what background to accompany a comic/manga page
*/
@@ -212,14 +317,14 @@ object ImageUtil {
val leftOffsetX = left - offsetX
val rightOffsetX = right + offsetX
val topLeftPixel = image.getPixel(left, top)
val topRightPixel = image.getPixel(right, top)
val midLeftPixel = image.getPixel(left, midY)
val midRightPixel = image.getPixel(right, midY)
val topCenterPixel = image.getPixel(midX, top)
val botLeftPixel = image.getPixel(left, bot)
val bottomCenterPixel = image.getPixel(midX, bot)
val botRightPixel = image.getPixel(right, bot)
val topLeftPixel = image[left, top]
val topRightPixel = image[right, top]
val midLeftPixel = image[left, midY]
val midRightPixel = image[right, midY]
val topCenterPixel = image[midX, top]
val botLeftPixel = image[left, bot]
val bottomCenterPixel = image[midX, bot]
val botRightPixel = image[right, bot]
val topLeftIsDark = topLeftPixel.isDark()
val topRightIsDark = topRightPixel.isDark()
@@ -272,8 +377,8 @@ object ImageUtil {
var whiteStreak = false
val notOffset = x == left || x == right
inner@ for ((index, y) in (0 until image.height step image.height / 25).withIndex()) {
val pixel = image.getPixel(x, y)
val pixelOff = image.getPixel(x + (if (x < image.width / 2) -offsetX else offsetX), y)
val pixel = image[x, y]
val pixelOff = image[x + (if (x < image.width / 2) -offsetX else offsetX), y]
if (pixel.isWhite()) {
whitePixelsStreak++
whitePixels++
@@ -364,8 +469,8 @@ object ImageUtil {
val topCornersIsDark = topLeftIsDark && topRightIsDark
val botCornersIsDark = botLeftIsDark && botRightIsDark
val topOffsetCornersIsDark = image.getPixel(leftOffsetX, top).isDark() && image.getPixel(rightOffsetX, top).isDark()
val botOffsetCornersIsDark = image.getPixel(leftOffsetX, bot).isDark() && image.getPixel(rightOffsetX, bot).isDark()
val topOffsetCornersIsDark = image[leftOffsetX, top].isDark() && image[rightOffsetX, top].isDark()
val botOffsetCornersIsDark = image[leftOffsetX, bot].isDark() && image[rightOffsetX, bot].isDark()
val gradient = when {
darkBG && botCornersIsWhite -> {
@@ -394,15 +499,31 @@ object ImageUtil {
)
}
private fun Int.isDark(): Boolean =
private fun @receiver:ColorInt Int.isDark(): Boolean =
red < 40 && blue < 40 && green < 40 && alpha > 200
private fun Int.isCloseTo(other: Int): Boolean =
private fun @receiver:ColorInt Int.isCloseTo(other: Int): Boolean =
abs(red - other.red) < 30 && abs(green - other.green) < 30 && abs(blue - other.blue) < 30
private fun Int.isWhite(): Boolean =
private fun @receiver:ColorInt Int.isWhite(): Boolean =
red + blue + green > 740
/**
* Used to check an image's dimensions without loading it in the memory.
*/
private fun extractImageOptions(
imageStream: InputStream,
resetAfterExtraction: Boolean = true,
): BitmapFactory.Options {
imageStream.mark(imageStream.available() + 1)
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
if (resetAfterExtraction) imageStream.reset()
return options
}
// Android doesn't include some mappings
private val SUPPLEMENTARY_MIMETYPE_MAPPING = mapOf(
// https://issuetracker.google.com/issues/182703810
@@ -115,12 +115,13 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor(
.setInterpolator(interpolator)
.setDuration(duration)
.applySystemAnimatorScale(context)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
currentAnimator = null
postInvalidate()
}
},
.setListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
currentAnimator = null
postInvalidate()
}
},
)
}
@@ -37,12 +37,13 @@ class ThemesPreference @JvmOverloads constructor(context: Context, attrs: Attrib
recycler?.adapter = adapter
// Retain scroll position on activity recreate after changing theme
recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
lastScrollPosition = recyclerView.computeHorizontalScrollOffset()
}
},
recycler?.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
lastScrollPosition = recyclerView.computeHorizontalScrollOffset()
}
},
)
lastScrollPosition?.let { scrollToOffset(it) }
}
@@ -45,11 +45,12 @@ class BottomSheetViewPager @JvmOverloads constructor(
}
init {
addOnPageChangeListener(object : SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
requestLayout()
}
},
addOnPageChangeListener(
object : SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
requestLayout()
}
},
)
}
}
+31 -27
View File
@@ -31,13 +31,13 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.Hitomi
import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.util.preference.minusAssign
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.logcat
import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE
import exh.log.xLogW
@@ -308,36 +308,40 @@ object EXHMigrations {
}
}
if (oldVersion under 20) {
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
try {
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0 /* ALPHABETICAL */)
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
val newSortingMode = when (oldSortingMode) {
LibrarySort.ALPHA -> SortModeSetting.ALPHABETICAL
LibrarySort.LAST_READ -> SortModeSetting.LAST_READ
LibrarySort.LAST_CHECKED -> SortModeSetting.LAST_CHECKED
LibrarySort.UNREAD -> SortModeSetting.UNREAD
LibrarySort.TOTAL -> SortModeSetting.TOTAL_CHAPTERS
LibrarySort.LATEST_CHAPTER -> SortModeSetting.LATEST_CHAPTER
LibrarySort.CHAPTER_FETCH_DATE -> SortModeSetting.DATE_FETCHED
LibrarySort.DATE_ADDED -> SortModeSetting.DATE_ADDED
LibrarySort.DRAG_AND_DROP -> SortModeSetting.DRAG_AND_DROP
LibrarySort.TAG_LIST -> SortModeSetting.TAG_LIST
else -> SortModeSetting.ALPHABETICAL
}
val newSortingMode = when (oldSortingMode) {
0 -> SortModeSetting.ALPHABETICAL
1 -> SortModeSetting.LAST_READ
2 -> SortModeSetting.LAST_CHECKED
3 -> SortModeSetting.UNREAD
4 -> SortModeSetting.TOTAL_CHAPTERS
6 -> SortModeSetting.LATEST_CHAPTER
7 -> SortModeSetting.DRAG_AND_DROP
8 -> SortModeSetting.DATE_ADDED
9 -> SortModeSetting.TAG_LIST
10 -> SortModeSetting.DATE_FETCHED
else -> SortModeSetting.ALPHABETICAL
}
val newSortingDirection = when (oldSortingDirection) {
true -> SortDirectionSetting.ASCENDING
else -> SortDirectionSetting.DESCENDING
}
val newSortingDirection = when (oldSortingDirection) {
true -> SortDirectionSetting.ASCENDING
else -> SortDirectionSetting.DESCENDING
}
prefs.edit(commit = true) {
remove(PreferenceKeys.librarySortingMode)
remove(PreferenceKeys.librarySortingDirection)
}
prefs.edit(commit = true) {
remove(PreferenceKeys.librarySortingMode)
remove(PreferenceKeys.librarySortingDirection)
}
prefs.edit {
putString(PreferenceKeys.librarySortingMode, newSortingMode.name)
putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name)
prefs.edit {
putString(PreferenceKeys.librarySortingMode, newSortingMode.name)
putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name)
}
} catch (e: Exception) {
logcat(throwable = e) { "Already done migration" }
}
}
if (oldVersion under 21) {
+6 -5
View File
@@ -6,31 +6,32 @@ import exh.eh.tags.Character
import exh.eh.tags.Cosplayer
import exh.eh.tags.Female
import exh.eh.tags.Group
import exh.eh.tags.Group2
import exh.eh.tags.Language
import exh.eh.tags.Male
import exh.eh.tags.Mixed
import exh.eh.tags.Other
import exh.eh.tags.Parody
import exh.eh.tags.ReClass
import exh.eh.tags.Reclass
object EHTags {
fun getAllTags() = listOf(
fun getAllTags(): List<String> = listOf(
Female.getTags(),
Male.getTags(),
Language.getTags(),
ReClass.getTags(),
Reclass.getTags(),
Mixed.getTags(),
Other.getTags(),
Cosplayer.getTags(),
Parody.getTags(),
Character.getTags(),
Group.getTags(),
Group2.getTags(),
Artist.getTags(),
Artist2.getTags(),
).flatten().flatten()
fun getNamespaces0Tags() = listOf(
fun getNamespaces(): List<String> = listOf(
"reclass",
"language",
"parody",
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
+77 -2
View File
@@ -1,46 +1,121 @@
package exh.eh.tags
object Cosplayer : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"cosplayer:abaoyeshituniang",
"cosplayer:ai lei jiang",
"cosplayer:akane araragi",
"cosplayer:akemi101xoxo",
"cosplayer:aleksandra bodler",
"cosplayer:alicekyo",
"cosplayer:alin ma",
"cosplayer:alisa kiss",
"cosplayer:alodia gosiengfiao",
"cosplayer:amanda welp",
"cosplayer:anastasia komori",
"cosplayer:aokotan",
"cosplayer:arisa mizuhara",
"cosplayer:arty huang",
"cosplayer:ashiya noriko",
"cosplayer:atsuki",
"cosplayer:ayaka matsunaga",
"cosplayer:bailey jay",
"cosplayer:banbanko",
"cosplayer:bishoujomom",
"cosplayer:bobbi starr",
"cosplayer:carry key",
"cosplayer:charles dera",
"cosplayer:chunmomo",
"cosplayer:comonun",
"cosplayer:danielle vedovelli",
"cosplayer:darling cute",
"cosplayer:dillion harper",
"cosplayer:donnaloli",
"cosplayer:evenink",
"cosplayer:fe galvao",
"cosplayer:fluffy nemu",
"cosplayer:franxcos",
"cosplayer:g44 wa kizutsukanai",
"cosplayer:gumiho hannya",
"cosplayer:hane ame",
"cosplayer:helly von valentine",
"cosplayer:hessakai",
"cosplayer:hey shika",
"cosplayer:higurashi rin",
"cosplayer:himeecosplay",
"cosplayer:hinaughtya",
"cosplayer:holly wolf",
"cosplayer:imokawa naoko",
"cosplayer:iori moe",
"cosplayer:ishikawa asami",
"cosplayer:jaycee",
"cosplayer:jessica nigri",
"cosplayer:jill",
"cosplayer:kalinka fox",
"cosplayer:kanda midori",
"cosplayer:kaya huang",
"cosplayer:kimmie mi",
"cosplayer:kitami eri",
"cosplayer:koyama rikako",
"cosplayer:kqueentsun",
"cosplayer:kurumi.",
"cosplayer:kuuko w",
"cosplayer:lenfried",
"cosplayer:lewdoart",
"cosplayer:lovelyspacekitten",
"cosplayer:marie-claude bourbonnais",
"cosplayer:meikoui",
"cosplayer:miih cosplay",
"cosplayer:mikomin",
"cosplayer:misa daidai",
"cosplayer:mizhimaoqiu",
"cosplayer:mochizuki eiko",
"cosplayer:momoiro reku",
"cosplayer:momokun",
"cosplayer:nadyasonika",
"cosplayer:neroko kaigan",
"cosplayer:niannian d",
"cosplayer:nicky",
"cosplayer:niyeye",
"cosplayer:nora fawn",
"cosplayer:octokuro",
"cosplayer:oichi",
"cosplayer:okada yui",
"cosplayer:penkarui",
"cosplayer:punk macarroni",
"cosplayer:queenie",
"cosplayer:rio-chan",
"cosplayer:rioko",
"cosplayer:rocksy light",
"cosplayer:rolyatistaylor",
"cosplayer:saiwari ph",
"cosplayer:saku",
"cosplayer:sakurai hinoki",
"cosplayer:sandykuroneko",
"cosplayer:saotome love",
"cosplayer:sawaka",
"cosplayer:sexyflowerwater",
"cosplayer:shibuya kaho",
"cosplayer:shiro kitsune",
"cosplayer:siao ding",
"cosplayer:smoettii",
"cosplayer:sneaky",
"cosplayer:soa lianna",
"cosplayer:son yeeun",
"cosplayer:sunnyvier",
"cosplayer:tanaka hitomi",
"cosplayer:tenleid",
"cosplayer:todopokie",
"cosplayer:tsubaki zakuro",
"cosplayer:tsuki desu",
"cosplayer:tyouduki maryou",
"cosplayer:uchida mahiro",
"cosplayer:valery himera",
"cosplayer:velvet",
"cosplayer:wildhoney423",
"cosplayer:yume",
"cosplayer:yunocos69",
"cosplayer:yuzupyon",
"cosplayer:zara durose",
)
}
+75 -2
View File
@@ -1,11 +1,13 @@
package exh.eh.tags
object Female : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"female:abortion",
"female:absorption",
"female:adventitious mouth",
"female:adventitious penis",
"female:adventitious vagina",
"female:afro",
"female:age progression",
"female:age regression",
"female:ahegao",
@@ -17,10 +19,13 @@ object Female : TagList {
"female:anal birth",
"female:anal intercourse",
"female:anal prolapse",
"female:analphagia",
"female:angel",
"female:animal on animal",
"female:animal on furry",
"female:animegao",
"female:anorexic",
"female:apparel bukkake",
"female:apron",
"female:armpit licking",
"female:armpit sex",
@@ -33,8 +38,10 @@ object Female : TagList {
"female:bald",
"female:ball sucking",
"female:balljob",
"female:balls expansion",
"female:bandages",
"female:bandaid",
"female:bat girl",
"female:bbw",
"female:bdsm",
"female:bear",
@@ -47,6 +54,8 @@ object Female : TagList {
"female:big balls",
"female:big breasts",
"female:big clit",
"female:big lips",
"female:big muscles",
"female:big nipples",
"female:big penis",
"female:big vagina",
@@ -68,6 +77,7 @@ object Female : TagList {
"female:bodystocking",
"female:bodysuit",
"female:bondage",
"female:braces",
"female:brain fuck",
"female:breast expansion",
"female:breast feeding",
@@ -80,6 +90,7 @@ object Female : TagList {
"female:butler",
"female:cannibalism",
"female:cashier",
"female:cat",
"female:catfight",
"female:catgirl",
"female:cbt",
@@ -95,11 +106,16 @@ object Female : TagList {
"female:christmas",
"female:clamp",
"female:clit growth",
"female:clit insertion",
"female:clit stimulation",
"female:clone",
"female:closed eyes",
"female:clothed male nude female",
"female:clothed paizuri",
"female:clown",
"female:coach",
"female:cock ring",
"female:cockphagia",
"female:cockslapping",
"female:collar",
"female:condom",
@@ -115,11 +131,13 @@ object Female : TagList {
"female:crossdressing",
"female:crotch tattoo",
"female:crown",
"female:crying",
"female:cum bath",
"female:cum in eye",
"female:cum swap",
"female:cumflation",
"female:cunnilingus",
"female:cuntbusting",
"female:dark nipples",
"female:dark sclera",
"female:dark skin",
@@ -129,6 +147,8 @@ object Female : TagList {
"female:deer girl",
"female:defloration",
"female:demon girl",
"female:denki anma",
"female:detached sleeves",
"female:diaper",
"female:dick growth",
"female:dickgirl on dickgirl",
@@ -138,6 +158,8 @@ object Female : TagList {
"female:dog",
"female:dog girl",
"female:doll joints",
"female:dolphin",
"female:domination loss",
"female:donkey",
"female:double anal",
"female:double blowjob",
@@ -169,6 +191,7 @@ object Female : TagList {
"female:farting",
"female:females only",
"female:femdom",
"female:fff threesome",
"female:fft threesome",
"female:filming",
"female:fingering",
@@ -183,11 +206,13 @@ object Female : TagList {
"female:foot insertion",
"female:foot licking",
"female:footjob",
"female:forced exposure",
"female:forniphilia",
"female:fox",
"female:fox girl",
"female:freckles",
"female:frog",
"female:frog girl",
"female:frottage",
"female:fundoshi",
"female:furry",
@@ -199,11 +224,15 @@ object Female : TagList {
"female:gender change",
"female:gender morph",
"female:ghost",
"female:giant sperm",
"female:giantess",
"female:gigantic breasts",
"female:gijinka",
"female:giraffe girl",
"female:glasses",
"female:glory hole",
"female:gloves",
"female:goblin",
"female:gokkun",
"female:gothic lolita",
"female:granddaughter",
@@ -213,12 +242,14 @@ object Female : TagList {
"female:guro",
"female:gyaru",
"female:gymshorts",
"female:haigure",
"female:hair buns",
"female:hairjob",
"female:hairy",
"female:hairy armpits",
"female:handicapped",
"female:handjob",
"female:hanging",
"female:harem",
"female:harness",
"female:harpy",
@@ -237,18 +268,24 @@ object Female : TagList {
"female:human cattle",
"female:human on furry",
"female:humiliation",
"female:hyena girl",
"female:impregnation",
"female:incest",
"female:infantilism",
"female:inflation",
"female:insect",
"female:insect girl",
"female:internal urination",
"female:inverted nipples",
"female:invisible",
"female:kangaroo",
"female:kappa",
"female:kemonomimi",
"female:kigurumi pajama",
"female:kimono",
"female:kindergarten uniform",
"female:kissing",
"female:kneepit sex",
"female:kunoichi",
"female:lab coat",
"female:lactation",
@@ -258,6 +295,7 @@ object Female : TagList {
"female:layer cake",
"female:leash",
"female:leg lock",
"female:legjob",
"female:leotard",
"female:lingerie",
"female:lioness",
@@ -266,7 +304,10 @@ object Female : TagList {
"female:lolicon",
"female:long tongue",
"female:low bestiality",
"female:low guro",
"female:low lolicon",
"female:low scat",
"female:low smegma",
"female:machine",
"female:maggot",
"female:magical girl",
@@ -290,20 +331,25 @@ object Female : TagList {
"female:minigirl",
"female:monkey",
"female:monkey girl",
"female:monoeye",
"female:monster girl",
"female:moral degeneration",
"female:mother",
"female:mouse",
"female:mouse girl",
"female:mouth mask",
"female:multimouth blowjob",
"female:multiple arms",
"female:multiple assjob",
"female:multiple breasts",
"female:multiple footjob",
"female:multiple handjob",
"female:multiple nipples",
"female:multiple orgasms",
"female:multiple paizuri",
"female:multiple penises",
"female:multiple straddling",
"female:multiple vaginas",
"female:muscle",
"female:muscle growth",
"female:nakadashi",
@@ -315,6 +361,7 @@ object Female : TagList {
"female:nipple birth",
"female:nipple expansion",
"female:nipple fuck",
"female:nipple stimulation",
"female:nose fuck",
"female:nose hook",
"female:nun",
@@ -327,12 +374,16 @@ object Female : TagList {
"female:oppai loli",
"female:orc",
"female:orgasm denial",
"female:otter girl",
"female:oyakodon",
"female:paizuri",
"female:panda girl",
"female:pantyhose",
"female:pantyjob",
"female:parasite",
"female:pasties",
"female:penis birth",
"female:personality excretion",
"female:petplay",
"female:petrification",
"female:phimosis",
@@ -364,13 +415,16 @@ object Female : TagList {
"female:rape",
"female:real doll",
"female:reptile",
"female:retractable penis",
"female:rhinoceros",
"female:rimjob",
"female:robot",
"female:ryona",
"female:saliva",
"female:sarashi",
"female:scar",
"female:scat",
"female:scat insertion",
"female:school gym uniform",
"female:school swimsuit",
"female:schoolboy uniform",
@@ -378,22 +432,29 @@ object Female : TagList {
"female:scrotal lingerie",
"female:selfcest",
"female:sex toys",
"female:shapening",
"female:shared senses",
"female:shark",
"female:shark girl",
"female:shaved head",
"female:sheep",
"female:sheep girl",
"female:shemale",
"female:shibari",
"female:shimaidon",
"female:shimapan",
"female:shrinking",
"female:sister",
"female:skinsuit",
"female:skunk girl",
"female:slave",
"female:sleeping",
"female:slime",
"female:slime girl",
"female:slug",
"female:small breasts",
"female:small penis",
"female:smalldom",
"female:smegma",
"female:smell",
"female:smoking",
@@ -401,6 +462,7 @@ object Female : TagList {
"female:snake",
"female:snake girl",
"female:snuff",
"female:sockjob",
"female:sole dickgirl",
"female:sole female",
"female:solo action",
@@ -412,8 +474,10 @@ object Female : TagList {
"female:squirting",
"female:ssbbw",
"female:stewardess",
"female:stirrup legwear",
"female:stockings",
"female:stomach deformation",
"female:straitjacket",
"female:strap-on",
"female:stretching",
"female:stuck in wall",
@@ -427,10 +491,13 @@ object Female : TagList {
"female:table masturbation",
"female:tail",
"female:tail plug",
"female:tailjob",
"female:tailphagia",
"female:tall girl",
"female:tanlines",
"female:teacher",
"female:tentacles",
"female:thick eyebrows",
"female:thigh high boots",
"female:tiara",
"female:tickling",
@@ -443,11 +510,13 @@ object Female : TagList {
"female:tracksuit",
"female:trampling",
"female:transformation",
"female:transparent clothing",
"female:tribadism",
"female:triple anal",
"female:triple penetration",
"female:triple vaginal",
"female:ttf threesome",
"female:ttt threesome",
"female:tube",
"female:turtle",
"female:tutor",
@@ -472,7 +541,10 @@ object Female : TagList {
"female:waiter",
"female:waitress",
"female:weight gain",
"female:wet clothes",
"female:whale",
"female:whip",
"female:wingjob",
"female:wings",
"female:witch",
"female:wolf",
@@ -484,6 +556,7 @@ object Female : TagList {
"female:x-ray",
"female:yandere",
"female:yuri",
"female:zebra",
"female:zombie",
)
}
+71 -344
View File
@@ -1,8 +1,7 @@
package exh.eh.tags
object Group : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"group:---",
"group:... mou ii desu.",
"group:...with my tears.",
@@ -72,6 +71,7 @@ object Group : TagList {
"group:38.5 c",
"group:3d adult comics",
"group:3d bdsm dungeon",
"group:3d live",
"group:3d pose shuu",
"group:3darlings",
"group:3dfiends",
@@ -402,6 +402,7 @@ object Group : TagList {
"group:anakichi",
"group:anal crisis",
"group:analog store",
"group:analyst freedom",
"group:ananwanco",
"group:anata o aishite yamazu",
"group:anatawo haijindesu",
@@ -514,6 +515,7 @@ object Group : TagList {
"group:arikui paradise",
"group:arinko.",
"group:arisan-antenna",
"group:aristogracy",
"group:ariyon dou",
"group:ark emerald",
"group:ark nantoka",
@@ -586,6 +588,7 @@ object Group : TagList {
"group:asunaro-shiki bakudan",
"group:at 1level",
"group:at down",
"group:at e.com",
"group:at kenkyuujo",
"group:at m-gun",
"group:at mztm",
@@ -690,6 +693,7 @@ object Group : TagList {
"group:balklash.",
"group:ball colon s",
"group:ballet cohort",
"group:bambi",
"group:banana koubou",
"group:banana musume",
"group:banana no kawa",
@@ -752,6 +756,7 @@ object Group : TagList {
"group:bery manjhr",
"group:bessungou",
"group:beta houkai",
"group:beta kikaku hanbai",
"group:beta na kanzume",
"group:betaneta",
"group:betsu ni suki janai yo",
@@ -829,6 +834,7 @@ object Group : TagList {
"group:blue topaz",
"group:blue24",
"group:bluebox",
"group:bluehistory",
"group:bluehot plus",
"group:bluejelly",
"group:bluemage",
@@ -1038,6 +1044,7 @@ object Group : TagList {
"group:chikutakudoh",
"group:chikuwano kimochi",
"group:chikyuugai seimeitai mokyu",
"group:child box",
"group:childmaid",
"group:childwife",
"group:chill-out",
@@ -1143,6 +1150,7 @@ object Group : TagList {
"group:circle yuki",
"group:circlesprocket",
"group:citoron soft",
"group:citric acid1350",
"group:clammbon",
"group:cleanliness.",
"group:cleari tei",
@@ -1408,6 +1416,7 @@ object Group : TagList {
"group:dolcecanto",
"group:dollproject",
"group:dom joshidan",
"group:donaora",
"group:donburi beya",
"group:dondondon",
"group:dont understand",
@@ -1458,6 +1467,7 @@ object Group : TagList {
"group:drill",
"group:drill biyori",
"group:dro-ya",
"group:dropwortbell",
"group:drug slash tag slash 21",
"group:dual beat",
"group:dualtail",
@@ -1531,6 +1541,7 @@ object Group : TagList {
"group:enshu spirits",
"group:entgegen",
"group:enuhuo",
"group:enyidou",
"group:ephese",
"group:epic lust",
"group:epicureansyndrome",
@@ -1726,6 +1737,7 @@ object Group : TagList {
"group:fuwa fuwa pinkchan",
"group:fuwamoko honpo",
"group:fuwaten",
"group:fuyuzora izumo",
"group:fuzukikai",
"group:g area",
"group:g equals kundow",
@@ -1768,9 +1780,11 @@ object Group : TagList {
"group:garaku dusk",
"group:garakuta shoujo",
"group:garakuta-ya",
"group:garasu hokou",
"group:garbage",
"group:gardening bulldog",
"group:garnet-works.",
"group:garunansa mk-2",
"group:garyuh-chitai",
"group:gas ketsu jinsei",
"group:gasshuukoku netamekoru",
@@ -1885,6 +1899,7 @@ object Group : TagList {
"group:gpen",
"group:gpx",
"group:grand cross",
"group:grand plie.",
"group:graphic l",
"group:gravidan",
"group:great acta",
@@ -1987,6 +2002,9 @@ object Group : TagList {
"group:halleluya.",
"group:hallenchi planet",
"group:hallucigenia",
)
override fun getTags2(): List<String> = listOf(
"group:halopack",
"group:ham string",
"group:ham.",
@@ -2002,13 +2020,12 @@ object Group : TagList {
"group:hamurabi-dou",
"group:hamustar",
"group:hamusuta-nonikomi",
"group:hana ni arashi.",
"group:hana tabako",
)
override fun getTags2() = listOf(
"group:hana to ribon",
"group:hana x mezo",
"group:hanafubuki gorilla",
"group:hanahubu",
"group:hanaji koubou",
"group:hanamachi shimaiten",
"group:hanamaru mugen gym",
@@ -2037,6 +2054,7 @@ object Group : TagList {
"group:happo ryuu",
"group:happy birthday",
"group:happy flame time",
"group:happy log",
"group:happy strawberry",
"group:happydrop",
"group:happywest",
@@ -2136,6 +2154,7 @@ object Group : TagList {
"group:hetaretch",
"group:hetaruya",
"group:heya no sumi.",
"group:hgt labo",
"group:hi at skip",
"group:hi plus us",
"group:hi-b",
@@ -2199,6 +2218,7 @@ object Group : TagList {
"group:hiroq",
"group:hirouguma",
"group:hisagoya",
"group:hisou and anchoku",
"group:hisuitei",
"group:hisyoku no tansansui",
"group:hito no fundoshi",
@@ -2229,6 +2249,7 @@ object Group : TagList {
"group:hokoushayou shingou",
"group:hokuroza",
"group:holiday school",
"group:home not found",
"group:homepie koubou",
"group:homerun chaya",
"group:homuras r comics",
@@ -2248,6 +2269,7 @@ object Group : TagList {
"group:hook",
"group:hooliganism",
"group:horiishi horuto",
"group:horonabe ken",
"group:horrorbabecentral",
"group:horsetail",
"group:hoshi no hako",
@@ -2367,6 +2389,7 @@ object Group : TagList {
"group:imobatake",
"group:imokenpi",
"group:imomushi kyouiku madoguchi",
"group:inakahaishin",
"group:inaridou shoten",
"group:inc satsujinsha",
"group:inceton games",
@@ -2395,6 +2418,7 @@ object Group : TagList {
"group:intoku.info",
"group:inudrill.",
"group:inukamedou",
"group:inukichi club",
"group:inukorohouse",
"group:inunabe",
"group:inuteikoku",
@@ -2414,6 +2438,7 @@ object Group : TagList {
"group:isada-ke",
"group:isami kaihatsu jigyoudan",
"group:isamu. no oheya",
"group:isanayoruho",
"group:ishikari shake nabe doukoukai",
"group:ishikorodou",
"group:ishimuraya",
@@ -2460,6 +2485,7 @@ object Group : TagList {
"group:jamadai oukoku",
"group:janculsoft",
"group:jangarian",
"group:jar of elements",
"group:jasmin universal village",
"group:jeepney.cony",
"group:jei c1on-ri",
@@ -2535,6 +2561,7 @@ object Group : TagList {
"group:k.a.d",
"group:k.f.d.",
"group:k.o.store",
"group:k.z.z. gundan",
"group:k2 company",
"group:k2 tomo no kai",
"group:k3",
@@ -2555,6 +2582,7 @@ object Group : TagList {
"group:kaeri no kai 2",
"group:kaeru soft",
"group:kagaku-shitsu.",
"group:kage mitsu",
"group:kagisawadou",
"group:kagishippo",
"group:kagiyama baking co ltd",
@@ -2584,6 +2612,7 @@ object Group : TagList {
"group:kaki purin",
"group:kakiabura",
"group:kakinotanehitotsubu",
"group:kaku shoujo",
"group:kakunetu neko punch",
"group:kakuzato-ichi",
"group:kamaboko higii",
@@ -2612,6 +2641,7 @@ object Group : TagList {
"group:kangaroo kick",
"group:kanimiso pan",
"group:kanimiso-tei",
"group:kankitudou",
"group:kankituteien",
"group:kanmi ningyou",
"group:kanmidokoro usb",
@@ -2840,12 +2870,14 @@ object Group : TagList {
"group:kokkishin",
"group:koko sou iu mise janainde",
"group:kokochikyuu",
"group:kokonji honpo",
"group:kokonoe",
"group:kokonokaya",
"group:kokonokiya",
"group:kokoro ha koi iro",
"group:kokoro no bookmark",
"group:kokou no gokutsubushi",
"group:koks k yokochou",
"group:koku-from-shojo",
"group:kokumaro chousei tounyuu",
"group:kokuritsu hinanjo",
@@ -2916,6 +2948,7 @@ object Group : TagList {
"group:kouni yuu",
"group:kousaien",
"group:kousoku gurihari-tei",
"group:kousoku purin",
"group:kouzaka-san to makino jimusho",
"group:kouzu shoukai",
"group:kouzuya",
@@ -3047,8 +3080,10 @@ object Group : TagList {
"group:lab-ideas",
"group:labomagi",
"group:lagrangian-point",
"group:lagunaseca",
"group:laikaloid",
"group:lala la",
"group:lalapaloosa",
"group:lamchat",
"group:lamia advisers",
"group:landurchin",
@@ -3056,6 +3091,7 @@ object Group : TagList {
"group:lantern chord",
"group:lapis lazuli",
"group:lapislazuli triple star",
"group:laser beam",
"group:lathimania kyouwakoku",
"group:latte chaba",
"group:lavenderblue",
@@ -3171,6 +3207,7 @@ object Group : TagList {
"group:m kichibeya",
"group:m plus dilore",
"group:m slash k club",
"group:m-i-p",
"group:m-keifu",
"group:m-koujou",
"group:m-lab.",
@@ -3460,6 +3497,7 @@ object Group : TagList {
"group:mindcontrolcomics",
"group:mine slash mine",
"group:minemine kikaku",
"group:minimum fuusen",
"group:minimum lab",
"group:miniomlet ongakudan",
"group:minisuka fx",
@@ -3481,6 +3519,7 @@ object Group : TagList {
"group:misin koujou",
"group:misonodenpatou",
"group:misoyahonpo",
"group:misssail",
"group:misuterutein",
"group:misutta",
"group:misuzu dennou gijutsukenkyuujo",
@@ -3531,6 +3570,7 @@ object Group : TagList {
"group:moe hentai",
"group:moe hina",
"group:moe shoujo ryouiki",
"group:moegara",
"group:moeyuki soft",
"group:moezilla-gumi",
"group:mofu mofu sheep",
@@ -3582,6 +3622,7 @@ object Group : TagList {
"group:moon night kitten",
"group:moon ruler",
"group:moonlegacy",
"group:moonlight laboratory",
"group:moonrevenge",
"group:moonshell",
"group:moonsorrow",
@@ -3639,6 +3680,7 @@ object Group : TagList {
"group:mozuku dokokai",
"group:mp",
"group:mp0",
"group:mr. hokke",
"group:mr.jack plus",
"group:mr.k",
"group:mr.outside",
@@ -3765,6 +3807,7 @@ object Group : TagList {
"group:namakura dou",
"group:namanecotei",
"group:namasute koubou",
"group:namazuchaya",
"group:namekataya",
"group:nami-nami restaurant",
"group:namida no teinen taishoku",
@@ -3906,6 +3949,7 @@ object Group : TagList {
"group:nekozamedan",
"group:nekuronomikon",
"group:nel-zel formula",
"group:nemu wa yakiniku ga tabetai",
"group:nemurineko",
"group:nenashigusa no ie",
"group:nendo ningyo",
@@ -3961,6 +4005,9 @@ object Group : TagList {
"group:nikomark",
"group:nikomi omurice",
"group:nikoniko company",
)
override fun getTags3(): List<String> = listOf(
"group:niku ringo",
"group:nikubenki seisakusho",
"group:nikudan",
@@ -4006,9 +4053,6 @@ object Group : TagList {
"group:niwatori",
"group:niwatori-ya",
"group:niwatoritowani",
)
override fun getTags3() = listOf(
"group:niy koubou",
"group:niziro",
"group:niziyumedokoro",
@@ -4025,6 +4069,7 @@ object Group : TagList {
"group:nocohica",
"group:nodobotoke kingyo",
"group:nogusaw puzzle",
"group:noir jou entrance",
"group:nokishita no rakuen.",
"group:nomerikomu",
"group:nomigoro.",
@@ -4040,7 +4085,6 @@ object Group : TagList {
"group:norakurari.",
"group:noraneko koubou",
"group:noraneko-no-tama",
"group:nori5rou",
"group:norihee ginjou",
"group:noritama-gozen",
"group:norn",
@@ -4175,6 +4219,7 @@ object Group : TagList {
"group:omusubi koubou",
"group:on my way",
"group:on-show",
"group:onabe no naka.",
"group:onaka suita domei",
"group:onasuga 99-yen",
"group:one dollar",
@@ -4220,6 +4265,7 @@ object Group : TagList {
"group:ororiya enpitsudo",
"group:osanagokoro no kimi ni",
"group:osaru-san panic",
"group:oshaburi tengoku",
"group:oshigoto no jikan",
"group:osouzaiya-san",
"group:osova",
@@ -4439,6 +4485,7 @@ object Group : TagList {
"group:pockyfactory",
"group:poco black",
"group:poco poco",
"group:poino",
"group:poisonblues",
"group:poiyo dimension",
"group:pokachutei",
@@ -4464,6 +4511,7 @@ object Group : TagList {
"group:poppin stompin",
"group:popship",
"group:popularplus",
"group:porcini",
"group:porno maker",
"group:potato house",
"group:poteto dango",
@@ -4544,7 +4592,6 @@ object Group : TagList {
"group:rabbit kuukan",
"group:rabbits",
"group:rabitan",
"group:rabu.",
"group:raccoondog",
"group:radiant slash h plus",
"group:radiostar",
@@ -4797,6 +4844,7 @@ object Group : TagList {
"group:sakura mangekyou",
"group:sakura naomiki",
"group:sakura nigou",
"group:sakura no tomoru hie",
"group:sakura prin",
"group:sakura zensen",
"group:sakuradou",
@@ -4827,6 +4875,7 @@ object Group : TagList {
"group:sangenshokudou",
"group:sanjuuhachi shiki kikanjuu",
"group:sankaku apron",
"group:sankaku button",
"group:sankaku doumei",
"group:sankokudo",
"group:sanma kizoku",
@@ -5081,6 +5130,7 @@ object Group : TagList {
"group:shonen gekikuukan",
"group:shonnaka-dou",
"group:shootouts",
"group:short kami",
"group:shortcut koubou",
"group:shosekido",
"group:shouchuu mac",
@@ -5110,6 +5160,7 @@ object Group : TagList {
"group:shuryousha",
"group:shuto reccara",
"group:shuueisha",
"group:shuukyuu 8-ka",
"group:shuukyuu itsukasei",
"group:shuutaisei",
"group:shyness over drive",
@@ -5143,9 +5194,9 @@ object Group : TagList {
"group:siop",
"group:sioyaki",
"group:sioyude",
"group:sirisiri denbu club",
"group:sirius soft",
"group:siro house",
"group:siropome",
"group:sirouto plan",
"group:sirubedou",
"group:sisinabeya",
@@ -5261,6 +5312,7 @@ object Group : TagList {
"group:st. rio",
"group:st. rororo",
"group:staccato squirrel",
"group:stamina teishoku",
"group:standard azarashi",
"group:star link",
"group:star-dreamer tei",
@@ -5402,7 +5454,6 @@ object Group : TagList {
"group:sushi-go-round",
"group:suteinu nursery",
"group:sutekiplan",
"group:suzuki masahisa",
"group:suzuki shouten",
"group:suzunaridou",
"group:suzupony",
@@ -5421,6 +5472,7 @@ object Group : TagList {
"group:syamisen koubou",
"group:synthetic garden",
"group:syonen-kikakugai.",
"group:syounan rakujin society",
"group:syounen heroine",
"group:syounen kouraku",
"group:syouseki kessyou",
@@ -5474,6 +5526,7 @@ object Group : TagList {
"group:takara no suzunari",
"group:takashi-ya",
"group:takayashiki kaihatsu",
"group:take4 project",
"group:takeda syouten",
"group:takegamiya",
"group:takeshidou-chou",
@@ -5494,6 +5547,7 @@ object Group : TagList {
"group:tamatamasanmyaku",
"group:tamokuteki hall",
"group:tamokuteki kuukan",
"group:tanajou",
"group:tanaka shouten",
"group:tanaura honpo",
"group:tanetsuke ichinengo",
@@ -5526,6 +5580,7 @@ object Group : TagList {
"group:team tanabe",
"group:team z and 3n",
"group:team zero",
"group:team4000",
"group:tears of ymir",
"group:teatime",
"group:tecchitecchi",
@@ -5534,9 +5589,11 @@ object Group : TagList {
"group:tedaingu",
"group:teddy-plaza",
"group:teemonk",
"group:teiesuya",
"group:teihatu syouzyo titai",
"group:teikiatu de ikou",
"group:teikoku club",
"group:tekirororock",
"group:tekken neko gourmet",
"group:tekoman-dou",
"group:telomere limiter",
@@ -5718,6 +5775,7 @@ object Group : TagList {
"group:toumei tsuushin",
"group:toushi ryoku kenkyuujo",
"group:toutaku tuyagadou",
"group:touyou bujutsu gakkou",
"group:touyu okiba kari",
"group:touyu stand",
"group:touzoku tachi no rakuda no mure",
@@ -5918,6 +5976,7 @@ object Group : TagList {
"group:usagi youjinbou",
"group:usagijiru",
"group:usagikoara",
"group:usaginoheya",
"group:usagitei",
"group:usako kf",
"group:usamimi syndrome",
@@ -5949,337 +6008,5 @@ object Group : TagList {
"group:vagina dentata",
"group:valiant",
"group:valkyria",
"group:valkyrieharlem",
"group:vanilla fuumi shoujo",
"group:vanilla star",
"group:vanilla-dou max",
"group:variety pot",
"group:vashadow",
"group:vc productions",
"group:venom",
"group:veronica no ha",
"group:violence asia team",
"group:visual biscuits",
"group:vitamin gohan",
"group:vitamin soft",
"group:vitamin x",
"group:vivi-sectr",
"group:vivid color",
"group:vivid dot",
"group:vivido",
"group:viweb",
"group:voltcompany.",
"group:vpans extasy",
"group:vulcannu",
"group:w at nd",
"group:wabi sabi wasabi",
"group:wagamama dou",
"group:wakaba syokei",
"group:wakarase seisaku iinkai",
"group:wakatobi",
"group:wakusei-teki shukou",
"group:wakuwaku boycott",
"group:wakuwaku doubutsuen",
"group:walhalla illusion",
"group:wamusho",
"group:wanko-tei",
"group:wankyoku canvas",
"group:wanwandoh",
"group:warabimochi",
"group:waretama",
"group:warp loop",
"group:washikul.",
"group:washokudeniku.",
"group:wata 120 percent",
"group:wataame",
"group:watagashi maker",
"group:watanabe tou",
"group:wataru kuya",
"group:water ducts",
"group:waterfall",
"group:waterspoon",
"group:waterstudio",
"group:waterwheel",
"group:weather report",
"group:webstudioofflimits",
"group:wellca",
"group:wendybell",
"group:west vision",
"group:whirl-wind",
"group:white out",
"group:white plus dk",
"group:white rocket",
)
override fun getTags4() = listOf(
"group:whitecloth",
"group:whitesoft",
"group:whitewill",
"group:wi-fe hacker",
"group:wicked heart",
"group:wild goat",
"group:will tame",
"group:willow soft",
"group:wind mail",
"group:wind of the keep valley",
"group:windarteam",
"group:winger in mind",
"group:winghills",
"group:wish",
"group:wish kibou no tsubasa",
"group:witchflame",
"group:witching hour entertainment",
"group:wizard",
"group:wolf fang dou",
"group:wonderland 203",
"group:wope-retta",
"group:world of porncraft",
"group:world temperament",
"group:worstworks",
"group:wriggle souzeme tomonokai",
"group:x model",
"group:x-tei",
"group:xephs artwork",
"group:xl-toons",
"group:xx koubou",
"group:xxxxxxx",
"group:xyzyroh",
"group:y slash s slash k",
"group:y.d.l",
"group:y.m.sensha",
"group:yaboudo project",
"group:yabougumi",
"group:yabukaradou",
"group:yaburi dokoro",
"group:yadapot",
"group:yadokugaeru",
"group:yadoo van yahdoo",
"group:yagi q-syah",
"group:yago no ana",
"group:yajilshi plus",
"group:yajirushi key",
"group:yajiya",
"group:yakata",
"group:yakimisomura",
"group:yakinasu teishoku",
"group:yakiniku teishoku",
"group:yakisaketeishoku",
"group:yakisoba pants",
"group:yakisoba rengo",
"group:yakiubu",
"group:yakousei fan club",
"group:yaku 40 man sarad",
"group:yakumi sarai",
"group:yakusyo",
"group:yakutai",
"group:yam x 2 dai teikoku",
"group:yamada ichizoku.",
"group:yamadamaya",
"group:yamadaya",
"group:yamagiwa art cg studio",
"group:yamaguchirou",
"group:yamakawa denenhuukei",
"group:yamami no yado",
"group:yamamori gohan",
"group:yamanaka no naka",
"group:yamano murao",
"group:yami ni ugomeku",
"group:yamikumo tsuushin",
"group:yamotodou rakugakiichi",
"group:yanagiba dai",
"group:yanagisegawa",
"group:yanasegawabeya",
"group:yangyang nickbow",
"group:yanmarumaa",
"group:yaou keikaku",
"group:yaoyorozu-kobo",
"group:yaoyorozudo",
"group:yasai batake",
"group:yaseuma lo-ru",
"group:yashiya",
"group:yashock kaigi",
"group:yasrin-do",
"group:yasudajuku",
"group:yasuomi-craft",
"group:yasyokutei",
"group:yatuumi no kagami",
"group:yawaraka okashiya",
"group:yaya hinata-ya",
"group:yes sir.",
"group:yggdrasil",
"group:yo-metdo",
"group:yoban left",
"group:yobigakka",
"group:yohsyuan",
"group:yojouhan factory",
"group:yojouhan shobou",
"group:yojouhan toshi",
"group:yokan musume",
"group:yokazetei",
"group:yokohama zza koubou",
"group:yokoshimanchi.",
"group:yokoshimaya",
"group:yokoshoku ice",
"group:yomamagoto",
"group:yomosue doukoukai",
"group:yomothuhirasaka",
"group:yonatan black mutou",
"group:yonbangai garo",
"group:yonjuichi",
"group:yonmasuya",
"group:yonurime",
"group:yorando",
"group:yoru no benkyoukai",
"group:yoru no okazu shokudou",
"group:yoruyama no kyuukeijo",
"group:yoseatume tekina nanika",
"group:yoshida gorou shoukai",
"group:yoshiga dokoro",
"group:yoshii tech sha",
"group:yotukuro",
"group:you",
"group:you you tsuushin",
"group:you you you",
"group:youchi-na-ochakai",
"group:youen bijo no garou",
"group:youkai ankake",
"group:youkai tamanokoshi",
"group:youkandou",
"group:youki m.k.c.",
"group:youmusya",
"group:yours-wow",
"group:yousei allergen",
"group:youseimangasya",
"group:youtoujirushi",
"group:youtsuu transmitter",
"group:youyukai",
"group:yowatari kouba",
"group:yoyude ikemasu",
"group:yozorairodrops",
"group:ys company",
"group:yu-ta.18",
"group:yu-yu-tei",
"group:yuasa rengou",
"group:yubidou",
"group:yudenakya nama-beer",
"group:yudokuya",
"group:yuhshiki",
"group:yukagenikaga",
"group:yukeyuke ryuseigo",
"group:yuki daruma koujou",
"group:yuki no iori",
"group:yukijirushi nyuugyou",
"group:yukikagerou",
"group:yukimi honpo",
"group:yukimura",
"group:yukinko okeya",
"group:yukino koubou",
"group:yukirinrin",
"group:yukyu-kyuka",
"group:yume bouei shoujo tai",
"group:yume yori suteki na",
"group:yume-zakura",
"group:yumeiro-goromo",
"group:yumeizukosya",
"group:yumemigachi campus",
"group:yumemigokoti",
"group:yumemiru-kikai",
"group:yumenamakon",
"group:yumenokage",
"group:yumeoikyounouta",
"group:yumeoukoku",
"group:yunabon",
"group:yurayuraseyuura",
"group:yureika blade",
"group:yuri dokidoki",
"group:yuri equal 18l",
"group:yuriai kojinshi kai",
"group:yurinyurin",
"group:yuriru-rarika",
"group:yurumebox",
"group:yuruyakatou",
"group:yuruyuru seisakusho",
"group:yusuzumi",
"group:yuu adashino suisan",
"group:yuu heya",
"group:yuubin basha",
"group:yuudachitei",
"group:yuuendou",
"group:yuugai tosho kikaku",
"group:yuugen kaisha sokuhou seisakusho",
"group:yuugensangyou sukimakaze",
"group:yuugure koubou",
"group:yuuhodou",
"group:yuujikouji",
"group:yuukakumin",
"group:yuuki kagoubutsu",
"group:yuukyuu shinden",
"group:yuukyuu suisenkan",
"group:yuunagi gaibutai",
"group:yuuriko",
"group:yuusha kandenchi",
"group:yuutopia",
"group:yuuya-yuu",
"group:yuyake box",
"group:yuzu soft",
"group:yuzucha",
"group:yuzuen",
"group:yuzumomo jam",
"group:yuzuonsen",
"group:yuzuponz",
"group:yuzurihaya",
"group:z",
"group:z-tabukuroneko house",
"group:z-vector",
"group:za da carjya",
"group:zakkin kougyou",
"group:zan-sei",
"group:zankirow",
"group:zankoku doumei",
"group:zankoku na kami ga shihai suru",
"group:zantetuken",
"group:zasshu-ken",
"group:zassoubatake",
"group:zassoya",
"group:zatouichi",
"group:zatuyou gakari",
"group:zawameki jambo",
"group:zeiniku shoujotai",
"group:zenmai koubou",
"group:zenmai kourogi",
"group:zennihon do-m jitsuryoku kentei kousa",
"group:zenoside",
"group:zenra restaurant",
"group:zensekai yakenohara doumei",
"group:zenshuu bougyo",
"group:zensoku zenkai.",
"group:zensun habaku",
"group:zenzidou kosyubenjo",
"group:zero byte",
"group:zero equals mono",
"group:zero ni kaeru tsuki",
"group:zero-one",
"group:zero-sen",
"group:zero-xx",
"group:zerocool",
"group:zeroinfinityone",
"group:zeryishi",
"group:zettai shoujo",
"group:zettaitensei",
"group:zgf",
"group:zi",
"group:zion",
"group:zmey no soukutsu",
"group:zokubutsu.zip",
"group:zombie to yukaina nakamatachi",
"group:zonzon sharp 4",
"group:zooerastia",
"group:zouri no sato",
"group:zozalist",
"group:zugaikotsu marudashi",
"group:zvizva-dan",
"group:zydan",
"group:zyulokuya",
"group:zzz comics",
)
}
+342
View File
@@ -0,0 +1,342 @@
package exh.eh.tags
object Group2 : TagList {
override fun getTags1(): List<String> = listOf(
"group:valkyrieharlem",
"group:vanilla fuumi shoujo",
"group:vanilla star",
"group:vanilla-dou max",
"group:variety pot",
"group:vashadow",
"group:vc productions",
"group:venom",
"group:veronica no ha",
"group:violence asia team",
"group:visual biscuits",
"group:vitamin gohan",
"group:vitamin soft",
"group:vitamin x",
"group:vivi-sectr",
"group:vivid color",
"group:vivid dot",
"group:vivido",
"group:viweb",
"group:voltcompany.",
"group:vpans extasy",
"group:vulcannu",
"group:w at nd",
"group:wabi sabi wasabi",
"group:wagamama dou",
"group:wakaba syokei",
"group:wakarase seisaku iinkai",
"group:wakatobi",
"group:wakusei-teki shukou",
"group:wakuwaku boycott",
"group:wakuwaku doubutsuen",
"group:walhalla illusion",
"group:wamusho",
"group:wanko-tei",
"group:wankyoku canvas",
"group:wanwandoh",
"group:warabimochi",
"group:waretama",
"group:warp loop",
"group:wasa wasa",
"group:washikul.",
"group:washokudeniku.",
"group:wata 120 percent",
"group:wataame",
"group:watagashi maker",
"group:watanabe tou",
"group:wataru kuya",
"group:water ducts",
"group:waterfall",
"group:waterspoon",
"group:waterstudio",
"group:waterwheel",
"group:weather report",
"group:webstudioofflimits",
"group:wellca",
"group:wendybell",
"group:west vision",
"group:whirl-wind",
"group:white out",
"group:white plus dk",
"group:white rocket",
"group:whitecloth",
"group:whitesoft",
"group:whitewill",
"group:wi-fe hacker",
"group:wicked heart",
"group:wild goat",
"group:will tame",
"group:willow soft",
"group:wind mail",
"group:wind of the keep valley",
"group:windarteam",
"group:winger in mind",
"group:winghills",
"group:wish",
"group:wish kibou no tsubasa",
"group:witchflame",
"group:witching hour entertainment",
"group:wizard",
"group:wolf fang dou",
"group:wonderland 203",
"group:wope-retta",
"group:world of porncraft",
"group:world temperament",
"group:worstworks",
"group:wriggle souzeme tomonokai",
"group:x model",
"group:x-tei",
"group:xephs artwork",
"group:xl-toons",
"group:xx koubou",
"group:xxxxxxx",
"group:xyzyroh",
"group:y slash s slash k",
"group:y.d.l",
"group:y.m.sensha",
"group:yaboudo project",
"group:yabougumi",
"group:yabukaradou",
"group:yaburi dokoro",
"group:yadapot",
"group:yadokugaeru",
"group:yadoo van yahdoo",
"group:yagi q-syah",
"group:yago no ana",
"group:yajilshi plus",
"group:yajirushi key",
"group:yajiya",
"group:yakata",
"group:yakimisomura",
"group:yakinasu teishoku",
"group:yakiniku teishoku",
"group:yakisaketeishoku",
"group:yakisoba pants",
"group:yakisoba rengo",
"group:yakiubu",
"group:yakousei fan club",
"group:yaku 40 man sarad",
"group:yakumi sarai",
"group:yakusyo",
"group:yakutai",
"group:yam x 2 dai teikoku",
"group:yamada ichizoku.",
"group:yamadamaya",
"group:yamadaya",
"group:yamagiwa art cg studio",
"group:yamaguchirou",
"group:yamakawa denenhuukei",
"group:yamami no yado",
"group:yamamori gohan",
"group:yamanaka no naka",
"group:yamano murao",
"group:yamato nadeshiko club",
"group:yami ni ugomeku",
"group:yamikumo tsuushin",
"group:yamotodou rakugakiichi",
"group:yanagiba dai",
"group:yanagisegawa",
"group:yanasegawabeya",
"group:yangyang nickbow",
"group:yanmarumaa",
"group:yaou keikaku",
"group:yaoyorozu-kobo",
"group:yaoyorozudo",
"group:yasai batake",
"group:yaseuma lo-ru",
"group:yashiya",
"group:yashock kaigi",
"group:yasrin-do",
"group:yasudajuku",
"group:yasuomi-craft",
"group:yasyokutei",
"group:yatomomin",
"group:yatuumi no kagami",
"group:yawaraka okashiya",
"group:yaya hinata-ya",
"group:yes sir.",
"group:yggdrasil",
"group:yo-metdo",
"group:yoban left",
"group:yobigakka",
"group:yohsyuan",
"group:yojouhan factory",
"group:yojouhan shobou",
"group:yojouhan toshi",
"group:yokan musume",
"group:yokazetei",
"group:yokohama zza koubou",
"group:yokoshimanchi.",
"group:yokoshimaya",
"group:yokoshoku ice",
"group:yoku mireba beta",
"group:yomamagoto",
"group:yomosue doukoukai",
"group:yomothuhirasaka",
"group:yonatan black mutou",
"group:yonbangai garo",
"group:yonjuichi",
"group:yonmasuya",
"group:yonurime",
"group:yorando",
"group:yoru no benkyoukai",
"group:yoru no okazu shokudou",
"group:yoruyama no kyuukeijo",
"group:yoseatume tekina nanika",
"group:yoshida gorou shoukai",
"group:yoshiga dokoro",
"group:yoshii tech sha",
"group:yotukuro",
"group:you",
"group:you you tsuushin",
"group:you you you",
"group:youchi-na-ochakai",
"group:youen bijo no garou",
"group:yougensya",
"group:youkai ankake",
"group:youkai tamanokoshi",
"group:youkandou",
"group:youki m.k.c.",
"group:youmusya",
"group:yours-wow",
"group:yousei allergen",
"group:youseimangasya",
"group:youtoujirushi",
"group:youtsuu transmitter",
"group:youyukai",
"group:yowatari kouba",
"group:yoyude ikemasu",
"group:yozorairodrops",
"group:ys company",
"group:yu-ta.18",
"group:yu-yu-tei",
"group:yuasa rengou",
"group:yubidou",
"group:yudenakya nama-beer",
"group:yudokuya",
"group:yuhshiki",
"group:yukagenikaga",
"group:yukan high zakura",
"group:yukeyuke ryuseigo",
"group:yuki daruma koujou",
"group:yuki no iori",
"group:yukijirushi nyuugyou",
"group:yukikagerou",
"group:yukimi honpo",
"group:yukimura",
"group:yukinko okeya",
"group:yukino koubou",
"group:yukirinrin",
"group:yukyu-kyuka",
"group:yume bouei shoujo tai",
"group:yume yori suteki na",
"group:yume-zakura",
"group:yumeiro-goromo",
"group:yumeizukosya",
"group:yumemigachi campus",
"group:yumemigokoti",
"group:yumemiru-kikai",
"group:yumenamakon",
"group:yumenokage",
"group:yumeoikyounouta",
"group:yumeoukoku",
"group:yunabon",
"group:yurayuraseyuura",
"group:yureika blade",
"group:yuri dokidoki",
"group:yuri equal 18l",
"group:yuriai kojinshi kai",
"group:yurinyurin",
"group:yuriru-rarika",
"group:yurumebox",
"group:yuruyakatou",
"group:yuruyuru seisakusho",
"group:yusuzumi",
"group:yuu adashino suisan",
"group:yuu heya",
"group:yuubin basha",
"group:yuudachitei",
"group:yuuendou",
"group:yuugai tosho kikaku",
"group:yuugen kaisha sokuhou seisakusho",
"group:yuugensangyou sukimakaze",
"group:yuugure koubou",
"group:yuuhodou",
"group:yuujikouji",
"group:yuukakumin",
"group:yuuki kagoubutsu",
"group:yuukyuu shinden",
"group:yuukyuu suisenkan",
"group:yuunagi gaibutai",
"group:yuuriko",
"group:yuusha kandenchi",
"group:yuutopia",
"group:yuuya-yuu",
"group:yuyake box",
"group:yuzu soft",
"group:yuzucha",
"group:yuzuen",
"group:yuzumomo jam",
"group:yuzuonsen",
"group:yuzuponz",
"group:yuzurihaya",
"group:z",
"group:z-tabukuroneko house",
"group:z-vector",
"group:za da carjya",
"group:zakkin kougyou",
"group:zan-sei",
"group:zankirow",
"group:zankoku doumei",
"group:zankoku na kami ga shihai suru",
"group:zantetuken",
"group:zasshu-ken",
"group:zassoubatake",
"group:zassoya",
"group:zatouichi",
"group:zatuyou gakari",
"group:zawameki jambo",
"group:zeiniku shoujotai",
"group:zenmai koubou",
"group:zenmai kourogi",
"group:zennihon do-m jitsuryoku kentei kousa",
"group:zenoside",
"group:zenra restaurant",
"group:zenryoku sissou cat",
"group:zensekai yakenohara doumei",
"group:zenshuu bougyo",
"group:zensoku zenkai.",
"group:zensun habaku",
"group:zenzidou kosyubenjo",
"group:zero byte",
"group:zero equals mono",
"group:zero ni kaeru tsuki",
"group:zero-one",
"group:zero-sen",
"group:zero-xx",
"group:zerocool",
"group:zeroinfinityone",
"group:zeryishi",
"group:zettai shoujo",
"group:zettaitensei",
"group:zgf",
"group:zi",
"group:zion",
"group:zmey no soukutsu",
"group:zokubutsu.zip",
"group:zombie to yukaina nakamatachi",
"group:zonzon sharp 4",
"group:zooerastia",
"group:zouri no sato",
"group:zozalist",
"group:zugaikotsu marudashi",
"group:zvizva-dan",
"group:zydan",
"group:zyulokuya",
"group:zzz comics",
)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package exh.eh.tags
object Language : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"language:arabic",
"language:bulgarian",
"language:catalan",
+136 -2
View File
@@ -1,28 +1,39 @@
package exh.eh.tags
object Male : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"male:abortion",
"male:absorption",
"male:adventitious penis",
"male:afro",
"male:age progression",
"male:age regression",
"male:ahegao",
"male:albino",
"male:alien",
"male:all the way through",
"male:amputee",
"male:anal",
"male:anal birth",
"male:anal intercourse",
"male:anal prolapse",
"male:analphagia",
"male:angel",
"male:animal on animal",
"male:animal on furry",
"male:animegao",
"male:anorexic",
"male:apparel bukkake",
"male:apron",
"male:armpit licking",
"male:armpit sex",
"male:asphyxiation",
"male:ass expansion",
"male:assjob",
"male:autofellatio",
"male:bald",
"male:ball sucking",
"male:balljob",
"male:balls expansion",
"male:bandages",
"male:bat boy",
@@ -30,10 +41,14 @@ object Male : TagList {
"male:bdsm",
"male:bear",
"male:bear boy",
"male:beauty mark",
"male:bestiality",
"male:big areolae",
"male:big ass",
"male:big balls",
"male:big breasts",
"male:big lips",
"male:big muscles",
"male:big nipples",
"male:big penis",
"male:bike shorts",
@@ -47,12 +62,16 @@ object Male : TagList {
"male:bloomers",
"male:blowjob",
"male:blowjob face",
"male:body modification",
"male:body painting",
"male:body swap",
"male:body writing",
"male:bodystocking",
"male:bodysuit",
"male:bondage",
"male:braces",
"male:brain fuck",
"male:breast expansion",
"male:breast feeding",
"male:bride",
"male:brother",
@@ -62,11 +81,13 @@ object Male : TagList {
"male:burping",
"male:business suit",
"male:butler",
"male:camel",
"male:cannibalism",
"male:cashier",
"male:cat",
"male:catboy",
"male:cbt",
"male:centaur",
"male:cervix prolapse",
"male:chastity belt",
"male:cheating",
@@ -75,13 +96,20 @@ object Male : TagList {
"male:chinese dress",
"male:chloroform",
"male:christmas",
"male:clamp",
"male:clit insertion",
"male:clit stimulation",
"male:clone",
"male:closed eyes",
"male:clothed female nude male",
"male:clown",
"male:coach",
"male:cock ring",
"male:cockphagia",
"male:cockslapping",
"male:collar",
"male:condom",
"male:conjoined",
"male:coprophagia",
"male:corruption",
"male:corset",
@@ -90,25 +118,39 @@ object Male : TagList {
"male:cowman",
"male:crab",
"male:crossdressing",
"male:crotch tattoo",
"male:crown",
"male:crying",
"male:cum bath",
"male:cumflation",
"male:cunnilingus",
"male:cuntboy",
"male:dark nipples",
"male:dark sclera",
"male:dark skin",
"male:deepthroat",
"male:deer",
"male:deer boy",
"male:demon",
"male:denki anma",
"male:detached sleeves",
"male:diaper",
"male:dick growth",
"male:dickgirl on male",
"male:dicknipples",
"male:dilf",
"male:dinosaur",
"male:dog",
"male:dog boy",
"male:doll joints",
"male:dolphin",
"male:domination loss",
"male:donkey",
"male:double anal",
"male:double blowjob",
"male:double penetration",
"male:dougi",
"male:draenei",
"male:dragon",
"male:drill hair",
"male:drugs",
@@ -126,6 +168,7 @@ object Male : TagList {
"male:eye-covering bang",
"male:eyemask",
"male:eyepatch",
"male:facesitting",
"male:facial hair",
"male:fairy",
"male:farting",
@@ -138,8 +181,10 @@ object Male : TagList {
"male:fisting",
"male:focus paizuri",
"male:food on body",
"male:foot insertion",
"male:foot licking",
"male:footjob",
"male:forced exposure",
"male:forniphilia",
"male:fox",
"male:fox boy",
@@ -156,8 +201,13 @@ object Male : TagList {
"male:gender morph",
"male:ghost",
"male:giant",
"male:giant sperm",
"male:gijinka",
"male:giraffe boy",
"male:glasses",
"male:glory hole",
"male:gloves",
"male:goat",
"male:goblin",
"male:gokkun",
"male:gorilla",
@@ -169,42 +219,63 @@ object Male : TagList {
"male:gyaru-oh",
"male:gymshorts",
"male:hair buns",
"male:hairjob",
"male:hairy",
"male:hairy armpits",
"male:handjob",
"male:hanging",
"male:harem",
"male:harpy",
"male:headphones",
"male:heterochromia",
"male:hijab",
"male:hood",
"male:horns",
"male:horse",
"male:horse boy",
"male:horse cock",
"male:hotpants",
"male:huge penis",
"male:human on furry",
"male:humiliation",
"male:hyena boy",
"male:impregnation",
"male:incest",
"male:infantilism",
"male:inflation",
"male:insect",
"male:insect boy",
"male:internal urination",
"male:inverted nipples",
"male:invisible",
"male:josou seme",
"male:kangaroo",
"male:kappa",
"male:kemonomimi",
"male:kigurumi pajama",
"male:kimono",
"male:kindergarten uniform",
"male:kissing",
"male:kunoichi",
"male:lab coat",
"male:lactation",
"male:large insertions",
"male:large tattoo",
"male:latex",
"male:layer cake",
"male:leash",
"male:leg lock",
"male:leotard",
"male:lingerie",
"male:lion",
"male:living clothes",
"male:lizard guy",
"male:long tongue",
"male:low bestiality",
"male:low guro",
"male:low scat",
"male:low shotacon",
"male:low smegma",
"male:machine",
"male:maggot",
"male:magical girl",
@@ -213,33 +284,46 @@ object Male : TagList {
"male:males only",
"male:masked face",
"male:masturbation",
"male:mecha boy",
"male:merman",
"male:mesuiki",
"male:metal armor",
"male:midget",
"male:miko",
"male:military",
"male:milking",
"male:mind break",
"male:mind control",
"male:miniguy",
"male:minotaur",
"male:mmm threesome",
"male:monkey",
"male:monkey boy",
"male:monoeye",
"male:monster",
"male:moral degeneration",
"male:mouse",
"male:mouse boy",
"male:mouth mask",
"male:multimouth blowjob",
"male:multiple arms",
"male:multiple assjob",
"male:multiple footjob",
"male:multiple handjob",
"male:multiple orgasms",
"male:multiple penises",
"male:multiple straddling",
"male:muscle",
"male:muscle growth",
"male:nakadashi",
"male:navel fuck",
"male:nazi",
"male:necrophilia",
"male:netorare",
"male:ninja",
"male:nipple birth",
"male:nipple fuck",
"male:nipple stimulation",
"male:nose fuck",
"male:nose hook",
"male:nun",
@@ -248,94 +332,131 @@ object Male : TagList {
"male:oil",
"male:old man",
"male:onahole",
"male:oni",
"male:orc",
"male:orgasm denial",
"male:ostrich",
"male:otokofutanari",
"male:otter boy",
"male:oyakodon",
"male:paizuri",
"male:panda boy",
"male:panther",
"male:pantyhose",
"male:pantyjob",
"male:parasite",
"male:pasties",
"male:pegasus",
"male:pegging",
"male:penis birth",
"male:personality excretion",
"male:petplay",
"male:petrification",
"male:phimosis",
"male:piercing",
"male:pig",
"male:pig man",
"male:pillory",
"male:pirate",
"male:piss drinking",
"male:pixie cut",
"male:plant boy",
"male:pole dancing",
"male:policeman",
"male:ponytail",
"male:possession",
"male:pregnant",
"male:prehensile hair",
"male:priest",
"male:prolapse",
"male:prostate massage",
"male:prostitution",
"male:pubic stubble",
"male:public use",
"male:pussyboys only",
"male:rabbit",
"male:raccoon boy",
"male:randoseru",
"male:rape",
"male:reptile",
"male:retractable penis",
"male:rhinoceros",
"male:rimjob",
"male:robot",
"male:ryona",
"male:saliva",
"male:scar",
"male:scat",
"male:school gym uniform",
"male:school swimsuit",
"male:schoolboy uniform",
"male:schoolgirl uniform",
"male:scrotal lingerie",
"male:selfcest",
"male:sex toys",
"male:shared senses",
"male:shark",
"male:shark boy",
"male:shaved head",
"male:sheep",
"male:sheep boy",
"male:shibari",
"male:shimaidon",
"male:shimapan",
"male:shotacon",
"male:shrinking",
"male:skinsuit",
"male:skunk boy",
"male:slave",
"male:sleeping",
"male:slime",
"male:slime boy",
"male:slug",
"male:small penis",
"male:smalldom",
"male:smegma",
"male:smell",
"male:smoking",
"male:snake",
"male:snake boy",
"male:snuff",
"male:sockjob",
"male:sole male",
"male:sole pussyboy",
"male:solo action",
"male:spanking",
"male:speculum",
"male:spider",
"male:spider boy",
"male:squid boy",
"male:squirrel boy",
"male:ssbbm",
"male:steward",
"male:stewardess",
"male:stirrup legwear",
"male:stockings",
"male:stomach deformation",
"male:straitjacket",
"male:strap-on",
"male:stretching",
"male:stuck in wall",
"male:sumata",
"male:sundress",
"male:sunglasses",
"male:sweating",
"male:swimsuit",
"male:swinging",
"male:syringe",
"male:table masturbation",
"male:tail",
"male:tail plug",
"male:tailjob",
"male:tailphagia",
"male:tall man",
"male:tanlines",
"male:teacher",
"male:tentacles",
"male:thick eyebrows",
"male:thigh high boots",
"male:tiara",
"male:tickling",
@@ -348,16 +469,23 @@ object Male : TagList {
"male:tracksuit",
"male:trampling",
"male:transformation",
"male:triple anal",
"male:triple penetration",
"male:tube",
"male:turtle",
"male:tutor",
"male:twins",
"male:twintails",
"male:unbirth",
"male:uncle",
"male:underwater",
"male:unicorn",
"male:unusual insertions",
"male:unusual pupils",
"male:unusual teeth",
"male:urethra insertion",
"male:urination",
"male:vacbed",
"male:vampire",
"male:very long hair",
"male:virginity",
@@ -366,6 +494,9 @@ object Male : TagList {
"male:voyeurism",
"male:waiter",
"male:waitress",
"male:weight gain",
"male:wet clothes",
"male:whale",
"male:whip",
"male:wings",
"male:witch",
@@ -374,8 +505,11 @@ object Male : TagList {
"male:wooden horse",
"male:worm",
"male:wormhole",
"male:wrestling",
"male:x-ray",
"male:yandere",
"male:yaoi",
"male:zebra",
"male:zombie",
)
}
+2 -2
View File
@@ -1,8 +1,7 @@
package exh.eh.tags
object Mixed : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"mixed:animal on animal",
"mixed:body swap",
"mixed:ffm threesome",
@@ -16,6 +15,7 @@ object Mixed : TagList {
"mixed:multiple assjob",
"mixed:multiple footjob",
"mixed:multiple handjob",
"mixed:nudism",
"mixed:oyakodon",
"mixed:shimaidon",
"mixed:ttm threesome",
+5 -3
View File
@@ -1,9 +1,9 @@
package exh.eh.tags
object Other : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"other:3d",
"other:3d imageset",
"other:already uploaded",
"other:anaglyph",
"other:animated",
@@ -13,10 +13,12 @@ object Other : TagList {
"other:comic",
"other:compilation",
"other:dakimakura",
"other:defaced",
"other:figure",
"other:forbidden content",
"other:full censorship",
"other:full color",
"other:game manual",
"other:game sprite",
"other:goudoushi",
"other:hardcore",
@@ -33,10 +35,10 @@ object Other : TagList {
"other:nudity only",
"other:out of order",
"other:paperchild",
"other:poor grammar",
"other:realporn",
"other:redraw",
"other:replaced",
"other:rough grammar",
"other:rough translation",
"other:sample",
"other:scanmark",
+68 -7
View File
@@ -1,7 +1,7 @@
package exh.eh.tags
object Parody : TagList {
override fun getTags1() = listOf(
override fun getTags1(): List<String> = listOf(
"parody:.hack",
"parody:.hackg.u.",
"parody:.hacklegend of the twilight",
@@ -31,6 +31,7 @@ object Parody : TagList {
"parody:a town where you live",
"parody:a vampyre story",
"parody:a.d.police",
"parody:a.i. ga tomaranai",
"parody:abenobashi mahou shoutengai",
"parody:acca 13-ku kansatsu-ka",
"parody:accel world",
@@ -66,8 +67,10 @@ object Parody : TagList {
"parody:akira",
"parody:aku no musume",
"parody:aku no onna kanbu",
"parody:akuma no memumemu-chan",
"parody:akuyaku reijou nanode last boss o kattemimashita",
"parody:aladdin",
"parody:albatross koukairoku",
"parody:aldnoah.zero",
"parody:alice gear aegis",
"parody:alice in wonderland",
@@ -124,6 +127,7 @@ object Parody : TagList {
"parody:ar nosurge",
"parody:ar tonelico",
"parody:ar tonelico qoga",
"parody:araburu kisetsu no otome-domo yo",
"parody:araiguma rascal",
"parody:arasaa mama no watashi de ii no",
"parody:arcana famiglia",
@@ -194,6 +198,7 @@ object Parody : TagList {
"parody:backbeard-sama ga miteru",
"parody:bagi the monster of mighty nature",
"parody:baka to test to shoukanjuu",
"parody:bakemono no ko",
"parody:bakemonogatari",
"parody:bakuen campus guardress",
"parody:bakugan",
@@ -206,6 +211,7 @@ object Parody : TagList {
"parody:ballroom e youkoso",
"parody:bamboo blade",
"parody:band yarouze",
"parody:banished from the heros party i decided to live a quiet life in the countryside",
"parody:banjo-kazooie",
"parody:banner of the stars",
"parody:baribari densetsu",
@@ -271,6 +277,7 @@ object Parody : TagList {
"parody:bloodstained",
"parody:bloody roar",
"parody:blue dragon",
"parody:blue skin no mori",
"parody:blue spring ride",
"parody:blue submarine no. 6",
"parody:bna brand new animal",
@@ -355,6 +362,7 @@ object Parody : TagList {
"parody:childs play",
"parody:chio-chan no tsuugakuro",
"parody:chip n dale rescue rangers",
"parody:cho aniki",
"parody:chobits",
"parody:chogattai majutsu robot ginguiser",
"parody:chokotto sister",
@@ -428,6 +436,7 @@ object Parody : TagList {
"parody:daiakuji",
"parody:daibanchou -big bang age-",
"parody:daicon",
"parody:daihanjou manpuku marche",
"parody:daikoukai jidai",
"parody:daisenryaku",
"parody:daiya no ace",
@@ -440,6 +449,7 @@ object Parody : TagList {
"parody:dantalian no shoka",
"parody:daphne in the brilliant blue",
"parody:dark chronicle",
"parody:dark messiah",
"parody:dark water",
"parody:darker than black",
"parody:darkstalkers",
@@ -457,6 +467,7 @@ object Parody : TagList {
"parody:defenders",
"parody:defense devil",
"parody:defense of the ancients",
"parody:delicious party precure",
"parody:demento",
"parody:demi-chan wa kataritai",
"parody:demonbane",
@@ -480,6 +491,7 @@ object Parody : TagList {
"parody:devil may cry",
"parody:devil summoner soul hackers",
"parody:devil survivor",
"parody:devilman lady",
"parody:dexters laboratory",
"parody:di gi charat",
"parody:diablo",
@@ -550,6 +562,7 @@ object Parody : TagList {
"parody:dragonaut",
"parody:dragonica",
"parody:dragons crown",
"parody:dragons raiden",
"parody:drakengard",
"parody:drawn together",
"parody:dream c club",
@@ -619,6 +632,7 @@ object Parody : TagList {
"parody:family project",
"parody:fancy lala",
"parody:fantastic four",
"parody:fantasy bishoujo juniku ojisan to",
"parody:fantasy earth zero",
"parody:far east of eden kabuki klash",
"parody:fatal frame",
@@ -706,6 +720,7 @@ object Parody : TagList {
"parody:fushigi no umi no nadia",
"parody:fushigi yuugi",
"parody:fushigiboshi no futagohime",
"parody:futaba channel",
"parody:futakoi",
"parody:futari ecchi",
"parody:futari wa precure splash star",
@@ -723,6 +738,7 @@ object Parody : TagList {
"parody:ga-rei",
"parody:gad guard",
"parody:gaiking",
"parody:gaikotsu kishi-sama tadaima isekai e odekakechuu",
"parody:gakkou gurashi",
"parody:gakkou no kaidan",
"parody:gakuen alice",
@@ -733,6 +749,7 @@ object Parody : TagList {
"parody:galaxy cyclone braiger",
"parody:galaxy express 999",
"parody:galaxy fraulein yuna",
"parody:galleria no chika meikyuu to majo no ryodan",
"parody:gan kon",
"parody:ganbare goemon",
"parody:gangsta.",
@@ -753,6 +770,7 @@ object Parody : TagList {
"parody:gen 13",
"parody:gen colon lock",
"parody:genji tsuushin agedama",
"parody:genkai tokki monster monpiece",
"parody:genmu no tou to tsurugi no okite",
"parody:genmu senki leda",
"parody:genroh",
@@ -824,6 +842,7 @@ object Parody : TagList {
"parody:gundam 00",
"parody:gundam 0083",
"parody:gundam age",
"parody:gundam breaker mobile",
"parody:gundam build fighters",
"parody:gundam g no reconguista",
"parody:gundam seed",
@@ -856,6 +875,7 @@ object Parody : TagList {
"parody:hakumei to mikochi",
"parody:hakuouki",
"parody:hakushaku to yousei",
"parody:hakushon daimaou",
"parody:half-life",
"parody:halo",
"parody:hamtaro",
@@ -906,8 +926,10 @@ object Parody : TagList {
"parody:highschool dxd",
"parody:highschool of the dead",
"parody:higurashi no naku koro ni",
"parody:higyaku no noel",
"parody:hikarian",
"parody:hikaru no go",
"parody:hikounin sentai akibaranger",
"parody:hime chen otogi chikku idol lilpri",
"parody:hime kishi lilia",
"parody:hime-chans ribbon",
@@ -918,6 +940,7 @@ object Parody : TagList {
"parody:history kikan",
"parody:historys strongest disciple kenichi",
"parody:hitomi no karte",
"parody:hitomi-chan wa hitomishiri",
"parody:hitomi-sensei no hokenshitsu",
"parody:hitori bocchi no marumaru seikatsu",
"parody:hitsugi katsugi no kuro.",
@@ -978,6 +1001,7 @@ object Parody : TagList {
"parody:ikoku meiro no croisee",
"parody:imouto sae ireba ii.",
"parody:in search of the lost future",
"parody:inaka ni kaeru to yakeni natsuita kasshoku ponytail shota ga iru",
"parody:inazuma eleven",
"parody:inazuman",
"parody:inda no himekishi janne",
@@ -998,6 +1022,7 @@ object Parody : TagList {
"parody:is",
"parody:isekai izakaya nobu",
"parody:isekai maou to shoukan shoujo no dorei majutsu",
"parody:isekai meikyuu de harem o",
"parody:isekai no seikishi monogatari",
"parody:isekai shokudou",
"parody:isekai wa smartphone to tomo ni.",
@@ -1007,6 +1032,7 @@ object Parody : TagList {
"parody:its not my fault that im not popular",
"parody:iwa kakeru",
"parody:ixion saga dt",
"parody:iya na kao sare nagara opantsu misete moraitai",
"parody:izuna legend of the unemployed ninja",
"parody:jackie chan adventures",
"parody:jaja uma grooming up",
@@ -1035,9 +1061,11 @@ object Parody : TagList {
"parody:johnny bravo",
"parody:johnny test",
"parody:jojos bizarre adventure",
"parody:joshikousei girls high",
"parody:joshikousei no mudazukai",
"parody:joukamachi no dandelion",
"parody:jouki toshi no tantei shoujo",
"parody:ju-on",
"parody:jubei-chan",
"parody:jumping rabbit",
"parody:jungle de ikou",
@@ -1060,11 +1088,13 @@ object Parody : TagList {
"parody:k.o. beast",
"parody:kaerunyo panyorn",
"parody:kage no densetsu",
"parody:kageki shojo",
"parody:kagihime monogatari eikyuu alice rondo",
"parody:kaguya-sama wa kokurasetai",
"parody:kaichou wa maid-sama",
"parody:kaifuku jutsushi no yarinaoshi",
"parody:kaiji",
"parody:kaijin kaihatsubu no kuroitsu-san",
"parody:kaiketsu zorro",
"parody:kaitou tenshi twin angel",
"parody:kaizoku sentai gokaiger",
@@ -1080,6 +1110,7 @@ object Parody : TagList {
"parody:kami-tachi ni hirowareta otoko",
"parody:kamikaze kaitou jeanne",
"parody:kamisama dolls",
"parody:kamisama hajimemashita",
"parody:kamisama minarai himitsu no cocotama",
"parody:kamisama ni natta hi",
"parody:kampfer",
@@ -1103,6 +1134,7 @@ object Parody : TagList {
"parody:katekyo hitman reborn",
"parody:katsute mahou shoujo to aku wa tekitai shite ita.",
"parody:katte ni kaizou",
"parody:kawaii dake ja nai shikimori-san",
"parody:kawaikereba hentai demo suki ni natte kuremasu ka",
"parody:kaze no na wa amnesia",
"parody:kaze no yojimbo",
@@ -1113,6 +1145,7 @@ object Parody : TagList {
"parody:kemeko deluxe",
"parody:kemono friends",
"parody:kemono jihen",
"parody:kenja no deshi o nanoru kenja",
"parody:kenja no mago",
"parody:kenka banchou otome",
"parody:kenkou zenrakei suieibu umishou",
@@ -1146,6 +1179,7 @@ object Parody : TagList {
"parody:kingdom hearts",
"parody:kinnikuman",
"parody:kino no tabi",
"parody:kinsou no vermeil",
"parody:kira kira",
"parody:kirakira precure a la mode",
"parody:kirarin revolution",
@@ -1173,6 +1207,7 @@ object Parody : TagList {
"parody:komi-san wa komyushou desu.",
"parody:konjiki no word master",
"parody:kono aozora ni yakusoku o",
"parody:kono healer mendokusai",
"parody:kono oto tomare",
"parody:kono subarashii sekai ni syukufuku o",
"parody:konto koroshiya 1989",
@@ -1216,9 +1251,11 @@ object Parody : TagList {
"parody:kyoukai no rinne",
"parody:kyoukai senjou no horizon",
"parody:kyouryuu wakusei",
"parody:kyuuketsuki sugu shinu",
"parody:kyuukyoku choujin r",
"parody:kyuukyoku hentai kamen",
"parody:kyuukyoku shinka shita full dive rpg ga genjitsu yori mo kusogee dattara",
"parody:kyuukyuu sentai gogofive",
"parody:kyuushu sentai danjija",
"parody:la corda doro",
"parody:la pucelle",
@@ -1232,6 +1269,7 @@ object Parody : TagList {
"parody:le ranch",
"parody:league of legends",
"parody:left 4 dead",
"parody:legend of legaia",
"parody:legend of lyon flare",
"parody:legend of queen opala",
"parody:legend of the cryptids",
@@ -1326,6 +1364,8 @@ object Parody : TagList {
"parody:mahouka koukou no rettousei",
"parody:mahoutsukai no yakusoku",
"parody:mahoutsukai no yome",
"parody:mahoutsukai reimeiki",
"parody:mai mai miracle",
"parody:mai-hime",
"parody:mai-otome",
"parody:mairimashita iruma-kun",
@@ -1383,6 +1423,7 @@ object Parody : TagList {
"parody:mayoi neko overrun",
"parody:maze runner",
"parody:mazinger z",
"parody:me me me",
"parody:mecha mote",
"parody:mechakko dotakon",
"parody:medabots",
@@ -1423,6 +1464,7 @@ object Parody : TagList {
"parody:mirmo de pon",
"parody:miss machiko",
"parody:mission impossible",
"parody:misumisou",
"parody:mitsudomoe",
"parody:mitsume ga tooru",
"parody:mitsumete knight",
@@ -1479,7 +1521,6 @@ object Parody : TagList {
"parody:muv-luv alternative total eclipse",
"parody:mx0",
"parody:my dad the rock star",
"parody:my dress-up darling",
"parody:my hero academia",
"parody:my life as a teenage robot",
"parody:my little pony friendship is magic",
@@ -1508,6 +1549,7 @@ object Parody : TagList {
"parody:nazo no kanojo x",
"parody:nee chanto shiyou yo",
"parody:nee summer",
"parody:needy streamer overload",
"parody:nejimaki seirei senki tenkyou no alderamin",
"parody:nekketsu saikyou go-saurer",
"parody:nekome kozou",
@@ -1563,6 +1605,7 @@ object Parody : TagList {
"parody:oda nobuna no yabou",
"parody:odin sphere",
"parody:odoru daisousasen",
"parody:odyssey",
"parody:oira uchuu no tankoufu",
"parody:ojama yurei-kun",
"parody:ojamajo doremi",
@@ -1636,6 +1679,7 @@ object Parody : TagList {
"parody:parappa the rapper",
"parody:parasite eve",
"parody:parasyte",
"parody:paripi koumei",
"parody:pastel",
"parody:pastel yumi",
"parody:patlabor",
@@ -1709,6 +1753,7 @@ object Parody : TagList {
"parody:pu-li-ru-la",
"parody:puella magi madoka magica",
"parody:pumpkin scissors",
"parody:punch-out",
"parody:puppet princess of marl kingdom",
"parody:pussy saga",
"parody:puyo puyo",
@@ -1740,6 +1785,7 @@ object Parody : TagList {
"parody:red pride of eden",
"parody:redline",
"parody:redwall",
"parody:refrain no chika meikyuu to majo no ryodan",
"parody:regalia the three sacred stars",
"parody:reibaishi izuna",
"parody:remi nobodys girl",
@@ -1822,6 +1868,7 @@ object Parody : TagList {
"parody:sarah and duck",
"parody:sasameki koto",
"parody:sasami magical girls club",
"parody:satsukare",
"parody:savage reign",
"parody:sayonara zetsubou sensei",
"parody:school days",
@@ -1838,6 +1885,7 @@ object Parody : TagList {
"parody:seiken densetsu",
"parody:seiken densetsu 2",
"parody:seiken densetsu 3",
"parody:seiken densetsu ds",
"parody:seikesshou albatross",
"parody:seikon no qwaser",
"parody:seirei no moribito",
@@ -1869,12 +1917,14 @@ object Parody : TagList {
"parody:sensei no bulge",
"parody:sentimental graffiti",
"parody:sentouin hakenshimasu",
"parody:senyoku no sigrdrifa",
"parody:serial experiments lain",
"parody:seto no hanayome",
"parody:seven mortal sins",
"parody:seven of seven",
"parody:sewayaki kitsune no senko-san",
"parody:sexfriend",
"parody:shachiku-san wa youjo yuurei ni iyasaretai.",
"parody:shadow of the colossus",
"parody:shakugan no shana",
"parody:shakunetsu no nirai kanai",
@@ -1912,6 +1962,7 @@ object Parody : TagList {
"parody:shinryaku ika musume",
"parody:shinseiki inma seiden",
"parody:shinsekai yori",
"parody:shiroi suna no aquatope",
"parody:shironeko project",
"parody:shisha no teikoku",
"parody:shishunki renaissance david-kun",
@@ -1951,9 +2002,13 @@ object Parody : TagList {
"parody:snow white and the seven dwarfs",
"parody:sokihei m.d. geist",
"parody:solatorobo",
)
override fun getTags2(): List<String> = listOf(
"parody:soltyrei",
"parody:sonic soldier borgman",
"parody:sonic the hedgehog",
"parody:sono bisque doll wa koi o suru",
"parody:sono hanabira ni kuchizuke o",
"parody:sora no iro mizu no iro",
"parody:sora no kanata no dystopia",
@@ -1967,6 +2022,7 @@ object Parody : TagList {
"parody:soredemo machi wa mawatteiru",
"parody:soredemo tsuma o aishiteru",
"parody:soromon no kagi",
"parody:sou-bou-tei kowasubeshi",
"parody:soukou kijo iris",
"parody:soukyuu no fafner",
"parody:soul cradle",
@@ -2002,9 +2058,6 @@ object Parody : TagList {
"parody:star gladiator",
"parody:star ocean",
"parody:star ocean 2",
)
override fun getTags2() = listOf(
"parody:star ocean 3",
"parody:star ocean 4",
"parody:star trek",
@@ -2055,6 +2108,7 @@ object Parody : TagList {
"parody:sword art online",
"parody:sword art online alternative gun gale online",
"parody:sword girls",
"parody:sword of the stranger",
"parody:sword world rpg",
"parody:sym-bionic titan",
"parody:t.u.f.f. puppy",
@@ -2087,6 +2141,7 @@ object Parody : TagList {
"parody:tantei opera milky holmes",
"parody:tantei wa mou shindeiru.",
"parody:tari tari",
"parody:tartaros",
"parody:tasogare otome x amnesia",
"parody:tasogare sakaba uwabami breakers",
"parody:tatakae tarantella",
@@ -2236,6 +2291,7 @@ object Parody : TagList {
"parody:the x-files",
"parody:they are my noble masters",
"parody:this ugly yet beautiful world",
"parody:thomas the tank engine and friends",
"parody:threads of fate",
"parody:three from prostokvashino",
"parody:thundercats",
@@ -2244,8 +2300,7 @@ object Parody : TagList {
"parody:tiny toons",
"parody:to heart",
"parody:to love-ru",
"parody:toaru kagaku no railgun",
"parody:toaru majutsu no index",
"parody:toaru project",
"parody:tobe isami",
"parody:togainu no chi",
"parody:toheart2",
@@ -2259,6 +2314,7 @@ object Parody : TagList {
"parody:tokyo mew mew",
"parody:tokyo red hood",
"parody:tom and jerry",
"parody:tom clancys ghost recon",
"parody:tom clancys rainbow six",
"parody:tomb raider",
"parody:tomodachi no imouto ga ore ni dake uzai",
@@ -2306,6 +2362,7 @@ object Parody : TagList {
"parody:twinkle review",
"parody:uchi no musume ni te o dasuna",
"parody:uchouten kazoku",
"parody:uchuu eiyuu monogatari",
"parody:uchuu kaizoku sara",
"parody:uchuu kazoku carlvinson",
"parody:uchuu no kishi tekkaman",
@@ -2332,6 +2389,7 @@ object Parody : TagList {
"parody:uta no prince-sama",
"parody:utawarerumono",
"parody:utawarerumono itsuwari no kamen",
"parody:utsukushiki sei no dendoushi rei rei",
"parody:uzaki-chan wa asobitai",
"parody:va-11 hall-a",
"parody:valkyria chronicles",
@@ -2412,6 +2470,7 @@ object Parody : TagList {
"parody:witchblade",
"parody:witchs weapon",
"parody:with you",
"parody:wixoss diva a live",
"parody:wizard of oz",
"parody:wolfs rain",
"parody:wooser no sonohigurashi",
@@ -2437,6 +2496,7 @@ object Parody : TagList {
"parody:yagate kimi ni naru",
"parody:yahari ore no seishun love come wa machigatteiru",
"parody:yakitate japan",
"parody:yakumo-san wa ezuke ga shitai.",
"parody:yakushiji ryouko no kaiki jikenbo",
"parody:yakusoku no neverland",
"parody:yama no susume",
@@ -2453,6 +2513,7 @@ object Parody : TagList {
"parody:yiik",
"parody:yin yang yo",
"parody:yoake mae yori ruriiro na",
"parody:yofukashi no uta",
"parody:yokohama kaidashi kikou",
"parody:yomawari",
"parody:yondemasuyo azazel-san",
@@ -1,8 +1,7 @@
package exh.eh.tags
object ReClass : TagList {
override fun getTags1() = listOf(
object Reclass : TagList {
override fun getTags1(): List<String> = listOf(
"reclass:artistcg",
"reclass:asianporn",
"reclass:cosplay",
+1 -4
View File
@@ -7,12 +7,9 @@ interface TagList {
fun getTags3(): List<String> = emptyList()
fun getTags4(): List<String> = emptyList()
fun getTags() = listOf(
fun getTags(): List<List<String>> = listOf(
getTags1(),
getTags2(),
getTags3(),
getTags4(),
)
}
@@ -3,7 +3,6 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@@ -13,11 +12,11 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
class AzukiHandler(currentClient: OkHttpClient) {
class AzukiHandler(currentClient: OkHttpClient, userAgent: String) {
val baseUrl = "https://www.azuki.co"
private val apiUrl = "https://production.api.azuki.co"
val headers = Headers.Builder()
.add("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.add("User-Agent", userAgent)
.build()
val client: OkHttpClient = currentClient
@@ -3,7 +3,6 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.booleanOrNull
@@ -16,11 +15,11 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
class ComikeyHandler(cloudflareClient: OkHttpClient) {
class ComikeyHandler(cloudflareClient: OkHttpClient, userAgent: String) {
val baseUrl = "https://comikey.com"
private val apiUrl = "$baseUrl/sapi"
val headers = Headers.Builder()
.add("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.add("User-Agent", userAgent)
.build()
val client: OkHttpClient = cloudflareClient
@@ -3,7 +3,6 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@@ -12,11 +11,11 @@ import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response
class MangaHotHandler(currentClient: OkHttpClient) {
class MangaHotHandler(currentClient: OkHttpClient, userAgent: String) {
val baseUrl = "https://mangahot.jp"
private val apiUrl = "https://api.mangahot.jp"
val headers = Headers.Builder()
.add("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.add("User-Agent", userAgent)
.build()
val client: OkHttpClient = currentClient
+46 -43
View File
@@ -1,49 +1,52 @@
package exh.md.utils
@Suppress("unused")
enum class MdLang(val lang: String, val prettyPrint: String, val extLang: String = lang) {
ENGLISH("en", "English"),
JAPANESE("ja", "Japanese"),
POLISH("pl", "Polish"),
SERBO_CROATIAN("rs", "Serbo-Croatian", "sh"),
DUTCH("nl", "Dutch"),
ITALIAN("it", "IT"),
RUSSIAN("ru", "Russian"),
GERMAN("de", "German"),
HUNGARIAN("hu", "Hungarian"),
FRENCH("fr", "French"),
FINNISH("fi", "Finnish"),
VIETNAMESE("vi", "Vietnamese"),
GREEK("el", "Greek"),
BULGARIAN("bg", "BULGARIN"),
SPANISH_ES("es", "Spanish (Es)"),
PORTUGUESE_BR("pt-br", "Portuguese (Br)", "pt-BR"),
PORTUGUESE("pt", "Portuguese (Pt)"),
SWEDISH("sv", "Swedish"),
ARABIC("ar", "Arabic"),
DANISH("da", "Danish"),
CHINESE_SIMPLIFIED("zh", "Chinese (Simp)", "zh-Hans"),
BENGALI("bn", "Bengali"),
ROMANIAN("ro", "Romanian"),
CZECH("cs", "Czech"),
MONGOLIAN("mn", "Mongolian"),
TURKISH("tr", "Turkish"),
INDONESIAN("id", "Indonesian"),
KOREAN("kr", "Korean", "ko"),
SPANISH_LATAM("es-la", "Spanish (LATAM)", "es-419"),
PERSIAN("fa", "Persian"),
MALAY("ms", "Malay"),
THAI("th", "Thai"),
CATALAN("ca", "Catalan"),
FILIPINO("tl", "Filipino", "fil"),
CHINESE_TRAD("zh-hk", "Chinese (Trad)", "zh-Hant"),
UKRAINIAN("uk", "Ukrainian"),
BURMESE("my", "Burmese"),
LINTHUANIAN("lt", "Lithuanian"),
HEBREW("he", "Hebrew"),
HINDI("hi", "Hindi"),
NORWEGIAN("no", "Norwegian"),
NEPALI("ne", "Nepali")
enum class MdLang(val lang: String, val extLang: String = lang) {
ENGLISH("en"),
JAPANESE("ja"),
POLISH("pl"),
SERBO_CROATIAN("rs", "sh"),
DUTCH("nl"),
ITALIAN("it"),
RUSSIAN("ru"),
GERMAN("de"),
HUNGARIAN("hu"),
FRENCH("fr"),
FINNISH("fi"),
VIETNAMESE("vi"),
GREEK("el"),
BULGARIAN("bg"),
SPANISH_ES("es"),
PORTUGUESE_BR("pt-br", "pt-BR"),
PORTUGUESE("pt"),
SWEDISH("sv"),
ARABIC("ar"),
DANISH("da"),
CHINESE_SIMPLIFIED("zh", "zh-Hans"),
BENGALI("bn"),
ROMANIAN("ro"),
CZECH("cs"),
MONGOLIAN("mn"),
TURKISH("tr"),
INDONESIAN("id"),
KOREAN("kr", "ko"),
SPANISH_LATAM("es-la", "es-419"),
PERSIAN("fa"),
MALAY("ms"),
THAI("th"),
CATALAN("ca"),
FILIPINO("tl", "fil"),
CHINESE_TRAD("zh-hk", "zh-Hant"),
UKRAINIAN("uk"),
BURMESE("my"),
LINTHUANIAN("lt"),
HEBREW("he"),
HINDI("hi"),
NORWEGIAN("no"),
NEPALI("ne"),
LATIN("la"),
TAMIL("ta"),
KAZAKH("kk"),
;
companion object {
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM17,18H7v-2h10V18zM10.3,14L7,10.7l1.4,-1.4l1.9,1.9l5.3,-5.3L17,7.3L10.3,14z" />
</vector>
@@ -9,8 +9,7 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="40dp"
android:padding="8dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<TextView
@@ -20,10 +19,12 @@
android:layout_marginEnd="8dp"
android:gravity="center" />
<com.google.android.material.switchmaterial.SwitchMaterial
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/dedupe_switch"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:paddingVertical="16dp"
android:paddingHorizontal="16dp"/>
</LinearLayout>

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