Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd120c5081 | |||
| 34e9d9f146 | |||
| b7f7187293 | |||
| 4abadea4f9 | |||
| 1b3d76398b | |||
| 688fdecaf8 | |||
| 0bedee1778 | |||
| bb89f9f636 | |||
| f8011981eb | |||
| 7e17e52e07 | |||
| b65990ad29 | |||
| d9560d40de | |||
| 036ab3351d | |||
| 769293355f | |||
| 850d81600e | |||
| ce96b53f10 | |||
| b98dfd65b5 | |||
| 612e0a00bc | |||
| d286cf3267 | |||
| 1a28c7fb35 | |||
| 5909f90003 | |||
| 48f7b701dc | |||
| b17530ccc3 | |||
| f844a48b67 | |||
| 66929e097c | |||
| be30814d35 | |||
| 5d56c1961d | |||
| 4aa52a2576 | |||
| f7a1869066 | |||
| 2f1d76cbac | |||
| 5c5e08b99b | |||
| cc16d53ecc | |||
| 28fa3855c2 | |||
| 5a47a58e1e | |||
| c86714ef59 | |||
| 75fe57b851 | |||
| b9fffc45cc | |||
| de6cd169d0 | |||
| 95e8a02e33 | |||
| c720f0ac5c | |||
| 76af3b59f0 | |||
| 3f8cce8a32 | |||
| 26cfb4811f | |||
| e5a6d1b456 | |||
| f0b621dfe5 | |||
| d88f570f65 | |||
| b430e31da4 | |||
| 271f2d37bb | |||
| c2e36b4c5c | |||
| cb25deb5ac | |||
| a6c6cf77bb | |||
| e3dae57e0b | |||
| 226321f334 | |||
| 2187731d70 | |||
| fd32f2e879 | |||
| 5a094850d1 | |||
| e74053e989 | |||
| 798db44908 | |||
| 7715b5bdd0 | |||
| 084e11f21d | |||
| 01792c0618 | |||
| 0b93ceaa8f | |||
| bfdbe18509 | |||
| e3245d0610 | |||
| c2df6ee54a | |||
| ffc1e2d97b | |||
| d0c8b0c98a | |||
| f206ab8b32 | |||
| a443629234 | |||
| 3de4711e03 |
@@ -53,7 +53,7 @@ body:
|
|||||||
label: TachiyomiSY version
|
label: TachiyomiSY version
|
||||||
description: You can find your TachiyomiSY version in **More → About**.
|
description: You can find your TachiyomiSY version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "1.10.5"
|
Example: "1.11.0"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ body:
|
|||||||
label: Android version
|
label: Android version
|
||||||
description: You can find this somewhere in your Android settings.
|
description: You can find this somewhere in your Android settings.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "Android 11"
|
Example: "Android 14"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ body:
|
|||||||
label: Device
|
label: Device
|
||||||
description: List your device and model.
|
description: List your device and model.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "Google Pixel 5"
|
Example: "Google Pixel 8"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https:/mihon.app/docs/guides/troubleshooting/).
|
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https:/mihon.app/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[1.10.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[1.10.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
- label: I have updated the app to version **[1.11.0](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
+22
-25
@@ -1,7 +1,8 @@
|
|||||||
|
@file:Suppress("ChromeOsAbiSupport")
|
||||||
|
|
||||||
import mihon.buildlogic.getBuildTime
|
import mihon.buildlogic.getBuildTime
|
||||||
import mihon.buildlogic.getCommitCount
|
import mihon.buildlogic.getCommitCount
|
||||||
import mihon.buildlogic.getGitSha
|
import mihon.buildlogic.getGitSha
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("mihon.android.application")
|
id("mihon.android.application")
|
||||||
@@ -30,8 +31,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
|
|
||||||
versionCode = 69
|
versionCode = 71
|
||||||
versionName = "1.10.5"
|
versionName = "1.11.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@@ -141,6 +142,24 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs.addAll(
|
||||||
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
|
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
|
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.i18n)
|
implementation(projects.i18n)
|
||||||
// SY -->
|
// SY -->
|
||||||
@@ -307,28 +326,6 @@ androidComponents {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
|
||||||
withType<KotlinCompile> {
|
|
||||||
compilerOptions.freeCompilerArgs.addAll(
|
|
||||||
"-Xcontext-receivers",
|
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
|
||||||
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath(kotlinx.gradle)
|
classpath(kotlinx.gradle)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@@ -45,21 +46,6 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list containing only elements matching the given [predicate].
|
|
||||||
*
|
|
||||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
|
||||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
|
||||||
* collections that are created by code we control and are known to support random access.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
|
||||||
contract { callsInPlace(predicate) }
|
|
||||||
val destination = ArrayList<T>()
|
|
||||||
fastForEach { if (predicate(it)) destination.add(it) }
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list containing all elements not matching the given [predicate].
|
* Returns a list containing all elements not matching the given [predicate].
|
||||||
*
|
*
|
||||||
@@ -70,27 +56,7 @@ inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
|||||||
@OptIn(ExperimentalContracts::class)
|
@OptIn(ExperimentalContracts::class)
|
||||||
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||||
contract { callsInPlace(predicate) }
|
contract { callsInPlace(predicate) }
|
||||||
val destination = ArrayList<T>()
|
return fastFilter { !predicate(it) }
|
||||||
fastForEach { if (!predicate(it)) destination.add(it) }
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list containing only the non-null results of applying the
|
|
||||||
* given [transform] function to each element in the original collection.
|
|
||||||
*
|
|
||||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
|
||||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
|
||||||
* collections that are created by code we control and are known to support random access.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
|
||||||
contract { callsInPlace(transform) }
|
|
||||||
val destination = ArrayList<R>()
|
|
||||||
fastForEach { element ->
|
|
||||||
transform(element)?.let(destination::add)
|
|
||||||
}
|
|
||||||
return destination
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,26 +97,3 @@ inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
|
|||||||
fastForEach { if (predicate(it)) --count }
|
fastForEach { if (predicate(it)) --count }
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list containing only elements from the given collection
|
|
||||||
* having distinct keys returned by the given [selector] function.
|
|
||||||
*
|
|
||||||
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
|
|
||||||
* The elements in the resulting list are in the same order as they were in the source collection.
|
|
||||||
*
|
|
||||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
|
||||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
|
||||||
* collections that are created by code we control and are known to support random access.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
|
|
||||||
contract { callsInPlace(selector) }
|
|
||||||
val set = HashSet<K>()
|
|
||||||
val list = ArrayList<T>()
|
|
||||||
fastForEach {
|
|
||||||
val key = selector(it)
|
|
||||||
if (set.add(key)) list.add(it)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.domain.base
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -30,4 +31,6 @@ class BasePreferences(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||||
|
|
||||||
|
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
@@ -14,7 +15,6 @@ import androidx.compose.material3.SnackbarHost
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -33,20 +33,13 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
fun TabbedScreen(
|
fun TabbedScreen(
|
||||||
titleRes: StringResource,
|
titleRes: StringResource,
|
||||||
tabs: ImmutableList<TabContent>,
|
tabs: ImmutableList<TabContent>,
|
||||||
startIndex: Int? = null,
|
state: PagerState = rememberPagerState { tabs.size },
|
||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
onChangeSearchQuery: (String?) -> Unit = {},
|
onChangeSearchQuery: (String?) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val state = rememberPagerState { tabs.size }
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
LaunchedEffect(startIndex) {
|
|
||||||
if (startIndex != null) {
|
|
||||||
state.scrollToPage(startIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val tab = tabs[state.currentPage]
|
val tab = tabs[state.currentPage]
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ internal fun LibraryTabs(
|
|||||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
// SY -->
|
|
||||||
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
||||||
// SY <--
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.zIndex(1f),
|
modifier = Modifier.zIndex(1f),
|
||||||
) {
|
) {
|
||||||
|
|||||||
+119
-25
@@ -15,7 +15,6 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -23,7 +22,6 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
data class SyncFavoritesProgressProperties(
|
data class SyncFavoritesProgressProperties(
|
||||||
val title: String,
|
val title: String,
|
||||||
val text: String,
|
val text: String,
|
||||||
val canDismiss: Boolean,
|
|
||||||
val positiveButtonText: String? = null,
|
val positiveButtonText: String? = null,
|
||||||
val positiveButton: (() -> Unit)? = null,
|
val positiveButton: (() -> Unit)? = null,
|
||||||
val negativeButtonText: String? = null,
|
val negativeButtonText: String? = null,
|
||||||
@@ -34,18 +32,23 @@ data class SyncFavoritesProgressProperties(
|
|||||||
fun SyncFavoritesProgressDialog(
|
fun SyncFavoritesProgressDialog(
|
||||||
status: FavoritesSyncStatus,
|
status: FavoritesSyncStatus,
|
||||||
setStatusIdle: () -> Unit,
|
setStatusIdle: () -> Unit,
|
||||||
openManga: (Manga) -> Unit,
|
openManga: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
||||||
when (status) {
|
when (status) {
|
||||||
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message),
|
text = context.stringResource(
|
||||||
canDismiss = false,
|
SYMR.strings.favorites_sync_bad_library_state,
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_gallery_in_multiple_categories, status.mangaTitle,
|
||||||
|
status.categories.joinToString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
|
positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
|
||||||
positiveButton = {
|
positiveButton = {
|
||||||
openManga(status.manga)
|
openManga(status.mangaId)
|
||||||
setStatusIdle()
|
setStatusIdle()
|
||||||
},
|
},
|
||||||
negativeButtonText = context.stringResource(MR.strings.action_ok),
|
negativeButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
@@ -53,31 +56,122 @@ fun SyncFavoritesProgressDialog(
|
|||||||
)
|
)
|
||||||
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
|
title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message),
|
text = context.stringResource(
|
||||||
canDismiss = false,
|
SYMR.strings.favorites_sync_done_errors_message,
|
||||||
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
status.messages.joinToString(separator = "\n") {
|
||||||
positiveButton = setStatusIdle,
|
when (it) {
|
||||||
)
|
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
|
||||||
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties(
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
context.stringResource(
|
||||||
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message),
|
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
|
||||||
canDismiss = false,
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, it.url,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, it.title, it.gid)
|
||||||
|
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
positiveButton = setStatusIdle,
|
positiveButton = setStatusIdle,
|
||||||
)
|
)
|
||||||
is FavoritesSyncStatus.Idle -> value = null
|
is FavoritesSyncStatus.Idle -> value = null
|
||||||
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> {
|
is FavoritesSyncStatus.Initializing -> {
|
||||||
value = SyncFavoritesProgressProperties(
|
value = SyncFavoritesProgressProperties(
|
||||||
title = context.stringResource(SYMR.strings.favorites_syncing),
|
title = context.stringResource(SYMR.strings.favorites_syncing),
|
||||||
text = status.message,
|
text = context.stringResource(SYMR.strings.favorites_sync_initializing),
|
||||||
canDismiss = false,
|
|
||||||
)
|
)
|
||||||
if (status is FavoritesSyncStatus.Processing && status.title != null) {
|
}
|
||||||
|
|
||||||
|
is FavoritesSyncStatus.SyncError -> value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.stringResource(SYMR.strings.favorites_sync_error),
|
||||||
|
text = context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_error_string,
|
||||||
|
when (status) {
|
||||||
|
FavoritesSyncStatus.SyncError.NotLoggedInSyncError -> context.stringResource(SYMR.strings.please_login)
|
||||||
|
FavoritesSyncStatus.SyncError.FailedToFetchFavorites ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_featch)
|
||||||
|
is FavoritesSyncStatus.SyncError.UnknownSyncError ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unknown_error, status.message)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_failed_to_add_to_local_error, status.title, status.reason,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, status.title, status.url,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, status.title, status.gid)
|
||||||
|
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
positiveButtonText = context.stringResource(MR.strings.action_ok),
|
||||||
|
positiveButton = setStatusIdle,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.Processing -> {
|
||||||
|
val properties = SyncFavoritesProgressProperties(
|
||||||
|
title = context.stringResource(SYMR.strings.favorites_syncing),
|
||||||
|
text = when (status) {
|
||||||
|
FavoritesSyncStatus.Processing.VerifyingLibrary ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_verifying_library)
|
||||||
|
FavoritesSyncStatus.Processing.DownloadingFavorites ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_downloading)
|
||||||
|
FavoritesSyncStatus.Processing.CalculatingRemoteChanges ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes)
|
||||||
|
FavoritesSyncStatus.Processing.CalculatingLocalChanges ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes)
|
||||||
|
FavoritesSyncStatus.Processing.SyncingCategoryNames ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names)
|
||||||
|
is FavoritesSyncStatus.Processing.RemovingRemoteGalleries ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, status.galleryCount)
|
||||||
|
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
|
||||||
|
if (status.isThrottling) {
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_processing_throttle,
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
|
||||||
|
}
|
||||||
|
is FavoritesSyncStatus.Processing.RemovingGalleryFromLocal ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, status.index, status.total)
|
||||||
|
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
|
||||||
|
if (status.isThrottling) {
|
||||||
|
context.stringResource(
|
||||||
|
SYMR.strings.favorites_sync_processing_throttle,
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
|
||||||
|
}
|
||||||
|
|
||||||
|
FavoritesSyncStatus.Processing.CleaningUp ->
|
||||||
|
context.stringResource(SYMR.strings.favorites_sync_cleaning_up)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
value = properties
|
||||||
|
if (
|
||||||
|
status is FavoritesSyncStatus.Processing.AddingGalleryToRemote ||
|
||||||
|
status is FavoritesSyncStatus.Processing.AddingGalleryToLocal
|
||||||
|
) {
|
||||||
delay(5.seconds)
|
delay(5.seconds)
|
||||||
value = SyncFavoritesProgressProperties(
|
value = properties.copy(
|
||||||
title = context.stringResource(SYMR.strings.favorites_syncing),
|
text = when (status) {
|
||||||
text = status.delayedMessage ?: status.message,
|
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
|
||||||
canDismiss = false,
|
properties.text + "\n\n" + status.title
|
||||||
|
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
|
||||||
|
properties.text + "\n\n" + status.title
|
||||||
|
else -> properties.text
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,8 +206,8 @@ fun SyncFavoritesProgressDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
properties = DialogProperties(
|
properties = DialogProperties(
|
||||||
dismissOnClickOutside = dialog.canDismiss,
|
dismissOnClickOutside = false,
|
||||||
dismissOnBackPress = dialog.canDismiss,
|
dismissOnBackPress = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ sealed class Preference {
|
|||||||
|
|
||||||
data class CustomPreference(
|
data class CustomPreference(
|
||||||
override val title: String,
|
override val title: String,
|
||||||
val content: @Composable (PreferenceItem<String>) -> Unit,
|
val content: @Composable () -> Unit,
|
||||||
) : PreferenceItem<String>() {
|
) : PreferenceItem<Unit>() {
|
||||||
override val enabled: Boolean = true
|
override val enabled: Boolean = true
|
||||||
override val subtitle: String? = null
|
override val subtitle: String? = null
|
||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
|
override val onValueChanged: suspend (newValue: Unit) -> Boolean = { true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ internal fun PreferenceItem(
|
|||||||
InfoWidget(text = item.title)
|
InfoWidget(text = item.title)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.CustomPreference -> {
|
is Preference.PreferenceItem.CustomPreference -> {
|
||||||
item.content(item)
|
item.content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -59,6 +59,7 @@ import eu.kanade.tachiyomi.source.AndroidSourceManager
|
|||||||
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
@@ -83,6 +84,7 @@ import tachiyomi.core.common.i18n.pluralStringResource
|
|||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
@@ -369,6 +371,26 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.pref_category_reader),
|
title = stringResource(MR.strings.pref_category_reader),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = basePreferences.hardwareBitmapThreshold(),
|
||||||
|
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
|
||||||
|
subtitleProvider = { value, options ->
|
||||||
|
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
|
||||||
|
},
|
||||||
|
enabled = !ImageUtil.HARDWARE_BITMAP_UNSUPPORTED &&
|
||||||
|
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
|
||||||
|
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
|
||||||
|
.mapIndexed { index, option ->
|
||||||
|
val display = if (index == 0) {
|
||||||
|
stringResource(MR.strings.pref_hardware_bitmap_threshold_default, option)
|
||||||
|
} else {
|
||||||
|
option.toString()
|
||||||
|
}
|
||||||
|
option to display
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
.toImmutableMap(),
|
||||||
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.pref_display_profile),
|
title = stringResource(MR.strings.pref_display_profile),
|
||||||
subtitle = basePreferences.displayProfile().get(),
|
subtitle = basePreferences.displayProfile().get(),
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
|
subtitle = stringResource(SYMR.strings.pref_sync_now_subtitle),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!SyncDataJob.isRunning(context)) {
|
if (!SyncDataJob.isRunning(context)) {
|
||||||
SyncDataJob.startNow(context)
|
SyncDataJob.startNow(context, manual = true)
|
||||||
} else {
|
} else {
|
||||||
context.toast(SYMR.strings.sync_in_progress)
|
context.toast(SYMR.strings.sync_in_progress)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -139,7 +139,7 @@ object SettingsMangadexScreen : SearchableSettings {
|
|||||||
title = mdex.name + " Login",
|
title = mdex.name + " Login",
|
||||||
content = {
|
content = {
|
||||||
BasePreferenceWidget(
|
BasePreferenceWidget(
|
||||||
title = it.title,
|
title = mdex.name + " Login",
|
||||||
widget = {
|
widget = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.PeopleAlt,
|
imageVector = Icons.Outlined.PeopleAlt,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.WorkManager
|
||||||
import coil3.ImageLoader
|
import coil3.ImageLoader
|
||||||
import coil3.SingletonImageLoader
|
import coil3.SingletonImageLoader
|
||||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||||
@@ -56,6 +58,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
@@ -78,6 +81,7 @@ import org.conscrypt.Conscrypt
|
|||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.storage.service.StorageManager
|
import tachiyomi.domain.storage.service.StorageManager
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -173,6 +177,14 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
.onEach(FirebaseConfig::setCrashlyticsEnabled)
|
.onEach(FirebaseConfig::setCrashlyticsEnabled)
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
|
|
||||||
|
basePreferences.hardwareBitmapThreshold().let { preference ->
|
||||||
|
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
basePreferences.hardwareBitmapThreshold().changes()
|
||||||
|
.onEach { ImageUtil.hardwareBitmapThreshold = it }
|
||||||
|
.launchIn(scope)
|
||||||
|
|
||||||
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
||||||
|
|
||||||
// Updates widget update
|
// Updates widget update
|
||||||
@@ -182,6 +194,9 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
if (!WorkManager.isInitialized()) {
|
||||||
|
WorkManager.initialize(this, Configuration.Builder().build())
|
||||||
|
}
|
||||||
val syncPreferences: SyncPreferences = Injekt.get()
|
val syncPreferences: SyncPreferences = Injekt.get()
|
||||||
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
|
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
|
||||||
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
|
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
|
||||||
@@ -283,12 +298,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
||||||
}
|
}
|
||||||
|
|
||||||
val syncPreferences: SyncPreferences = Injekt.get()
|
|
||||||
val syncTriggerOpt = syncPreferences.getSyncTriggerOptions()
|
|
||||||
if (syncPreferences.isSyncEnabled() && syncTriggerOpt.syncOnAppStart) {
|
|
||||||
SyncDataJob.startNow(this@App)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXH
|
// EXH
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import coil3.request.bitmapConfig
|
|||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
|
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
|
||||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
|
||||||
import mihon.core.common.archive.archiveReader
|
import mihon.core.common.archive.archiveReader
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
import tachiyomi.core.common.util.system.ImageUtil
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
@@ -71,7 +70,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
|||||||
if (
|
if (
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||||
options.bitmapConfig == Bitmap.Config.HARDWARE &&
|
options.bitmapConfig == Bitmap.Config.HARDWARE &&
|
||||||
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
|
ImageUtil.canUseHardwareBitmap(bitmap)
|
||||||
) {
|
) {
|
||||||
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
|
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
|
||||||
if (hwBitmap != null) {
|
if (hwBitmap != null) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.library
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkRequest
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
@@ -135,10 +137,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (tags.contains(WORK_NAME_AUTO)) {
|
if (tags.contains(WORK_NAME_AUTO)) {
|
||||||
val preferences = Injekt.get<LibraryPreferences>()
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
val preferences = Injekt.get<LibraryPreferences>()
|
||||||
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
|
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
||||||
return Result.retry()
|
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
|
||||||
|
return Result.retry()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a running manual worker. If exists, try again later
|
// Find a running manual worker. If exists, try again later
|
||||||
@@ -768,15 +772,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
|
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
|
||||||
val constraints = Constraints(
|
val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
|
||||||
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
|
NetworkType.UNMETERED
|
||||||
NetworkType.UNMETERED
|
} else {
|
||||||
} else {
|
NetworkType.CONNECTED
|
||||||
NetworkType.CONNECTED
|
}
|
||||||
},
|
val networkRequestBuilder = NetworkRequest.Builder()
|
||||||
requiresCharging = DEVICE_CHARGING in restrictions,
|
if (DEVICE_ONLY_ON_WIFI in restrictions) {
|
||||||
requiresBatteryNotLow = true,
|
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||||
)
|
}
|
||||||
|
if (DEVICE_NETWORK_NOT_METERED in restrictions) {
|
||||||
|
networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||||
|
}
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
// 'networkRequest' only applies to Android 9+, otherwise 'networkType' is used
|
||||||
|
.setRequiredNetworkRequest(networkRequestBuilder.build(), networkType)
|
||||||
|
.setRequiresCharging(DEVICE_CHARGING in restrictions)
|
||||||
|
.setRequiresBatteryNotLow(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||||
interval.toLong(),
|
interval.toLong(),
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ object Notifications {
|
|||||||
const val ID_LIBRARY_SIZE_WARNING = -103
|
const val ID_LIBRARY_SIZE_WARNING = -103
|
||||||
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
|
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
|
||||||
const val ID_LIBRARY_ERROR = -102
|
const val ID_LIBRARY_ERROR = -102
|
||||||
|
const val CHANNEL_LIBRARY_EHENTAI = "library_ehentai_channel"
|
||||||
|
const val ID_EHENTAI_PROGRESS = -199
|
||||||
|
const val ID_EHENTAI_ERROR = -198
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification channel and ids used by the downloader.
|
* Notification channel and ids used by the downloader.
|
||||||
@@ -71,6 +74,7 @@ object Notifications {
|
|||||||
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
|
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
|
||||||
const val ID_APP_UPDATER = 1
|
const val ID_APP_UPDATER = 1
|
||||||
const val ID_APP_UPDATE_PROMPT = 2
|
const val ID_APP_UPDATE_PROMPT = 2
|
||||||
|
const val ID_APP_UPDATE_ERROR = 3
|
||||||
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
|
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
|
||||||
const val ID_UPDATES_TO_EXTS = -401
|
const val ID_UPDATES_TO_EXTS = -401
|
||||||
const val ID_EXTENSION_INSTALLER = -402
|
const val ID_EXTENSION_INSTALLER = -402
|
||||||
@@ -166,6 +170,13 @@ object Notifications {
|
|||||||
setGroup(GROUP_APK_UPDATES)
|
setGroup(GROUP_APK_UPDATES)
|
||||||
setName(context.stringResource(MR.strings.channel_ext_updates))
|
setName(context.stringResource(MR.strings.channel_ext_updates))
|
||||||
},
|
},
|
||||||
|
// SY -->
|
||||||
|
buildNotificationChannel(CHANNEL_LIBRARY_EHENTAI, IMPORTANCE_LOW) {
|
||||||
|
setName("EHentai")
|
||||||
|
setGroup(GROUP_LIBRARY)
|
||||||
|
setShowBadge(false)
|
||||||
|
},
|
||||||
|
// SY <--
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ sealed class Image(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Location {
|
sealed interface Location {
|
||||||
|
@ConsistentCopyVisibility
|
||||||
data class Pictures private constructor(val relativePath: String) : Location {
|
data class Pictures private constructor(val relativePath: String) : Location {
|
||||||
companion object {
|
companion object {
|
||||||
fun create(relativePath: String = ""): Pictures {
|
fun create(relativePath: String = ""): Pictures {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.work.WorkerParameters
|
|||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
|
import eu.kanade.tachiyomi.util.system.isOnline
|
||||||
import eu.kanade.tachiyomi.util.system.isRunning
|
import eu.kanade.tachiyomi.util.system.isRunning
|
||||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
@@ -31,6 +32,9 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
|
|||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (tags.contains(TAG_AUTO)) {
|
if (tags.contains(TAG_AUTO)) {
|
||||||
|
if (!context.isOnline()) {
|
||||||
|
return Result.retry()
|
||||||
|
}
|
||||||
// Find a running manual worker. If exists, try again later
|
// Find a running manual worker. If exists, try again later
|
||||||
if (context.workManager.isRunning(TAG_MANUAL)) {
|
if (context.workManager.isRunning(TAG_MANUAL)) {
|
||||||
return Result.retry()
|
return Result.retry()
|
||||||
@@ -93,17 +97,18 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startNow(context: Context) {
|
fun startNow(context: Context, manual: Boolean = false) {
|
||||||
val wm = context.workManager
|
val wm = context.workManager
|
||||||
if (wm.isRunning(TAG_JOB)) {
|
if (wm.isRunning(TAG_JOB)) {
|
||||||
// Already running either as a scheduled or manual job
|
// Already running either as a scheduled or manual job
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val tag = if (manual) TAG_MANUAL else TAG_AUTO
|
||||||
val request = OneTimeWorkRequestBuilder<SyncDataJob>()
|
val request = OneTimeWorkRequestBuilder<SyncDataJob>()
|
||||||
.addTag(TAG_JOB)
|
.addTag(TAG_JOB)
|
||||||
.addTag(TAG_MANUAL)
|
.addTag(tag)
|
||||||
.build()
|
.build()
|
||||||
context.workManager.enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request)
|
context.workManager.enqueueUniqueWork(tag, ExistingWorkPolicy.KEEP, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop(context: Context) {
|
fun stop(context: Context) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import eu.kanade.domain.track.interactor.AddTracks
|
|||||||
import eu.kanade.domain.track.model.toDomainTrack
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -120,6 +121,10 @@ abstract class BaseTracker(
|
|||||||
updateRemote(track)
|
updateRemote(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
throw NotImplementedError("Not implemented.")
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateRemote(track: Track): Unit = withIOContext {
|
private suspend fun updateRemote(track: Track): Unit = withIOContext {
|
||||||
try {
|
try {
|
||||||
update(track)
|
update(track)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.annotation.ColorInt
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -82,4 +83,6 @@ interface Tracker {
|
|||||||
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
|
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
|
||||||
|
|
||||||
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
|
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@@ -232,6 +233,10 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
|||||||
interceptor.setAuth(null)
|
interceptor.setAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveOAuth(alOAuth: ALOAuth?) {
|
fun saveOAuth(alOAuth: ALOAuth?) {
|
||||||
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
|
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ import androidx.core.net.toUri
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import eu.kanade.tachiyomi.util.lang.htmlDecode
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -288,6 +291,71 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query (${'$'}mangaId: Int!) {
|
||||||
|
|Media (id: ${'$'}mangaId) {
|
||||||
|
|id
|
||||||
|
|title {
|
||||||
|
|userPreferred
|
||||||
|
|}
|
||||||
|
|coverImage {
|
||||||
|
|large
|
||||||
|
|}
|
||||||
|
|description
|
||||||
|
|staff {
|
||||||
|
|edges {
|
||||||
|
|role
|
||||||
|
|node {
|
||||||
|
|name {
|
||||||
|
|userPreferred
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("mangaId", track.remoteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
API_URL,
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<ALMangaMetadata>()
|
||||||
|
.let {
|
||||||
|
val media = it.data.media
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = media.id,
|
||||||
|
title = media.title.userPreferred,
|
||||||
|
thumbnailUrl = media.coverImage.large,
|
||||||
|
description = media.description?.htmlDecode()?.ifEmpty { null },
|
||||||
|
authors = media.staff.edges
|
||||||
|
.filter { it.role == "Story" || it.role == "Story & Art" }
|
||||||
|
.map { it.node.name.userPreferred }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = media.staff.edges
|
||||||
|
.filter { it.role == "Art" || it.role == "Story & Art" }
|
||||||
|
.map { it.node.name.userPreferred }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createDate(dateValue: Long): JsonObject {
|
private fun createDate(dateValue: Long): JsonObject {
|
||||||
if (dateValue == 0L) {
|
if (dateValue == 0L) {
|
||||||
return buildJsonObject {
|
return buildJsonObject {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.anilist.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALMangaMetadata(
|
||||||
|
val data: ALMangaMetadataData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALMangaMetadataData(
|
||||||
|
@SerialName("Media")
|
||||||
|
val media: ALMangaMetadataMedia,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALMangaMetadataMedia(
|
||||||
|
val id: Long,
|
||||||
|
val title: ALItemTitle,
|
||||||
|
val coverImage: ItemCover,
|
||||||
|
val description: String?,
|
||||||
|
val staff: ALStaff,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaff(
|
||||||
|
val edges: List<ALStaffEdge>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaffEdge(
|
||||||
|
val role: String,
|
||||||
|
val node: ALStaffNode,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALStaffNode(
|
||||||
|
val name: ALItemTitle,
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -75,6 +76,10 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
|||||||
return api.search(query)
|
return api.search(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun refresh(track: Track): Track {
|
override suspend fun refresh(track: Track): Track {
|
||||||
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
|
val remoteStatusTrack = api.statusLibManga(track) ?: throw Exception("Could not find manga")
|
||||||
track.copyPersonalFrom(remoteStatusTrack)
|
track.copyPersonalFrom(remoteStatusTrack)
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMCollectionResponse
|
|||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchItem
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchResult
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSearchResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMSubject
|
||||||
|
import eu.kanade.tachiyomi.data.track.bangumi.dto.Infobox
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
@@ -21,6 +24,7 @@ import tachiyomi.core.common.util.lang.withIOContext
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class BangumiApi(
|
class BangumiApi(
|
||||||
private val trackId: Long,
|
private val trackId: Long,
|
||||||
@@ -71,6 +75,8 @@ class BangumiApi(
|
|||||||
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
|
val url = "$API_URL/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
|
||||||
.toUri()
|
.toUri()
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
|
.appendQueryParameter("type", "1")
|
||||||
|
.appendQueryParameter("responseGroup", "large")
|
||||||
.appendQueryParameter("max_results", "20")
|
.appendQueryParameter("max_results", "20")
|
||||||
.build()
|
.build()
|
||||||
with(json) {
|
with(json) {
|
||||||
@@ -81,7 +87,6 @@ class BangumiApi(
|
|||||||
if (result.code == 404) emptyList<TrackSearch>()
|
if (result.code == 404) emptyList<TrackSearch>()
|
||||||
|
|
||||||
result.list
|
result.list
|
||||||
?.filter { it.type == 1 }
|
|
||||||
?.map { it.toTrackSearch(trackId) }
|
?.map { it.toTrackSearch(trackId) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
}
|
}
|
||||||
@@ -126,6 +131,34 @@ class BangumiApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(GET("${API_URL}/v0/subjects/${track.remoteId}"))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<BGMSubject>()
|
||||||
|
.let {
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = it.id,
|
||||||
|
title = it.nameCn,
|
||||||
|
thumbnailUrl = it.images?.common,
|
||||||
|
description = it.summary,
|
||||||
|
authors = it.infobox
|
||||||
|
.filter { it.key == "作者" }
|
||||||
|
.filterIsInstance<Infobox.SingleValue>()
|
||||||
|
.map { it.value }
|
||||||
|
.joinToString(", "),
|
||||||
|
artists = it.infobox
|
||||||
|
.filter { it.key == "插图" }
|
||||||
|
.filterIsInstance<Infobox.SingleValue>()
|
||||||
|
.map { it.value }
|
||||||
|
.joinToString(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun accessToken(code: String): BGMOAuth {
|
suspend fun accessToken(code: String): BGMOAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ data class BGMSearchItem(
|
|||||||
val nameCn: String,
|
val nameCn: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val type: Int,
|
val type: Int,
|
||||||
|
val summary: String?,
|
||||||
val images: BGMSearchItemCovers?,
|
val images: BGMSearchItemCovers?,
|
||||||
@SerialName("eps_count")
|
@SerialName("eps_count")
|
||||||
val epsCount: Long?,
|
val epsCount: Long?,
|
||||||
@@ -25,9 +26,13 @@ data class BGMSearchItem(
|
|||||||
) {
|
) {
|
||||||
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
|
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
|
||||||
remote_id = this@BGMSearchItem.id
|
remote_id = this@BGMSearchItem.id
|
||||||
title = nameCn
|
title = nameCn.ifBlank { name }
|
||||||
cover_url = images?.common ?: ""
|
cover_url = images?.common.orEmpty()
|
||||||
summary = this@BGMSearchItem.name
|
summary = if (nameCn.isNotBlank()) {
|
||||||
|
"作品原名:$name" + this@BGMSearchItem.summary?.let { "\n$it" }.orEmpty()
|
||||||
|
} else {
|
||||||
|
this@BGMSearchItem.summary.orEmpty()
|
||||||
|
}
|
||||||
score = rating?.score ?: -1.0
|
score = rating?.score ?: -1.0
|
||||||
tracking_url = url
|
tracking_url = url
|
||||||
total_chapters = epsCount ?: 0
|
total_chapters = epsCount ?: 0
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.bangumi.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BGMSubject(
|
||||||
|
val images: BGMSearchItemCovers?,
|
||||||
|
val summary: String,
|
||||||
|
val name: String,
|
||||||
|
@SerialName("name_cn")
|
||||||
|
val nameCn: String,
|
||||||
|
val infobox: List<Infobox>,
|
||||||
|
val id: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
// infobox deserializer and related classes courtesy of
|
||||||
|
// https://github.com/Snd-R/komf/blob/4c260a3dcd326a5e1d74ac9662eec8124ab7e461/komf-core/src/commonMain/kotlin/snd/komf/providers/bangumi/model/BangumiSubject.kt#L53-L89
|
||||||
|
object InfoBoxSerializer : JsonContentPolymorphicSerializer<Infobox>(Infobox::class) {
|
||||||
|
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Infobox> {
|
||||||
|
if (element !is JsonObject) throw SerializationException("Expected JsonObject go ${element::class}")
|
||||||
|
val value = element["value"]
|
||||||
|
|
||||||
|
return when (value) {
|
||||||
|
is JsonArray -> Infobox.MultipleValues.serializer()
|
||||||
|
is JsonPrimitive -> Infobox.SingleValue.serializer()
|
||||||
|
else -> throw SerializationException("Unexpected element type ${element::class}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(with = InfoBoxSerializer::class)
|
||||||
|
sealed interface Infobox {
|
||||||
|
val key: String
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SingleValue(
|
||||||
|
override val key: String,
|
||||||
|
val value: String,
|
||||||
|
) : Infobox
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MultipleValues(
|
||||||
|
override val key: String,
|
||||||
|
val value: List<InfoboxNestedValue>,
|
||||||
|
) : Infobox
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class InfoboxNestedValue(
|
||||||
|
@SerialName("k")
|
||||||
|
val key: String? = null,
|
||||||
|
@SerialName("v")
|
||||||
|
val value: String,
|
||||||
|
)
|
||||||
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -139,6 +140,10 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
|||||||
interceptor.newAuth(null)
|
interceptor.newAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getUserId(): String {
|
private fun getUserId(): String {
|
||||||
return getPassword()
|
return getPassword()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAddMangaResult
|
|||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAlgoliaSearchResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuAlgoliaSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult
|
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.DELETE
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@@ -15,6 +17,7 @@ import eu.kanade.tachiyomi.network.POST
|
|||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import eu.kanade.tachiyomi.util.lang.htmlDecode
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
@@ -240,11 +243,80 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query(${'$'}libraryId: ID!, ${'$'}staffCount: Int) {
|
||||||
|
|findLibraryEntryById(id: ${'$'}libraryId) {
|
||||||
|
|media {
|
||||||
|
|id
|
||||||
|
|titles {
|
||||||
|
|preferred
|
||||||
|
|}
|
||||||
|
|posterImage {
|
||||||
|
|original {
|
||||||
|
|url
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|description
|
||||||
|
|staff(first: ${'$'}staffCount) {
|
||||||
|
|nodes {
|
||||||
|
|role
|
||||||
|
|person {
|
||||||
|
|name
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("libraryId", track.remoteId)
|
||||||
|
put("staffCount", 25) // 25 based on nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
GRAPHQL_URL,
|
||||||
|
headers = headersOf("Accept-Language", "en"),
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<KitsuMangaMetadata>()
|
||||||
|
.let {
|
||||||
|
val manga = it.data.findLibraryEntryById.media
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = manga.id.toLong(),
|
||||||
|
title = manga.titles.preferred,
|
||||||
|
thumbnailUrl = manga.posterImage.original.url,
|
||||||
|
description = manga.description.en?.htmlDecode()?.ifEmpty { null },
|
||||||
|
authors = manga.staff.nodes
|
||||||
|
.filter { it.role == "Story" || it.role == "Story & Art" }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = manga.staff.nodes
|
||||||
|
.filter { it.role == "Art" || it.role == "Story & Art" }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
|
private const val CLIENT_ID = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd"
|
||||||
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
|
private const val CLIENT_SECRET = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
|
||||||
|
|
||||||
private const val BASE_URL = "https://kitsu.app/api/edge/"
|
private const val BASE_URL = "https://kitsu.app/api/edge/"
|
||||||
|
private const val GRAPHQL_URL = "https://kitsu.app/api/graphql"
|
||||||
private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
|
private const val LOGIN_URL = "https://kitsu.app/api/oauth/token"
|
||||||
private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
|
private const val BASE_MANGA_URL = "https://kitsu.app/manga/"
|
||||||
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
|
private const val ALGOLIA_KEY_URL = "https://kitsu.app/api/edge/algolia-keys/media/"
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.kitsu.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadata(
|
||||||
|
val data: KitsuMangaMetadataData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadataData(
|
||||||
|
val findLibraryEntryById: KitsuMangaMetadataById,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadataById(
|
||||||
|
val media: KitsuMangaMetadataMedia,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaMetadataMedia(
|
||||||
|
val id: String,
|
||||||
|
val titles: KitsuMangaTitle,
|
||||||
|
val posterImage: KitsuMangaCover,
|
||||||
|
val description: KitsuMangaDescription,
|
||||||
|
val staff: KitsuMangaStaff,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaTitle(
|
||||||
|
val preferred: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaCover(
|
||||||
|
val original: KitsuMangaCoverUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaCoverUrl(
|
||||||
|
val url: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaDescription(
|
||||||
|
val en: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaStaff(
|
||||||
|
val nodes: List<KitsuMangaStaffNode>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaStaffNode(
|
||||||
|
val role: String,
|
||||||
|
val person: KitsuMangaStaffPerson,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitsuMangaStaffPerson(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
@@ -10,7 +10,9 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUListItem
|
|||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.util.lang.htmlDecode
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -117,6 +119,20 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
|||||||
interceptor.newAuth(authenticated.sessionToken)
|
interceptor.newAuth(authenticated.sessionToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
val series = api.getSeries(track)
|
||||||
|
return series?.let {
|
||||||
|
TrackMangaMetadata(
|
||||||
|
it.seriesId,
|
||||||
|
it.title?.htmlDecode(),
|
||||||
|
it.image?.url?.original,
|
||||||
|
it.description?.htmlDecode(),
|
||||||
|
it.authors?.filter { it.type == "Author" }?.joinToString(separator = ", ") { it.name ?: "" },
|
||||||
|
it.authors?.filter { it.type == "Artist" }?.joinToString(separator = ", ") { it.name ?: "" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun restoreSession(): String? {
|
fun restoreSession(): String? {
|
||||||
return trackPreferences.trackPassword(this).get().ifBlank { null }
|
return trackPreferences.trackPassword(this).get().ifBlank { null }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,14 @@ class MangaUpdatesApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getSeries(track: DomainTrack): MURecord {
|
||||||
|
return with(json) {
|
||||||
|
client.newCall(GET("$BASE_URL/v1/series/${track.remoteId}"))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<MURecord>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BASE_URL = "https://api.mangaupdates.com"
|
private const val BASE_URL = "https://api.mangaupdates.com"
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ data class MURecord(
|
|||||||
val ratingVotes: Int? = null,
|
val ratingVotes: Int? = null,
|
||||||
@SerialName("latest_chapter")
|
@SerialName("latest_chapter")
|
||||||
val latestChapter: Int? = null,
|
val latestChapter: Int? = null,
|
||||||
|
val authors: List<MUAuthor>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun MURecord.toTrackSearch(id: Long): TrackSearch {
|
fun MURecord.toTrackSearch(id: Long): TrackSearch {
|
||||||
@@ -36,3 +37,9 @@ fun MURecord.toTrackSearch(id: Long): TrackSearch {
|
|||||||
start_date = this@toTrackSearch.year.toString()
|
start_date = this@toTrackSearch.year.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MUAuthor(
|
||||||
|
val type: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package eu.kanade.tachiyomi.data.track.mdlist
|
|||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
@@ -168,6 +170,21 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
|
|||||||
trackPreferences.trackToken(this).delete()
|
trackPreferences.trackToken(this).delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return withIOContext {
|
||||||
|
val mdex = mdex ?: throw MangaDexNotFoundException()
|
||||||
|
val manga = mdex.getMangaMetadata(track.toDbTrack())
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = 0,
|
||||||
|
title = manga?.title,
|
||||||
|
thumbnailUrl = manga?.thumbnail_url, // Doesn't load the actual cover because of Refer header
|
||||||
|
description = manga?.description,
|
||||||
|
authors = manga?.author,
|
||||||
|
artists = manga?.artist,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val isLoggedIn: Boolean
|
override val isLoggedIn: Boolean
|
||||||
get() = trackPreferences.trackToken(this).get().isNotEmpty()
|
get() = trackPreferences.trackToken(this).get().isNotEmpty()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.model
|
||||||
|
|
||||||
|
data class TrackMangaMetadata(
|
||||||
|
val remoteId: Long? = null,
|
||||||
|
val title: String? = null,
|
||||||
|
val thumbnailUrl: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val authors: String? = null,
|
||||||
|
val artists: String? = null,
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -156,6 +157,10 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
|||||||
interceptor.setAuth(null)
|
interceptor.setAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
fun getIfAuthExpired(): Boolean {
|
fun getIfAuthExpired(): Boolean {
|
||||||
return trackPreferences.trackAuthExpired(this).get()
|
return trackPreferences.trackAuthExpired(this).get()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.data.track.myanimelist
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItem
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALManga
|
||||||
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
|
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
|
||||||
@@ -193,6 +195,41 @@ class MyAnimeListApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return withIOContext {
|
||||||
|
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||||
|
.appendPath(track.remoteId.toString())
|
||||||
|
.appendQueryParameter(
|
||||||
|
"fields",
|
||||||
|
"id,title,synopsis,main_picture,authors{first_name,last_name}",
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(GET(url.toString()))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<MALMangaMetadata>()
|
||||||
|
.let {
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = it.id,
|
||||||
|
title = it.title,
|
||||||
|
thumbnailUrl = it.covers.large.ifEmpty { null } ?: it.covers.medium,
|
||||||
|
description = it.synopsis,
|
||||||
|
authors = it.authors
|
||||||
|
.filter { it.role == "Story" || it.role == "Story & Art" }
|
||||||
|
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
|
||||||
|
.joinToString(separator = ", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = it.authors
|
||||||
|
.filter { it.role == "Art" || it.role == "Story & Art" }
|
||||||
|
.map { "${it.node.firstName} ${it.node.lastName}".trim() }
|
||||||
|
.joinToString(separator = ", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getListPage(offset: Int): MALUserSearchResult {
|
private suspend fun getListPage(offset: Int): MALUserSearchResult {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
||||||
|
|||||||
@@ -23,4 +23,29 @@ data class MALManga(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class MALMangaCovers(
|
data class MALMangaCovers(
|
||||||
val large: String = "",
|
val large: String = "",
|
||||||
|
val medium: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MALMangaMetadata(
|
||||||
|
val id: Long,
|
||||||
|
val title: String,
|
||||||
|
val synopsis: String?,
|
||||||
|
@SerialName("main_picture")
|
||||||
|
val covers: MALMangaCovers,
|
||||||
|
val authors: List<MALAuthor>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MALAuthor(
|
||||||
|
val node: MALAuthorNode,
|
||||||
|
val role: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MALAuthorNode(
|
||||||
|
@SerialName("first_name")
|
||||||
|
val firstName: String,
|
||||||
|
@SerialName("last_name")
|
||||||
|
val lastName: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -98,6 +99,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
|||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata? {
|
||||||
|
return api.getMangaMetadata(track)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLogo() = R.drawable.ic_tracker_shikimori
|
override fun getLogo() = R.drawable.ic_tracker_shikimori
|
||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package eu.kanade.tachiyomi.data.track.shikimori
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMManga
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMManga
|
||||||
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry
|
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListEntry
|
||||||
@@ -132,6 +134,65 @@ class ShikimoriApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|query(${'$'}ids: String!) {
|
||||||
|
|mangas(ids: ${'$'}ids) {
|
||||||
|
|id
|
||||||
|
|name
|
||||||
|
|description
|
||||||
|
|poster {
|
||||||
|
|originalUrl
|
||||||
|
|}
|
||||||
|
|personRoles {
|
||||||
|
|person {
|
||||||
|
|name
|
||||||
|
|}
|
||||||
|
|rolesEn
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("ids", "${track.remoteId}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
"https://shikimori.one/api/graphql",
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<SMMetadata>()
|
||||||
|
.let {
|
||||||
|
if (it.data.mangas.isEmpty()) throw Exception("Could not get metadata from Shikimori")
|
||||||
|
val manga = it.data.mangas[0]
|
||||||
|
TrackMangaMetadata(
|
||||||
|
remoteId = manga.id.toLong(),
|
||||||
|
title = manga.name,
|
||||||
|
thumbnailUrl = manga.poster.originalUrl,
|
||||||
|
description = manga.description,
|
||||||
|
authors = manga.personRoles
|
||||||
|
.filter { it.rolesEn.contains("Story") || it.rolesEn.contains("Story & Art") }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
artists = manga.personRoles
|
||||||
|
.filter { it.rolesEn.contains("Art") || it.rolesEn.contains("Story & Art") }
|
||||||
|
.map { it.person.name }
|
||||||
|
.joinToString(", ")
|
||||||
|
.ifEmpty { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun accessToken(code: String): SMOAuth {
|
suspend fun accessToken(code: String): SMOAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.shikimori.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMetadata(
|
||||||
|
val data: SMMetadataData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMetadataData(
|
||||||
|
val mangas: List<SMMetadataResult>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMetadataResult(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val poster: SMMangaPoster,
|
||||||
|
val personRoles: List<SMMangaPersonRoles>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMangaPoster(
|
||||||
|
val originalUrl: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMMangaPersonRoles(
|
||||||
|
val person: SMPerson,
|
||||||
|
val rolesEn: List<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SMPerson(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
@@ -181,9 +181,9 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_close_24dp,
|
R.drawable.ic_close_24dp,
|
||||||
context.stringResource(MR.strings.action_cancel),
|
context.stringResource(MR.strings.action_cancel),
|
||||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER),
|
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_ERROR),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
notificationBuilder.show(Notifications.ID_APP_UPDATER)
|
notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import kotlinx.coroutines.cancel
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
|
import rikka.shizuku.ShizukuRemoteProcess
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
class ShizukuInstaller(private val service: Service) : Installer(service) {
|
class ShizukuInstaller(private val service: Service) : Installer(service) {
|
||||||
|
|
||||||
@@ -93,9 +95,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val newProcess: Method
|
||||||
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
||||||
@Suppress("DEPRECATION")
|
val process = newProcess.invoke(null, arrayOf("sh", "-c", command), null, null) as ShizukuRemoteProcess
|
||||||
val process = Shizuku.newProcess(arrayOf("sh", "-c", command), null, null)
|
|
||||||
if (stdin != null) {
|
if (stdin != null) {
|
||||||
process.outputStream.use { stdin.copyTo(it) }
|
process.outputStream.use { stdin.copyTo(it) }
|
||||||
}
|
}
|
||||||
@@ -122,6 +124,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
|
|||||||
service.stopSelf()
|
service.stopSelf()
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
newProcess = Shizuku::class.java
|
||||||
|
.getDeclaredMethod("newProcess", Array<out String>::class.java, Array<out String>::class.java, String::class.java)
|
||||||
|
newProcess.isAccessible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
return similarHandler.getRelated(manga)
|
return similarHandler.getRelated(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(track: Track): SManga? {
|
||||||
|
return mangaHandler.getMangaMetadata(track, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc())
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val dataSaverPref = "dataSaverV5"
|
private const val dataSaverPref = "dataSaverV5"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse
|
|||||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -27,14 +28,16 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
|||||||
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
data class BrowseTab(
|
data object BrowseTab : Tab {
|
||||||
private val toExtensions: Boolean = false,
|
|
||||||
) : Tab {
|
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
@@ -52,6 +55,12 @@ data class BrowseTab(
|
|||||||
navigator.push(GlobalSearchScreen())
|
navigator.push(GlobalSearchScreen())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val switchToExtensionTabChannel = Channel<Unit>(1, BufferOverflow.DROP_OLDEST)
|
||||||
|
|
||||||
|
fun showExtension() {
|
||||||
|
switchToExtensionTabChannel.trySend(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -65,35 +74,43 @@ data class BrowseTab(
|
|||||||
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
|
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
|
||||||
val extensionsState by extensionsScreenModel.state.collectAsState()
|
val extensionsState by extensionsScreenModel.state.collectAsState()
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
val tabs = if (hideFeedTab) {
|
||||||
|
persistentListOf(
|
||||||
|
sourcesTab(),
|
||||||
|
extensionsTab(extensionsScreenModel),
|
||||||
|
migrateSourceTab(),
|
||||||
|
)
|
||||||
|
} else if (feedTabInFront) {
|
||||||
|
persistentListOf(
|
||||||
|
feedTab(),
|
||||||
|
sourcesTab(),
|
||||||
|
extensionsTab(extensionsScreenModel),
|
||||||
|
migrateSourceTab(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
persistentListOf(
|
||||||
|
sourcesTab(),
|
||||||
|
feedTab(),
|
||||||
|
extensionsTab(extensionsScreenModel),
|
||||||
|
migrateSourceTab(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
val state = rememberPagerState { tabs.size }
|
||||||
|
|
||||||
TabbedScreen(
|
TabbedScreen(
|
||||||
titleRes = MR.strings.browse,
|
titleRes = MR.strings.browse,
|
||||||
// SY -->
|
tabs = tabs,
|
||||||
tabs = if (hideFeedTab) {
|
state = state,
|
||||||
persistentListOf(
|
|
||||||
sourcesTab(),
|
|
||||||
extensionsTab(extensionsScreenModel),
|
|
||||||
migrateSourceTab(),
|
|
||||||
)
|
|
||||||
} else if (feedTabInFront) {
|
|
||||||
persistentListOf(
|
|
||||||
feedTab(),
|
|
||||||
sourcesTab(),
|
|
||||||
extensionsTab(extensionsScreenModel),
|
|
||||||
migrateSourceTab(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
persistentListOf(
|
|
||||||
sourcesTab(),
|
|
||||||
feedTab(),
|
|
||||||
extensionsTab(extensionsScreenModel),
|
|
||||||
migrateSourceTab(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
startIndex = 2.takeIf { toExtensions },
|
|
||||||
// SY <--
|
|
||||||
searchQuery = extensionsState.searchQuery,
|
searchQuery = extensionsState.searchQuery,
|
||||||
onChangeSearchQuery = extensionsScreenModel::search,
|
onChangeSearchQuery = extensionsScreenModel::search,
|
||||||
)
|
)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
switchToExtensionTabChannel.receiveAsFlow()
|
||||||
|
.collectLatest { state.scrollToPage(/* SY --> */2/* SY <-- */) }
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
||||||
|
|||||||
+1
-4
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
@@ -29,8 +27,7 @@ import tachiyomi.i18n.sy.SYMR
|
|||||||
|
|
||||||
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
|
class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen() {
|
||||||
|
|
||||||
@delegate:Transient
|
var newSelectedItem: Pair<Long, Long>? = null
|
||||||
var newSelectedItem by mutableStateOf<Pair<Long, Long>?>(null)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
|||||||
+2
-3
@@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
|||||||
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
||||||
import exh.ui.ifSourcesLoaded
|
import exh.ui.ifSourcesLoaded
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@@ -71,7 +71,6 @@ data class SourceSearchScreen(
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
val openMigrateDialog: (Manga) -> Unit = {
|
val openMigrateDialog: (Manga) -> Unit = {
|
||||||
// SY -->
|
// SY -->
|
||||||
navigator.items
|
navigator.items
|
||||||
@@ -83,7 +82,7 @@ data class SourceSearchScreen(
|
|||||||
}
|
}
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.InputChip
|
import androidx.compose.material3.InputChip
|
||||||
import androidx.compose.material3.InputChipDefaults
|
import androidx.compose.material3.InputChipDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MenuAnchorType
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -154,7 +155,7 @@ fun AutoCompleteTextField(
|
|||||||
null
|
null
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.menuAnchor()
|
.menuAnchor(MenuAnchorType.PrimaryEditable)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.runOnEnterKeyPressed { submit() },
|
.runOnEnterKeyPressed { submit() },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -61,6 +60,7 @@ import exh.ui.ifSourcesLoaded
|
|||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
@@ -240,11 +240,9 @@ data class BrowseSourceScreen(
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
|
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object HistoryTab : Tab {
|
data object HistoryTab : Tab {
|
||||||
|
|
||||||
private val snackbarHostState = SnackbarHostState()
|
private val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.fastFilter
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
@@ -73,11 +73,11 @@ object HomeScreen : Screen() {
|
|||||||
private const val TAB_FADE_DURATION = 200
|
private const val TAB_FADE_DURATION = 200
|
||||||
private const val TAB_NAVIGATOR_KEY = "HomeTabs"
|
private const val TAB_NAVIGATOR_KEY = "HomeTabs"
|
||||||
|
|
||||||
private val tabs = listOf(
|
private val TABS = listOf(
|
||||||
LibraryTab,
|
LibraryTab,
|
||||||
UpdatesTab,
|
UpdatesTab,
|
||||||
HistoryTab,
|
HistoryTab,
|
||||||
BrowseTab(),
|
BrowseTab,
|
||||||
MoreTab,
|
MoreTab,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ object HomeScreen : Screen() {
|
|||||||
startBar = {
|
startBar = {
|
||||||
if (isTabletUi()) {
|
if (isTabletUi()) {
|
||||||
NavigationRail {
|
NavigationRail {
|
||||||
tabs
|
TABS
|
||||||
// SY -->
|
// SY -->
|
||||||
.fastFilter { it.isEnabled() }
|
.fastFilter { it.isEnabled() }
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -123,7 +123,7 @@ object HomeScreen : Screen() {
|
|||||||
exit = shrinkVertically(),
|
exit = shrinkVertically(),
|
||||||
) {
|
) {
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
tabs
|
TABS
|
||||||
// SY -->
|
// SY -->
|
||||||
.fastFilter { it.isEnabled() }
|
.fastFilter { it.isEnabled() }
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -179,7 +179,12 @@ object HomeScreen : Screen() {
|
|||||||
is Tab.Library -> LibraryTab
|
is Tab.Library -> LibraryTab
|
||||||
Tab.Updates -> UpdatesTab
|
Tab.Updates -> UpdatesTab
|
||||||
Tab.History -> HistoryTab
|
Tab.History -> HistoryTab
|
||||||
is Tab.Browse -> BrowseTab(it.toExtensions)
|
is Tab.Browse -> {
|
||||||
|
if (it.toExtensions) {
|
||||||
|
BrowseTab.showExtension()
|
||||||
|
}
|
||||||
|
BrowseTab
|
||||||
|
}
|
||||||
is Tab.More -> MoreTab
|
is Tab.More -> MoreTab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
|
import androidx.compose.ui.util.fastDistinctBy
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
|
import androidx.compose.ui.util.fastMapNotNull
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.preference.PreferenceMutableState
|
import eu.kanade.core.preference.PreferenceMutableState
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.fastDistinctBy
|
|
||||||
import eu.kanade.core.util.fastFilter
|
|
||||||
import eu.kanade.core.util.fastFilterNot
|
import eu.kanade.core.util.fastFilterNot
|
||||||
import eu.kanade.core.util.fastMapNotNull
|
|
||||||
import eu.kanade.core.util.fastPartition
|
import eu.kanade.core.util.fastPartition
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import tachiyomi.source.local.isLocal
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object LibraryTab : Tab {
|
data object LibraryTab : Tab {
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
@@ -165,7 +165,7 @@ object LibraryTab : Tab {
|
|||||||
},
|
},
|
||||||
onClickSyncNow = {
|
onClickSyncNow = {
|
||||||
if (!SyncDataJob.isRunning(context)) {
|
if (!SyncDataJob.isRunning(context)) {
|
||||||
SyncDataJob.startNow(context)
|
SyncDataJob.startNow(context, manual = true)
|
||||||
} else {
|
} else {
|
||||||
context.toast(SYMR.strings.sync_in_progress)
|
context.toast(SYMR.strings.sync_in_progress)
|
||||||
}
|
}
|
||||||
@@ -334,8 +334,8 @@ object LibraryTab : Tab {
|
|||||||
// SY -->
|
// SY -->
|
||||||
SyncFavoritesProgressDialog(
|
SyncFavoritesProgressDialog(
|
||||||
status = screenModel.favoritesSync.status.collectAsState().value,
|
status = screenModel.favoritesSync.status.collectAsState().value,
|
||||||
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
|
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle },
|
||||||
openManga = { navigator.push(MangaScreen(it.id)) },
|
openManga = { navigator.push(MangaScreen(it)) },
|
||||||
)
|
)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,26 @@ package eu.kanade.tachiyomi.ui.manga
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
@@ -26,22 +32,34 @@ import coil3.transform.RoundedCornersTransformation
|
|||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
|
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
|
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
|
||||||
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
||||||
import exh.util.dropBlank
|
import exh.util.dropBlank
|
||||||
import exh.util.trimOrNull
|
import exh.util.trimOrNull
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EditMangaDialog(
|
fun EditMangaDialog(
|
||||||
@@ -61,6 +79,10 @@ fun EditMangaDialog(
|
|||||||
var binding by remember {
|
var binding by remember {
|
||||||
mutableStateOf<EditMangaDialogBinding?>(null)
|
mutableStateOf<EditMangaDialogBinding?>(null)
|
||||||
}
|
}
|
||||||
|
val showTrackerSelectionDialogue = remember { mutableStateOf(false) }
|
||||||
|
val getTracks = remember { Injekt.get<GetTracks>() }
|
||||||
|
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
||||||
|
val tracks = remember { mutableStateOf(emptyList<Pair<Track, Tracker>>()) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@@ -109,7 +131,7 @@ fun EditMangaDialog(
|
|||||||
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
|
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
|
||||||
.also { binding = it }
|
.also { binding = it }
|
||||||
.apply {
|
.apply {
|
||||||
onViewCreated(manga, factoryContext, this, scope)
|
onViewCreated(manga, factoryContext, this, scope, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
|
||||||
}
|
}
|
||||||
.root
|
.root
|
||||||
},
|
},
|
||||||
@@ -118,9 +140,61 @@ fun EditMangaDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showTrackerSelectionDialogue.value) {
|
||||||
|
TrackerSelectDialog(
|
||||||
|
tracks = tracks.value,
|
||||||
|
onDismissRequest = { showTrackerSelectionDialogue.value = false },
|
||||||
|
onTrackerSelect = { tracker, track ->
|
||||||
|
scope.launch {
|
||||||
|
autofillFromTracker(binding!!, track, tracker)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
@Composable
|
||||||
|
private fun TrackerSelectDialog(
|
||||||
|
tracks: List<Pair<Track, Tracker>>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onTrackerSelect: (
|
||||||
|
tracker: Tracker,
|
||||||
|
track: Track,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(stringResource(SYMR.strings.select_tracker))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
tracks.forEach { (track, tracker) ->
|
||||||
|
TrackLogoIcon(
|
||||||
|
tracker,
|
||||||
|
onClick = {
|
||||||
|
onTrackerSelect(tracker, track)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
|
||||||
loadCover(manga, binding)
|
loadCover(manga, binding)
|
||||||
|
|
||||||
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
|
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
|
||||||
@@ -203,6 +277,55 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
|||||||
|
|
||||||
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
|
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
|
||||||
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
|
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
|
||||||
|
binding.autofillFromTracker.setOnClickListener {
|
||||||
|
scope.launch {
|
||||||
|
getTrackers(manga, binding, context, getTracks, trackerManager, tracks, showTrackerSelectionDialogue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getTrackers(manga: Manga, binding: EditMangaDialogBinding, context: Context, getTracks: GetTracks, trackerManager: TrackerManager, tracks: MutableState<List<Pair<Track, Tracker>>>, showTrackerSelectionDialogue: MutableState<Boolean>) {
|
||||||
|
tracks.value = getTracks.await(manga.id).map { track ->
|
||||||
|
track to trackerManager.get(track.trackerId)!!
|
||||||
|
}
|
||||||
|
.filterNot { (_, tracker) -> tracker is EnhancedTracker }
|
||||||
|
|
||||||
|
if (tracks.value.isEmpty()) {
|
||||||
|
context.toast(context.stringResource(SYMR.strings.entry_not_tracked))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracks.value.size > 1) {
|
||||||
|
showTrackerSelectionDialogue.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
autofillFromTracker(binding, tracks.value.first().first, tracks.value.first().second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTextIfNotBlank(field: (String) -> Unit, value: String?) {
|
||||||
|
value?.takeIf { it.isNotBlank() }?.let { field(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun autofillFromTracker(binding: EditMangaDialogBinding, track: Track, tracker: Tracker) {
|
||||||
|
try {
|
||||||
|
val trackerMangaMetadata = tracker.getMangaMetadata(track)
|
||||||
|
|
||||||
|
setTextIfNotBlank(binding.title::setText, trackerMangaMetadata?.title)
|
||||||
|
setTextIfNotBlank(binding.mangaAuthor::setText, trackerMangaMetadata?.authors)
|
||||||
|
setTextIfNotBlank(binding.mangaArtist::setText, trackerMangaMetadata?.artists)
|
||||||
|
setTextIfNotBlank(binding.thumbnailUrl::setText, trackerMangaMetadata?.thumbnailUrl)
|
||||||
|
setTextIfNotBlank(binding.mangaDescription::setText, trackerMangaMetadata?.description)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
tracker.logcat(LogPriority.ERROR, e)
|
||||||
|
binding.root.context.toast(
|
||||||
|
binding.root.context.stringResource(
|
||||||
|
MR.strings.track_error,
|
||||||
|
tracker.name,
|
||||||
|
e.message ?: "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
||||||
|
|||||||
@@ -1263,16 +1263,14 @@ class MangaScreenModel(
|
|||||||
*/
|
*/
|
||||||
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
|
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
|
||||||
toggleAllSelection(false)
|
toggleAllSelection(false)
|
||||||
|
if (chapters.isEmpty()) return
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
setReadStatus.await(
|
setReadStatus.await(
|
||||||
read = read,
|
read = read,
|
||||||
chapters = chapters.toTypedArray(),
|
chapters = chapters.toTypedArray(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (!read || successState?.hasLoggedInTrackers == false || autoTrackState == AutoTrackState.NEVER) {
|
||||||
successState?.hasLoggedInTrackers == false ||
|
|
||||||
!read || autoTrackState == AutoTrackState.NEVER
|
|
||||||
) {
|
|
||||||
return@launchIO
|
return@launchIO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object MoreTab : Tab {
|
data object MoreTab : Tab {
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ import com.github.chrisbanes.photoview.PhotoView
|
|||||||
import eu.kanade.tachiyomi.data.coil.cropBorders
|
import eu.kanade.tachiyomi.data.coil.cropBorders
|
||||||
import eu.kanade.tachiyomi.data.coil.customDecoder
|
import eu.kanade.tachiyomi.data.coil.customDecoder
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
||||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
|
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
|
import tachiyomi.core.common.util.system.ImageUtil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper view for showing page image.
|
* A wrapper view for showing page image.
|
||||||
@@ -116,21 +116,22 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
|
private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) {
|
||||||
|
val config = config
|
||||||
if (config != null &&
|
if (config != null &&
|
||||||
config!!.landscapeZoom &&
|
config.landscapeZoom &&
|
||||||
config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
|
config.minimumScaleType == SCALE_TYPE_CENTER_INSIDE &&
|
||||||
sWidth > sHeight &&
|
sWidth > sHeight &&
|
||||||
scale == minScale
|
scale == minScale
|
||||||
) {
|
) {
|
||||||
handler?.postDelayed(500) {
|
handler?.postDelayed(500) {
|
||||||
val point = when (config!!.zoomStartPosition) {
|
val point = when (config.zoomStartPosition) {
|
||||||
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
|
ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F)
|
||||||
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
|
ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F)
|
||||||
ZoomStartPosition.CENTER -> center
|
ZoomStartPosition.CENTER -> center
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetScale = height.toFloat() / sHeight.toFloat()
|
val targetScale = height.toFloat() / sHeight.toFloat()
|
||||||
animateScaleAndCenter(targetScale, point)!!
|
(animateScaleAndCenter(targetScale, point) ?: return@postDelayed)
|
||||||
.withDuration(500)
|
.withDuration(500)
|
||||||
.withEasing(EASE_IN_OUT_QUAD)
|
.withEasing(EASE_IN_OUT_QUAD)
|
||||||
.withInterruptible(true)
|
.withInterruptible(true)
|
||||||
@@ -232,7 +233,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
} else {
|
} else {
|
||||||
SubsamplingScaleImageView(context)
|
SubsamplingScaleImageView(context)
|
||||||
}.apply {
|
}.apply {
|
||||||
setMaxTileSize(GLUtil.maxTextureSize)
|
setMaxTileSize(ImageUtil.hardwareBitmapThreshold)
|
||||||
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
|
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
|
||||||
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
||||||
setMinimumTileDpi(180)
|
setMinimumTileDpi(180)
|
||||||
@@ -287,35 +288,44 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isWebtoon) {
|
when (data) {
|
||||||
val request = ImageRequest.Builder(context)
|
is BitmapDrawable -> {
|
||||||
.data(data)
|
setImage(ImageSource.bitmap(data.bitmap))
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
isVisible = true
|
||||||
.diskCachePolicy(CachePolicy.DISABLED)
|
}
|
||||||
.target(
|
is BufferedSource -> {
|
||||||
onSuccess = { result ->
|
if (!isWebtoon) {
|
||||||
val image = result as BitmapImage
|
setHardwareConfig(ImageUtil.canUseHardwareBitmap(data))
|
||||||
setImage(ImageSource.bitmap(image.bitmap))
|
setImage(ImageSource.inputStream(data.inputStream()))
|
||||||
isVisible = true
|
isVisible = true
|
||||||
},
|
return@apply
|
||||||
onError = {
|
}
|
||||||
this@ReaderPageImageView.onImageLoadError()
|
|
||||||
},
|
ImageRequest.Builder(context)
|
||||||
)
|
.data(data)
|
||||||
.size(ViewSizeResolver(this@ReaderPageImageView))
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.precision(Precision.INEXACT)
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.cropBorders(config.cropBorders)
|
.target(
|
||||||
.customDecoder(true)
|
onSuccess = { result ->
|
||||||
.crossfade(false)
|
val image = result as BitmapImage
|
||||||
.build()
|
setImage(ImageSource.bitmap(image.bitmap))
|
||||||
context.imageLoader.enqueue(request)
|
isVisible = true
|
||||||
} else {
|
},
|
||||||
when (data) {
|
onError = {
|
||||||
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
|
onImageLoadError()
|
||||||
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream()))
|
},
|
||||||
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
|
)
|
||||||
|
.size(ViewSizeResolver(this@ReaderPageImageView))
|
||||||
|
.precision(Precision.INEXACT)
|
||||||
|
.cropBorders(config.cropBorders)
|
||||||
|
.customDecoder(true)
|
||||||
|
.crossfade(false)
|
||||||
|
.build()
|
||||||
|
.let(context.imageLoader::enqueue)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
|
||||||
}
|
}
|
||||||
isVisible = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,8 +353,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
|||||||
else -> oldCurrent?.first ?: return
|
else -> oldCurrent?.first ?: return
|
||||||
}
|
}
|
||||||
|
|
||||||
val index = when (newPage) {
|
val index = when {
|
||||||
is ChapterTransition -> {
|
newPage is ChapterTransition && joinedItems.none { it.first == newPage || it.second == newPage } -> {
|
||||||
val filteredPages = joinedItems.filter {
|
val filteredPages = joinedItems.filter {
|
||||||
it.first is ReaderPage &&
|
it.first is ReaderPage &&
|
||||||
(it.first as ReaderPage).chapter == newPage.to
|
(it.first as ReaderPage).chapter == newPage.to
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.ui.stats
|
package eu.kanade.tachiyomi.ui.stats
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastDistinctBy
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
|
import androidx.compose.ui.util.fastMapNotNull
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.util.fastCountNot
|
import eu.kanade.core.util.fastCountNot
|
||||||
import eu.kanade.core.util.fastDistinctBy
|
|
||||||
import eu.kanade.core.util.fastFilter
|
|
||||||
import eu.kanade.core.util.fastFilterNot
|
import eu.kanade.core.util.fastFilterNot
|
||||||
import eu.kanade.core.util.fastMapNotNull
|
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||||
import eu.kanade.presentation.more.stats.data.StatsData
|
import eu.kanade.presentation.more.stats.data.StatsData
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object UpdatesTab : Tab {
|
data object UpdatesTab : Tab {
|
||||||
|
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.core.content.getSystemService
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
||||||
@@ -107,9 +108,13 @@ fun Context.createFileInCacheDir(name: String): File {
|
|||||||
fun Context.createReaderThemeContext(): Context {
|
fun Context.createReaderThemeContext(): Context {
|
||||||
val preferences = Injekt.get<UiPreferences>()
|
val preferences = Injekt.get<UiPreferences>()
|
||||||
val readerPreferences = Injekt.get<ReaderPreferences>()
|
val readerPreferences = Injekt.get<ReaderPreferences>()
|
||||||
|
val themeMode = preferences.themeMode().get()
|
||||||
val isDarkBackground = when (readerPreferences.readerTheme().get()) {
|
val isDarkBackground = when (readerPreferences.readerTheme().get()) {
|
||||||
1, 2 -> true // Black, Gray
|
1, 2 -> true // Black, Gray
|
||||||
3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default
|
3 -> when (themeMode) { // Automatic bg uses activity background by default
|
||||||
|
ThemeMode.SYSTEM -> applicationContext.isNightMode()
|
||||||
|
else -> themeMode == ThemeMode.DARK
|
||||||
|
}
|
||||||
else -> false // White
|
else -> false // White
|
||||||
}
|
}
|
||||||
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
val expected = if (isDarkBackground) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
||||||
|
|||||||
@@ -119,4 +119,10 @@ data class DummyTracker(
|
|||||||
track: eu.kanade.tachiyomi.data.database.models.Track,
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
epochMillis: Long,
|
epochMillis: Long,
|
||||||
) = Unit
|
) = Unit
|
||||||
|
|
||||||
|
override suspend fun getMangaMetadata(
|
||||||
|
track: tachiyomi.domain.track.model.Track,
|
||||||
|
): eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata = eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata(
|
||||||
|
0, "test", "test", "test", "test", "test",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
suspend fun findAcceptedRootAndDiscardOthers(
|
suspend fun findAcceptedRootAndDiscardOthers(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
): Triple<ChapterChain, List<ChapterChain>, Boolean> {
|
): Triple<ChapterChain, List<ChapterChain>, List<Chapter>> {
|
||||||
// Find other chains
|
// Find other chains
|
||||||
val chains = chapters
|
val chains = chapters
|
||||||
.flatMap { chapter ->
|
.flatMap { chapter ->
|
||||||
@@ -149,7 +149,7 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
setMangaCategories.await(it.manga.id, newCategories)
|
setMangaCategories.await(it.manga.id, newCategories)
|
||||||
}
|
}
|
||||||
|
|
||||||
Triple(newAccepted, toDiscard, new)
|
Triple(newAccepted, toDiscard, newChapters)
|
||||||
} else {
|
} else {
|
||||||
/*val notNeeded = chains.filter { it.manga.id != accepted.manga.id }
|
/*val notNeeded = chains.filter { it.manga.id != accepted.manga.id }
|
||||||
val (newChapters, new) = getChapterList(accepted, notNeeded, chainsAsChapters)
|
val (newChapters, new) = getChapterList(accepted, notNeeded, chainsAsChapters)
|
||||||
@@ -158,7 +158,7 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
// Insert new chapters for accepted manga
|
// Insert new chapters for accepted manga
|
||||||
db.insertChapters(newAccepted.chapters).await()*/
|
db.insertChapters(newAccepted.chapters).await()*/
|
||||||
|
|
||||||
Triple(accepted, emptyList(), false)
|
Triple(accepted, emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package exh.eh
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
|
import eu.kanade.tachiyomi.util.system.notify
|
||||||
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.text.NumberFormat
|
||||||
|
|
||||||
|
class EHentaiUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
private val securityPreferences: SecurityPreferences by injectLazy()
|
||||||
|
|
||||||
|
private val percentFormatter = NumberFormat.getPercentInstance().apply {
|
||||||
|
roundingMode = RoundingMode.DOWN
|
||||||
|
maximumFractionDigits = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitmap of the app for notifications.
|
||||||
|
*/
|
||||||
|
private val notificationBitmap by lazy {
|
||||||
|
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached progress notification to avoid creating a lot.
|
||||||
|
*/
|
||||||
|
val progressNotificationBuilder by lazy {
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_EHENTAI) {
|
||||||
|
setContentTitle(context.stringResource(MR.strings.app_name))
|
||||||
|
setSmallIcon(R.drawable.ic_refresh_24dp)
|
||||||
|
setLargeIcon(notificationBitmap)
|
||||||
|
setOngoing(true)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the notification containing the currently updating manga and the progress.
|
||||||
|
*
|
||||||
|
* @param manga the manga that are being updated.
|
||||||
|
* @param current the current progress.
|
||||||
|
* @param total the total progress.
|
||||||
|
*/
|
||||||
|
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
||||||
|
progressNotificationBuilder
|
||||||
|
.setContentTitle(
|
||||||
|
context.stringResource(
|
||||||
|
MR.strings.notification_updating_progress,
|
||||||
|
percentFormatter.format(current.toFloat() / total),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!securityPreferences.hideNotificationContent().get()) {
|
||||||
|
val updatingText = manga.title.chop(40)
|
||||||
|
progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
|
||||||
|
}
|
||||||
|
|
||||||
|
context.notify(
|
||||||
|
Notifications.ID_EHENTAI_PROGRESS,
|
||||||
|
progressNotificationBuilder
|
||||||
|
.setProgress(total, current, false)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows notification containing update entries that failed with action to open full log.
|
||||||
|
*
|
||||||
|
* @param failed Number of entries that failed to update.
|
||||||
|
* @param uri Uri for error log file containing all titles that failed.
|
||||||
|
*/
|
||||||
|
fun showUpdateErrorNotification(failed: Int, uri: Uri) {
|
||||||
|
if (failed == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.notify(
|
||||||
|
Notifications.ID_EHENTAI_ERROR,
|
||||||
|
Notifications.CHANNEL_LIBRARY_EHENTAI,
|
||||||
|
) {
|
||||||
|
setContentTitle(context.stringResource(MR.strings.notification_update_error, failed))
|
||||||
|
setContentText(context.stringResource(MR.strings.action_show_errors))
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
|
||||||
|
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the progress notification.
|
||||||
|
*/
|
||||||
|
fun cancelProgressNotification() {
|
||||||
|
context.cancelNotification(Notifications.ID_EHENTAI_PROGRESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package exh.eh
|
package exh.eh
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.os.Build
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.OutOfQuotaPolicy
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.elvishew.xlog.Logger
|
import com.elvishew.xlog.Logger
|
||||||
@@ -14,8 +18,10 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
|||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||||
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
|
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
|
||||||
|
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
|
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
|
||||||
@@ -27,9 +33,11 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
|
||||||
import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
|
import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
|
||||||
@@ -46,9 +54,10 @@ import kotlin.time.Duration.Companion.days
|
|||||||
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
|
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
private val preferences: UnsortedPreferences by injectLazy()
|
private val preferences: UnsortedPreferences by injectLazy()
|
||||||
|
private val libraryPreferences: LibraryPreferences by injectLazy()
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
||||||
private val logger: Logger = xLog()
|
private val logger: Logger by lazy { xLog() }
|
||||||
private val updateManga: UpdateManga by injectLazy()
|
private val updateManga: UpdateManga by injectLazy()
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
|
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId by injectLazy()
|
private val getChaptersByMangaId: GetChaptersByMangaId by injectLazy()
|
||||||
@@ -56,22 +65,38 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
|||||||
private val insertFlatMetadata: InsertFlatMetadata by injectLazy()
|
private val insertFlatMetadata: InsertFlatMetadata by injectLazy()
|
||||||
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
|
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
|
||||||
|
|
||||||
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
|
private val updateNotifier by lazy { EHentaiUpdateNotifier(context) }
|
||||||
|
private val libraryUpdateNotifier by lazy { LibraryUpdateNotifier(context) }
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
return try {
|
return try {
|
||||||
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
|
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
|
||||||
Result.success() // retry again later
|
Result.success() // retry again later
|
||||||
} else {
|
} else {
|
||||||
|
setForegroundSafely()
|
||||||
startUpdating()
|
startUpdating()
|
||||||
logger.d("Update job completed!")
|
logger.d("Update job completed!")
|
||||||
Result.success()
|
Result.success()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Result.success() // retry again later
|
Result.success() // retry again later
|
||||||
|
} finally {
|
||||||
|
updateNotifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||||
|
return ForegroundInfo(
|
||||||
|
Notifications.ID_EHENTAI_PROGRESS,
|
||||||
|
updateNotifier.progressNotificationBuilder.build(),
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun startUpdating() {
|
private suspend fun startUpdating() {
|
||||||
logger.d("Update job started!")
|
logger.d("Update job started!")
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
@@ -138,6 +163,11 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
|||||||
}
|
}
|
||||||
|
|
||||||
val (new, chapters) = try {
|
val (new, chapters) = try {
|
||||||
|
updateNotifier.showProgressNotification(
|
||||||
|
manga,
|
||||||
|
updatedThisIteration + failuresThisIteration,
|
||||||
|
mangaMetaToUpdateThisIter.size,
|
||||||
|
)
|
||||||
updateEntryAndGetChapters(manga)
|
updateEntryAndGetChapters(manga)
|
||||||
} catch (e: GalleryNotUpdatedException) {
|
} catch (e: GalleryNotUpdatedException) {
|
||||||
if (e.network) {
|
if (e.network) {
|
||||||
@@ -169,13 +199,15 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find accepted root and discard others
|
// Find accepted root and discard others
|
||||||
val (acceptedRoot, discardedRoots, hasNew) =
|
val (acceptedRoot, discardedRoots, exhNew) =
|
||||||
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
|
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
|
||||||
|
|
||||||
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
|
if (new.isNotEmpty() && manga.id == acceptedRoot.manga.id) {
|
||||||
(hasNew && updatedManga.none { it.first.id == acceptedRoot.manga.id })
|
libraryPreferences.newUpdatesCount().getAndSet { it + new.size }
|
||||||
) {
|
|
||||||
updatedManga += acceptedRoot.manga to new.toTypedArray()
|
updatedManga += acceptedRoot.manga to new.toTypedArray()
|
||||||
|
} else if (exhNew.isNotEmpty() && updatedManga.none { it.first.id == acceptedRoot.manga.id }) {
|
||||||
|
libraryPreferences.newUpdatesCount().getAndSet { it + exhNew.size }
|
||||||
|
updatedManga += acceptedRoot.manga to exhNew.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiedThisIteration += acceptedRoot.manga.id
|
modifiedThisIteration += acceptedRoot.manga.id
|
||||||
@@ -193,8 +225,9 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
updateNotifier.cancelProgressNotification()
|
||||||
if (updatedManga.isNotEmpty()) {
|
if (updatedManga.isNotEmpty()) {
|
||||||
updateNotifier.showUpdateNotifications(updatedManga)
|
libraryUpdateNotifier.showUpdateNotifications(updatedManga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +270,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
|||||||
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
|
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
|
||||||
|
|
||||||
fun launchBackgroundTest(context: Context) {
|
fun launchBackgroundTest(context: Context) {
|
||||||
context.workManager.enqueue(OneTimeWorkRequestBuilder<EHentaiUpdateWorker>().build())
|
context.workManager.enqueue(
|
||||||
|
OneTimeWorkRequestBuilder<EHentaiUpdateWorker>()
|
||||||
|
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||||
|
.addTag(TAG)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scheduleBackground(context: Context, prefInterval: Int? = null, prefRestrictions: Set<String>? = null) {
|
fun scheduleBackground(context: Context, prefInterval: Int? = null, prefRestrictions: Set<String>? = null) {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class MemAutoFlushingLookupTable<T>(
|
|||||||
val size = bb.getInt(4)
|
val size = bb.getInt(4)
|
||||||
val strBArr = ByteArray(size)
|
val strBArr = ByteArray(size)
|
||||||
if (!input.requireBytes(strBArr, size)) break
|
if (!input.requireBytes(strBArr, size)) break
|
||||||
table.put(k, serializer.read(strBArr.toString(Charsets.UTF_8)))
|
table.put(k, serializer.read(strBArr.decodeToString()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
@@ -131,7 +131,7 @@ class MemAutoFlushingLookupTable<T>(
|
|||||||
try {
|
try {
|
||||||
val out = fos.sink().buffer()
|
val out = fos.sink().buffer()
|
||||||
table.forEach { key, value ->
|
table.forEach { key, value ->
|
||||||
val v = serializer.write(value).toByteArray(Charsets.UTF_8)
|
val v = serializer.write(value).encodeToByteArray()
|
||||||
bb.putInt(0, key)
|
bb.putInt(0, key)
|
||||||
bb.putInt(4, v.size)
|
bb.putInt(4, v.size)
|
||||||
out.write(bb.array())
|
out.write(bb.array())
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,22 +7,31 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:abby dark star",
|
"cosplayer:abby dark star",
|
||||||
"cosplayer:adamae-dono",
|
"cosplayer:adamae-dono",
|
||||||
"cosplayer:ai lei jiang",
|
"cosplayer:ai lei jiang",
|
||||||
|
"cosplayer:ai xi",
|
||||||
"cosplayer:aiga mizuki",
|
"cosplayer:aiga mizuki",
|
||||||
|
"cosplayer:aimy",
|
||||||
"cosplayer:aizawa ren",
|
"cosplayer:aizawa ren",
|
||||||
"cosplayer:ajo cosplay",
|
"cosplayer:ajo cosplay",
|
||||||
"cosplayer:akane araragi",
|
"cosplayer:akane araragi",
|
||||||
|
"cosplayer:akari yamazaki",
|
||||||
"cosplayer:akemi101xoxo",
|
"cosplayer:akemi101xoxo",
|
||||||
"cosplayer:akiba cute star",
|
"cosplayer:akiba cute star",
|
||||||
"cosplayer:akitsu honoka",
|
"cosplayer:akitsu honoka",
|
||||||
"cosplayer:aleksandra bodler",
|
"cosplayer:aleksandra bodler",
|
||||||
|
"cosplayer:aleksandra lerman",
|
||||||
"cosplayer:aleksis hitc",
|
"cosplayer:aleksis hitc",
|
||||||
"cosplayer:alexis lust",
|
"cosplayer:alexis lust",
|
||||||
"cosplayer:alice bong",
|
"cosplayer:alice bong",
|
||||||
"cosplayer:alice cosplay",
|
"cosplayer:alice cosplay",
|
||||||
|
"cosplayer:alice wonder",
|
||||||
|
"cosplayer:aliceholic",
|
||||||
"cosplayer:alicekyo",
|
"cosplayer:alicekyo",
|
||||||
"cosplayer:alin ma",
|
"cosplayer:alin ma",
|
||||||
|
"cosplayer:alisa arkhangelskaya",
|
||||||
"cosplayer:alisa kiss",
|
"cosplayer:alisa kiss",
|
||||||
|
"cosplayer:alisa valeeva",
|
||||||
"cosplayer:alodia gosiengfiao",
|
"cosplayer:alodia gosiengfiao",
|
||||||
|
"cosplayer:alva velasco",
|
||||||
"cosplayer:alycia elvie",
|
"cosplayer:alycia elvie",
|
||||||
"cosplayer:amanda welp",
|
"cosplayer:amanda welp",
|
||||||
"cosplayer:amber hallibell",
|
"cosplayer:amber hallibell",
|
||||||
@@ -31,16 +40,20 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:anabelle joy",
|
"cosplayer:anabelle joy",
|
||||||
"cosplayer:anastasia komori",
|
"cosplayer:anastasia komori",
|
||||||
"cosplayer:angelina preobrazhenskaya",
|
"cosplayer:angelina preobrazhenskaya",
|
||||||
|
"cosplayer:aniela verbin",
|
||||||
"cosplayer:aninnyan",
|
"cosplayer:aninnyan",
|
||||||
"cosplayer:anizu chie",
|
"cosplayer:anizu chie",
|
||||||
"cosplayer:anna kuramoto",
|
"cosplayer:anna kuramoto",
|
||||||
"cosplayer:annie seixas",
|
"cosplayer:annie seixas",
|
||||||
|
"cosplayer:anxi",
|
||||||
"cosplayer:aokotan",
|
"cosplayer:aokotan",
|
||||||
"cosplayer:arai yomi",
|
"cosplayer:arai yomi",
|
||||||
"cosplayer:araki mai",
|
"cosplayer:araki mai",
|
||||||
|
"cosplayer:ari.anna",
|
||||||
"cosplayer:arisa mizuhara",
|
"cosplayer:arisa mizuhara",
|
||||||
"cosplayer:arty huang",
|
"cosplayer:arty huang",
|
||||||
"cosplayer:asakawa ran",
|
"cosplayer:asakawa ran",
|
||||||
|
"cosplayer:asakura kotomi",
|
||||||
"cosplayer:asakura naho",
|
"cosplayer:asakura naho",
|
||||||
"cosplayer:ashiya noriko",
|
"cosplayer:ashiya noriko",
|
||||||
"cosplayer:astasiadream",
|
"cosplayer:astasiadream",
|
||||||
@@ -48,28 +61,39 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:atina",
|
"cosplayer:atina",
|
||||||
"cosplayer:atsuki",
|
"cosplayer:atsuki",
|
||||||
"cosplayer:audalove",
|
"cosplayer:audalove",
|
||||||
|
"cosplayer:aveline tetsuya",
|
||||||
"cosplayer:ayaka matsunaga",
|
"cosplayer:ayaka matsunaga",
|
||||||
"cosplayer:bailey jay",
|
"cosplayer:bailey jay",
|
||||||
"cosplayer:banbanko",
|
"cosplayer:banbanko",
|
||||||
|
"cosplayer:beibei kappu",
|
||||||
|
"cosplayer:bella bunbun",
|
||||||
"cosplayer:bili bili",
|
"cosplayer:bili bili",
|
||||||
"cosplayer:bishoujomom",
|
"cosplayer:bishoujomom",
|
||||||
|
"cosplayer:blacqkl",
|
||||||
"cosplayer:bloodraven",
|
"cosplayer:bloodraven",
|
||||||
"cosplayer:bobbi starr",
|
"cosplayer:bobbi starr",
|
||||||
"cosplayer:bonn1ethebunny",
|
"cosplayer:bonn1ethebunny",
|
||||||
"cosplayer:bonnie bonkers",
|
"cosplayer:bonnie bonkers",
|
||||||
"cosplayer:boople snoot",
|
"cosplayer:boople snoot",
|
||||||
|
"cosplayer:breesknees",
|
||||||
|
"cosplayer:bukkitbrown",
|
||||||
"cosplayer:bunny ayumi",
|
"cosplayer:bunny ayumi",
|
||||||
"cosplayer:carry key",
|
"cosplayer:carry key",
|
||||||
"cosplayer:chadkasa",
|
"cosplayer:chadkasa",
|
||||||
|
"cosplayer:charess",
|
||||||
"cosplayer:charles dera",
|
"cosplayer:charles dera",
|
||||||
|
"cosplayer:chisai cosplay",
|
||||||
"cosplayer:chokoboll mukakoi.",
|
"cosplayer:chokoboll mukakoi.",
|
||||||
"cosplayer:christina volkova",
|
"cosplayer:christina volkova",
|
||||||
"cosplayer:chunmomo",
|
"cosplayer:chunmomo",
|
||||||
"cosplayer:cocopie",
|
"cosplayer:cocopie",
|
||||||
"cosplayer:comonun",
|
"cosplayer:comonun",
|
||||||
|
"cosplayer:cristina luise",
|
||||||
|
"cosplayer:dakko ja rrs",
|
||||||
"cosplayer:dani doe",
|
"cosplayer:dani doe",
|
||||||
"cosplayer:danielle beaulieu",
|
"cosplayer:danielle beaulieu",
|
||||||
"cosplayer:danielle vedovelli",
|
"cosplayer:danielle vedovelli",
|
||||||
|
"cosplayer:daria kravets",
|
||||||
"cosplayer:darling cute",
|
"cosplayer:darling cute",
|
||||||
"cosplayer:dessyy",
|
"cosplayer:dessyy",
|
||||||
"cosplayer:dillion harper",
|
"cosplayer:dillion harper",
|
||||||
@@ -78,39 +102,56 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:donnaloli",
|
"cosplayer:donnaloli",
|
||||||
"cosplayer:eira wang",
|
"cosplayer:eira wang",
|
||||||
"cosplayer:electricbum",
|
"cosplayer:electricbum",
|
||||||
|
"cosplayer:elena lenina",
|
||||||
|
"cosplayer:elena yuna",
|
||||||
|
"cosplayer:elisa cattabriga",
|
||||||
|
"cosplayer:elyhria",
|
||||||
"cosplayer:erin eevee",
|
"cosplayer:erin eevee",
|
||||||
|
"cosplayer:erotic doki",
|
||||||
"cosplayer:eroticneko",
|
"cosplayer:eroticneko",
|
||||||
|
"cosplayer:esther rutkovskaya-tudor",
|
||||||
"cosplayer:eunji pyo",
|
"cosplayer:eunji pyo",
|
||||||
"cosplayer:evawxsh",
|
"cosplayer:evawxsh",
|
||||||
"cosplayer:evenink",
|
"cosplayer:evenink",
|
||||||
"cosplayer:evie evangelion",
|
"cosplayer:evie evangelion",
|
||||||
"cosplayer:ezy summers",
|
"cosplayer:ezy summers",
|
||||||
|
"cosplayer:fatiaoliii",
|
||||||
|
"cosplayer:fay sg",
|
||||||
"cosplayer:fe galvao",
|
"cosplayer:fe galvao",
|
||||||
"cosplayer:felvelial",
|
"cosplayer:felvelial",
|
||||||
|
"cosplayer:feng jiang jiang",
|
||||||
"cosplayer:ferin feirn",
|
"cosplayer:ferin feirn",
|
||||||
"cosplayer:feywilde",
|
"cosplayer:feywilde",
|
||||||
|
"cosplayer:firtsbornunicorn",
|
||||||
"cosplayer:flora daria",
|
"cosplayer:flora daria",
|
||||||
"cosplayer:fluffy nemu",
|
"cosplayer:fluffy nemu",
|
||||||
"cosplayer:franxcos",
|
"cosplayer:franxcos",
|
||||||
"cosplayer:frauleinmilk",
|
"cosplayer:frauleinmilk",
|
||||||
|
"cosplayer:fubuki ami",
|
||||||
"cosplayer:fuji serika",
|
"cosplayer:fuji serika",
|
||||||
|
"cosplayer:futaba emiru",
|
||||||
"cosplayer:g44 wa kizutsukanai",
|
"cosplayer:g44 wa kizutsukanai",
|
||||||
"cosplayer:garo dazay",
|
"cosplayer:garo dazay",
|
||||||
|
"cosplayer:generic egirl",
|
||||||
"cosplayer:genthehobbit",
|
"cosplayer:genthehobbit",
|
||||||
"cosplayer:ghostly cosplay",
|
"cosplayer:ghostly cosplay",
|
||||||
|
"cosplayer:giorgia vecchini",
|
||||||
"cosplayer:giulia valeriani",
|
"cosplayer:giulia valeriani",
|
||||||
"cosplayer:goth egg",
|
"cosplayer:goth egg",
|
||||||
"cosplayer:gumiho hannya",
|
"cosplayer:gumiho hannya",
|
||||||
"cosplayer:guo chengzi",
|
"cosplayer:guo chengzi",
|
||||||
"cosplayer:hakuhi kaede",
|
"cosplayer:hakuhi kaede",
|
||||||
|
"cosplayer:hamasaki rio",
|
||||||
"cosplayer:han yeri",
|
"cosplayer:han yeri",
|
||||||
"cosplayer:hanamura misaki",
|
"cosplayer:hanamura misaki",
|
||||||
"cosplayer:hane ame",
|
"cosplayer:hane ame",
|
||||||
|
"cosplayer:harukaism",
|
||||||
"cosplayer:helly von valentine",
|
"cosplayer:helly von valentine",
|
||||||
"cosplayer:hessakai",
|
"cosplayer:hessakai",
|
||||||
"cosplayer:hey shika",
|
"cosplayer:hey shika",
|
||||||
"cosplayer:higurashi rin",
|
"cosplayer:higurashi rin",
|
||||||
"cosplayer:himeecosplay",
|
"cosplayer:himeecosplay",
|
||||||
|
"cosplayer:hinatasama",
|
||||||
"cosplayer:hinaughtya",
|
"cosplayer:hinaughtya",
|
||||||
"cosplayer:hiyo nishizuku",
|
"cosplayer:hiyo nishizuku",
|
||||||
"cosplayer:holly ava",
|
"cosplayer:holly ava",
|
||||||
@@ -121,18 +162,30 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:imokawa naoko",
|
"cosplayer:imokawa naoko",
|
||||||
"cosplayer:ino cosplay",
|
"cosplayer:ino cosplay",
|
||||||
"cosplayer:iori moe",
|
"cosplayer:iori moe",
|
||||||
|
"cosplayer:iri",
|
||||||
"cosplayer:ishikawa asami",
|
"cosplayer:ishikawa asami",
|
||||||
|
"cosplayer:jannet kat",
|
||||||
|
"cosplayer:jannet vinogradova",
|
||||||
|
"cosplayer:jasming chea",
|
||||||
"cosplayer:jaycee",
|
"cosplayer:jaycee",
|
||||||
|
"cosplayer:jenezial",
|
||||||
"cosplayer:jenna lynn meowri",
|
"cosplayer:jenna lynn meowri",
|
||||||
|
"cosplayer:jenni kaellberg",
|
||||||
"cosplayer:jessica nigri",
|
"cosplayer:jessica nigri",
|
||||||
|
"cosplayer:jessika jinx",
|
||||||
"cosplayer:jill",
|
"cosplayer:jill",
|
||||||
"cosplayer:jinxie",
|
"cosplayer:jinxie",
|
||||||
"cosplayer:jiuqujean",
|
"cosplayer:jiuqujean",
|
||||||
|
"cosplayer:jiuyan",
|
||||||
"cosplayer:jiyun choi",
|
"cosplayer:jiyun choi",
|
||||||
|
"cosplayer:joanna muller",
|
||||||
|
"cosplayer:julia larangeiras",
|
||||||
"cosplayer:julia shuenkova",
|
"cosplayer:julia shuenkova",
|
||||||
"cosplayer:jun ye tako",
|
"cosplayer:jun ye tako",
|
||||||
"cosplayer:kae kaieda",
|
"cosplayer:kae kaieda",
|
||||||
"cosplayer:kalinka fox",
|
"cosplayer:kalinka fox",
|
||||||
|
"cosplayer:kamelya chan",
|
||||||
|
"cosplayer:kamijiri ichigo",
|
||||||
"cosplayer:kamui alice",
|
"cosplayer:kamui alice",
|
||||||
"cosplayer:kanda likitsangjaroen",
|
"cosplayer:kanda likitsangjaroen",
|
||||||
"cosplayer:kanda midori",
|
"cosplayer:kanda midori",
|
||||||
@@ -141,6 +194,7 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:katiecakey",
|
"cosplayer:katiecakey",
|
||||||
"cosplayer:kay bear",
|
"cosplayer:kay bear",
|
||||||
"cosplayer:kaya huang",
|
"cosplayer:kaya huang",
|
||||||
|
"cosplayer:kei shino",
|
||||||
"cosplayer:khainsaw",
|
"cosplayer:khainsaw",
|
||||||
"cosplayer:kibashi",
|
"cosplayer:kibashi",
|
||||||
"cosplayer:kiera marie",
|
"cosplayer:kiera marie",
|
||||||
@@ -159,19 +213,27 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:kotea dali",
|
"cosplayer:kotea dali",
|
||||||
"cosplayer:koyama rikako",
|
"cosplayer:koyama rikako",
|
||||||
"cosplayer:kqueentsun",
|
"cosplayer:kqueentsun",
|
||||||
|
"cosplayer:kurasaka kururu",
|
||||||
"cosplayer:kurumi.",
|
"cosplayer:kurumi.",
|
||||||
"cosplayer:kururugi aoi",
|
"cosplayer:kururugi aoi",
|
||||||
"cosplayer:kuuko w",
|
"cosplayer:kuuko w",
|
||||||
"cosplayer:kyonatix",
|
"cosplayer:kyonatix",
|
||||||
|
"cosplayer:lagertha",
|
||||||
|
"cosplayer:lea martinez",
|
||||||
"cosplayer:lelewu",
|
"cosplayer:lelewu",
|
||||||
"cosplayer:lenfried",
|
"cosplayer:lenfried",
|
||||||
"cosplayer:lewdoart",
|
"cosplayer:lewdoart",
|
||||||
|
"cosplayer:lex kuma",
|
||||||
"cosplayer:lightz",
|
"cosplayer:lightz",
|
||||||
|
"cosplayer:lili erlih",
|
||||||
"cosplayer:lilly rose",
|
"cosplayer:lilly rose",
|
||||||
"cosplayer:lilya victorovna",
|
"cosplayer:lilya victorovna",
|
||||||
"cosplayer:lilylit",
|
"cosplayer:lilylit",
|
||||||
|
"cosplayer:lina erdel",
|
||||||
|
"cosplayer:ling li",
|
||||||
"cosplayer:linneas life",
|
"cosplayer:linneas life",
|
||||||
"cosplayer:linzi jiang",
|
"cosplayer:linzi jiang",
|
||||||
|
"cosplayer:little blue girl",
|
||||||
"cosplayer:lizyhsan",
|
"cosplayer:lizyhsan",
|
||||||
"cosplayer:lmusicl",
|
"cosplayer:lmusicl",
|
||||||
"cosplayer:lovelyspacekitten",
|
"cosplayer:lovelyspacekitten",
|
||||||
@@ -180,11 +242,16 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:mags.irl",
|
"cosplayer:mags.irl",
|
||||||
"cosplayer:mais conheyo",
|
"cosplayer:mais conheyo",
|
||||||
"cosplayer:manyu hanausagi",
|
"cosplayer:manyu hanausagi",
|
||||||
|
"cosplayer:mappy sanchez",
|
||||||
"cosplayer:marie-claude bourbonnais",
|
"cosplayer:marie-claude bourbonnais",
|
||||||
"cosplayer:mariigabii",
|
"cosplayer:mariigabii",
|
||||||
|
"cosplayer:masako yume",
|
||||||
|
"cosplayer:meagan vanburkleo",
|
||||||
|
"cosplayer:mei succubus",
|
||||||
"cosplayer:meikoui",
|
"cosplayer:meikoui",
|
||||||
"cosplayer:meriol-chan",
|
"cosplayer:meriol-chan",
|
||||||
"cosplayer:mianbing xianer",
|
"cosplayer:mianbing xianer",
|
||||||
|
"cosplayer:micro kitty",
|
||||||
"cosplayer:miih cosplay",
|
"cosplayer:miih cosplay",
|
||||||
"cosplayer:miiya",
|
"cosplayer:miiya",
|
||||||
"cosplayer:mik allen",
|
"cosplayer:mik allen",
|
||||||
@@ -192,18 +259,27 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:milena hime",
|
"cosplayer:milena hime",
|
||||||
"cosplayer:milky",
|
"cosplayer:milky",
|
||||||
"cosplayer:mimi-chan",
|
"cosplayer:mimi-chan",
|
||||||
|
"cosplayer:mimmi",
|
||||||
"cosplayer:mimo ningyo",
|
"cosplayer:mimo ningyo",
|
||||||
"cosplayer:minaduki miri",
|
"cosplayer:minaduki miri",
|
||||||
|
"cosplayer:minato riku",
|
||||||
"cosplayer:minematsu rie",
|
"cosplayer:minematsu rie",
|
||||||
|
"cosplayer:mingchudesu",
|
||||||
"cosplayer:mingming kizami",
|
"cosplayer:mingming kizami",
|
||||||
"cosplayer:mingtao",
|
"cosplayer:mingtao",
|
||||||
"cosplayer:minzy tea",
|
"cosplayer:minzy tea",
|
||||||
|
"cosplayer:miorin",
|
||||||
"cosplayer:misaco",
|
"cosplayer:misaco",
|
||||||
|
"cosplayer:mishka bear",
|
||||||
"cosplayer:missbrisolo",
|
"cosplayer:missbrisolo",
|
||||||
|
"cosplayer:misty silver",
|
||||||
|
"cosplayer:mitsuki riyu",
|
||||||
"cosplayer:miura aika",
|
"cosplayer:miura aika",
|
||||||
"cosplayer:miyoki",
|
"cosplayer:miyoki",
|
||||||
"cosplayer:mizhimaoqiu",
|
"cosplayer:mizhimaoqiu",
|
||||||
|
"cosplayer:mizuki akira",
|
||||||
"cosplayer:mochichuu",
|
"cosplayer:mochichuu",
|
||||||
|
"cosplayer:mochimochi-nn",
|
||||||
"cosplayer:mochizuki eiko",
|
"cosplayer:mochizuki eiko",
|
||||||
"cosplayer:mochizuki kanade",
|
"cosplayer:mochizuki kanade",
|
||||||
"cosplayer:moiicos",
|
"cosplayer:moiicos",
|
||||||
@@ -211,8 +287,11 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:momoiro reku",
|
"cosplayer:momoiro reku",
|
||||||
"cosplayer:momoko",
|
"cosplayer:momoko",
|
||||||
"cosplayer:momokun",
|
"cosplayer:momokun",
|
||||||
|
"cosplayer:momousagi mao",
|
||||||
"cosplayer:moody feet",
|
"cosplayer:moody feet",
|
||||||
"cosplayer:morgana cosplay",
|
"cosplayer:morgana cosplay",
|
||||||
|
"cosplayer:mowky",
|
||||||
|
"cosplayer:moyu mommy",
|
||||||
"cosplayer:mozuku kimura",
|
"cosplayer:mozuku kimura",
|
||||||
"cosplayer:mu zhi ben lan",
|
"cosplayer:mu zhi ben lan",
|
||||||
"cosplayer:murasaki",
|
"cosplayer:murasaki",
|
||||||
@@ -222,7 +301,11 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:nadyasonika",
|
"cosplayer:nadyasonika",
|
||||||
"cosplayer:nagisa",
|
"cosplayer:nagisa",
|
||||||
"cosplayer:nan tao momoko",
|
"cosplayer:nan tao momoko",
|
||||||
|
"cosplayer:natalya ditrikh",
|
||||||
"cosplayer:natasha roik",
|
"cosplayer:natasha roik",
|
||||||
|
"cosplayer:nateephan thammasilbanyad",
|
||||||
|
"cosplayer:nawo019",
|
||||||
|
"cosplayer:nayfi bardales",
|
||||||
"cosplayer:nekob0icarti",
|
"cosplayer:nekob0icarti",
|
||||||
"cosplayer:neroko kaigan",
|
"cosplayer:neroko kaigan",
|
||||||
"cosplayer:niannian d",
|
"cosplayer:niannian d",
|
||||||
@@ -235,58 +318,81 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:nonbinate",
|
"cosplayer:nonbinate",
|
||||||
"cosplayer:nora fawn",
|
"cosplayer:nora fawn",
|
||||||
"cosplayer:nuko meguro",
|
"cosplayer:nuko meguro",
|
||||||
|
"cosplayer:nuria gonzalez",
|
||||||
"cosplayer:nyako",
|
"cosplayer:nyako",
|
||||||
|
"cosplayer:nymph-princess",
|
||||||
"cosplayer:octokuro",
|
"cosplayer:octokuro",
|
||||||
"cosplayer:odoru neko ningen",
|
"cosplayer:odoru neko ningen",
|
||||||
"cosplayer:oharucosplay",
|
"cosplayer:oharucosplay",
|
||||||
"cosplayer:oichi",
|
"cosplayer:oichi",
|
||||||
"cosplayer:okada yui",
|
"cosplayer:okada yui",
|
||||||
"cosplayer:oki-cospi",
|
"cosplayer:oki-cospi",
|
||||||
|
"cosplayer:olivia metric",
|
||||||
"cosplayer:olyashaa saxon",
|
"cosplayer:olyashaa saxon",
|
||||||
"cosplayer:pattie cosplay",
|
"cosplayer:pattie cosplay",
|
||||||
"cosplayer:pattycake",
|
"cosplayer:pattycake",
|
||||||
"cosplayer:peachtot",
|
"cosplayer:peachtot",
|
||||||
"cosplayer:penkarui",
|
"cosplayer:penkarui",
|
||||||
|
"cosplayer:penny walsh",
|
||||||
|
"cosplayer:pichapu",
|
||||||
"cosplayer:pokket",
|
"cosplayer:pokket",
|
||||||
"cosplayer:poon warunya",
|
"cosplayer:poon warunya",
|
||||||
"cosplayer:punk macarroni",
|
"cosplayer:punk macarroni",
|
||||||
|
"cosplayer:purrblind",
|
||||||
"cosplayer:pushiku",
|
"cosplayer:pushiku",
|
||||||
"cosplayer:qiqi nanazi",
|
"cosplayer:qiqi nanazi",
|
||||||
"cosplayer:qiqi xiaojie",
|
"cosplayer:qiqi xiaojie",
|
||||||
"cosplayer:qiuhe keji",
|
"cosplayer:qiuhe keji",
|
||||||
"cosplayer:queenie",
|
"cosplayer:queenie",
|
||||||
|
"cosplayer:quist",
|
||||||
|
"cosplayer:rachel ravaged",
|
||||||
|
"cosplayer:rakuraku",
|
||||||
"cosplayer:ravvcoser",
|
"cosplayer:ravvcoser",
|
||||||
"cosplayer:raynearts",
|
"cosplayer:raynearts",
|
||||||
"cosplayer:rea kami",
|
"cosplayer:rea kami",
|
||||||
|
"cosplayer:renee storm",
|
||||||
"cosplayer:rhylee passfield",
|
"cosplayer:rhylee passfield",
|
||||||
"cosplayer:ri care",
|
"cosplayer:ri care",
|
||||||
|
"cosplayer:riani haratina",
|
||||||
"cosplayer:rinami",
|
"cosplayer:rinami",
|
||||||
|
"cosplayer:ringo mitsuki",
|
||||||
|
"cosplayer:rinoa",
|
||||||
"cosplayer:rio-chan",
|
"cosplayer:rio-chan",
|
||||||
"cosplayer:rioko",
|
"cosplayer:rioko",
|
||||||
|
"cosplayer:rissoft",
|
||||||
"cosplayer:rocksy light",
|
"cosplayer:rocksy light",
|
||||||
"cosplayer:rolyatistaylor",
|
"cosplayer:rolyatistaylor",
|
||||||
"cosplayer:rongrongzi",
|
"cosplayer:rongrongzi",
|
||||||
|
"cosplayer:rusuwu",
|
||||||
"cosplayer:sachi budou",
|
"cosplayer:sachi budou",
|
||||||
"cosplayer:saiwari ph",
|
"cosplayer:saiwari ph",
|
||||||
|
"cosplayer:saki kawanami",
|
||||||
"cosplayer:saku",
|
"cosplayer:saku",
|
||||||
"cosplayer:sakura ema",
|
"cosplayer:sakura ema",
|
||||||
"cosplayer:sakurai",
|
"cosplayer:sakurai",
|
||||||
"cosplayer:sakurai hinoki",
|
"cosplayer:sakurai hinoki",
|
||||||
|
"cosplayer:samantha boon",
|
||||||
"cosplayer:sandykuroneko",
|
"cosplayer:sandykuroneko",
|
||||||
"cosplayer:saotome love",
|
"cosplayer:saotome love",
|
||||||
"cosplayer:sara underwood",
|
"cosplayer:sara underwood",
|
||||||
"cosplayer:sarah quillian",
|
"cosplayer:sarah quillian",
|
||||||
|
"cosplayer:sarawrcosplay",
|
||||||
"cosplayer:sasaki remi",
|
"cosplayer:sasaki remi",
|
||||||
|
"cosplayer:sato yuri",
|
||||||
"cosplayer:savannah sixx",
|
"cosplayer:savannah sixx",
|
||||||
"cosplayer:sawaka",
|
"cosplayer:sawaka",
|
||||||
"cosplayer:scarlett afterdark",
|
"cosplayer:scarlett afterdark",
|
||||||
"cosplayer:sean lawless",
|
"cosplayer:sean lawless",
|
||||||
|
"cosplayer:sei",
|
||||||
"cosplayer:seltin sweet",
|
"cosplayer:seltin sweet",
|
||||||
|
"cosplayer:sena",
|
||||||
|
"cosplayer:sexy toys",
|
||||||
"cosplayer:sexyflowerwater",
|
"cosplayer:sexyflowerwater",
|
||||||
"cosplayer:sharkparty",
|
"cosplayer:sharkparty",
|
||||||
"cosplayer:shermie",
|
"cosplayer:shermie",
|
||||||
"cosplayer:shibuya kaho",
|
"cosplayer:shibuya kaho",
|
||||||
"cosplayer:shimizu yuno",
|
"cosplayer:shimizu yuno",
|
||||||
|
"cosplayer:shinen",
|
||||||
"cosplayer:shiro kitsune",
|
"cosplayer:shiro kitsune",
|
||||||
"cosplayer:shiroluxx",
|
"cosplayer:shiroluxx",
|
||||||
"cosplayer:siao ding",
|
"cosplayer:siao ding",
|
||||||
@@ -295,14 +401,24 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:soa lianna",
|
"cosplayer:soa lianna",
|
||||||
"cosplayer:son yeeun",
|
"cosplayer:son yeeun",
|
||||||
"cosplayer:sophie snomster",
|
"cosplayer:sophie snomster",
|
||||||
|
"cosplayer:stanislava anushkina",
|
||||||
|
"cosplayer:starfmodel",
|
||||||
"cosplayer:stelar hoshi",
|
"cosplayer:stelar hoshi",
|
||||||
|
"cosplayer:sunny lin",
|
||||||
"cosplayer:sunny ray",
|
"cosplayer:sunny ray",
|
||||||
"cosplayer:sunnyvier",
|
"cosplayer:sunnyvier",
|
||||||
"cosplayer:sunohara miki",
|
"cosplayer:sunohara miki",
|
||||||
|
"cosplayer:sushiflavoredmilk",
|
||||||
|
"cosplayer:suspira grey",
|
||||||
|
"cosplayer:tachibana remika",
|
||||||
"cosplayer:tanaka hitomi",
|
"cosplayer:tanaka hitomi",
|
||||||
|
"cosplayer:tanaka mana",
|
||||||
"cosplayer:tangtang",
|
"cosplayer:tangtang",
|
||||||
"cosplayer:tanja kensinger",
|
"cosplayer:tanja kensinger",
|
||||||
|
"cosplayer:tao liang azhai",
|
||||||
|
"cosplayer:tara nicole azarian",
|
||||||
"cosplayer:tasha leigh",
|
"cosplayer:tasha leigh",
|
||||||
|
"cosplayer:tatiana neva",
|
||||||
"cosplayer:tenleid",
|
"cosplayer:tenleid",
|
||||||
"cosplayer:tenryu-0",
|
"cosplayer:tenryu-0",
|
||||||
"cosplayer:tenshi myu.",
|
"cosplayer:tenshi myu.",
|
||||||
@@ -310,8 +426,10 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:tiffany gordon",
|
"cosplayer:tiffany gordon",
|
||||||
"cosplayer:tiny asa",
|
"cosplayer:tiny asa",
|
||||||
"cosplayer:todopokie",
|
"cosplayer:todopokie",
|
||||||
|
"cosplayer:tristan valdez",
|
||||||
"cosplayer:tsubaki zakuro",
|
"cosplayer:tsubaki zakuro",
|
||||||
"cosplayer:tsuki desu",
|
"cosplayer:tsuki desu",
|
||||||
|
"cosplayer:tsuki miko",
|
||||||
"cosplayer:tsukimiya madoka",
|
"cosplayer:tsukimiya madoka",
|
||||||
"cosplayer:tsuyato",
|
"cosplayer:tsuyato",
|
||||||
"cosplayer:turkish chi-chi",
|
"cosplayer:turkish chi-chi",
|
||||||
@@ -320,34 +438,49 @@ object Cosplayer : TagList {
|
|||||||
"cosplayer:ukyuu nako",
|
"cosplayer:ukyuu nako",
|
||||||
"cosplayer:una cosplayer",
|
"cosplayer:una cosplayer",
|
||||||
"cosplayer:uno megumi",
|
"cosplayer:uno megumi",
|
||||||
|
"cosplayer:ur senpai june",
|
||||||
"cosplayer:uru uruu",
|
"cosplayer:uru uruu",
|
||||||
|
"cosplayer:uta kohaku",
|
||||||
"cosplayer:valery himera",
|
"cosplayer:valery himera",
|
||||||
"cosplayer:velvet",
|
"cosplayer:velvet",
|
||||||
"cosplayer:veroodle",
|
"cosplayer:veroodle",
|
||||||
|
"cosplayer:vlada lutsak",
|
||||||
"cosplayer:wanco chan",
|
"cosplayer:wanco chan",
|
||||||
|
"cosplayer:whimpercat",
|
||||||
"cosplayer:wifey",
|
"cosplayer:wifey",
|
||||||
"cosplayer:wildhoney423",
|
"cosplayer:wildhoney423",
|
||||||
"cosplayer:xansoon",
|
"cosplayer:xansoon",
|
||||||
"cosplayer:xia xia zi",
|
"cosplayer:xia xia zi",
|
||||||
|
"cosplayer:xiaoyao yaoyao",
|
||||||
|
"cosplayer:xiaoying shi zhi xiaomulong",
|
||||||
"cosplayer:xidaidai",
|
"cosplayer:xidaidai",
|
||||||
"cosplayer:xue qi-sama",
|
"cosplayer:xue qi-sama",
|
||||||
"cosplayer:yaki",
|
"cosplayer:yaki",
|
||||||
"cosplayer:yaokoututu",
|
"cosplayer:yaokoututu",
|
||||||
"cosplayer:yaoyaoqwq",
|
"cosplayer:yaoyaoqwq",
|
||||||
|
"cosplayer:ying lili",
|
||||||
|
"cosplayer:yoko inui",
|
||||||
"cosplayer:yor succubus",
|
"cosplayer:yor succubus",
|
||||||
"cosplayer:yorkie w",
|
"cosplayer:yorkie w",
|
||||||
|
"cosplayer:youyou",
|
||||||
"cosplayer:yuki astra",
|
"cosplayer:yuki astra",
|
||||||
|
"cosplayer:yuki lefay",
|
||||||
"cosplayer:yuki teyi",
|
"cosplayer:yuki teyi",
|
||||||
"cosplayer:yume",
|
"cosplayer:yume",
|
||||||
|
"cosplayer:yummykimmy",
|
||||||
"cosplayer:yunie lannister",
|
"cosplayer:yunie lannister",
|
||||||
"cosplayer:yunocos69",
|
"cosplayer:yunocos69",
|
||||||
"cosplayer:yurihime",
|
"cosplayer:yurihime",
|
||||||
"cosplayer:yuumeilyn",
|
"cosplayer:yuumeilyn",
|
||||||
"cosplayer:yuyunte",
|
"cosplayer:yuyunte",
|
||||||
|
"cosplayer:yuzu chan",
|
||||||
"cosplayer:yuzuki",
|
"cosplayer:yuzuki",
|
||||||
|
"cosplayer:yuzukimiiu",
|
||||||
"cosplayer:yuzupyon",
|
"cosplayer:yuzupyon",
|
||||||
"cosplayer:zara durose",
|
"cosplayer:zara durose",
|
||||||
"cosplayer:zeico",
|
"cosplayer:zeico",
|
||||||
|
"cosplayer:zhenya zhuk",
|
||||||
|
"cosplayer:zhuimingyou",
|
||||||
"cosplayer:zyunka mukhina",
|
"cosplayer:zyunka mukhina",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ object Female : TagList {
|
|||||||
"female:big vagina",
|
"female:big vagina",
|
||||||
"female:bike shorts",
|
"female:bike shorts",
|
||||||
"female:bikini",
|
"female:bikini",
|
||||||
|
"female:bird girl",
|
||||||
"female:bisexual",
|
"female:bisexual",
|
||||||
|
"female:bite mark",
|
||||||
"female:blackmail",
|
"female:blackmail",
|
||||||
"female:blind",
|
"female:blind",
|
||||||
"female:blindfold",
|
"female:blindfold",
|
||||||
@@ -120,8 +122,10 @@ object Female : TagList {
|
|||||||
"female:cockslapping",
|
"female:cockslapping",
|
||||||
"female:collar",
|
"female:collar",
|
||||||
"female:condom",
|
"female:condom",
|
||||||
|
"female:confinement",
|
||||||
"female:conjoined",
|
"female:conjoined",
|
||||||
"female:coprophagia",
|
"female:coprophagia",
|
||||||
|
"female:corpse",
|
||||||
"female:corruption",
|
"female:corruption",
|
||||||
"female:corset",
|
"female:corset",
|
||||||
"female:cosplaying",
|
"female:cosplaying",
|
||||||
@@ -218,6 +222,7 @@ object Female : TagList {
|
|||||||
"female:frog",
|
"female:frog",
|
||||||
"female:frog girl",
|
"female:frog girl",
|
||||||
"female:frottage",
|
"female:frottage",
|
||||||
|
"female:full tour",
|
||||||
"female:full-packaged futanari",
|
"female:full-packaged futanari",
|
||||||
"female:fundoshi",
|
"female:fundoshi",
|
||||||
"female:furry",
|
"female:furry",
|
||||||
@@ -299,6 +304,7 @@ object Female : TagList {
|
|||||||
"female:kindergarten uniform",
|
"female:kindergarten uniform",
|
||||||
"female:kissing",
|
"female:kissing",
|
||||||
"female:kneepit sex",
|
"female:kneepit sex",
|
||||||
|
"female:kodomo doushi",
|
||||||
"female:kunoichi",
|
"female:kunoichi",
|
||||||
"female:lab coat",
|
"female:lab coat",
|
||||||
"female:lactation",
|
"female:lactation",
|
||||||
@@ -319,6 +325,7 @@ object Female : TagList {
|
|||||||
"female:long tongue",
|
"female:long tongue",
|
||||||
"female:low bestiality",
|
"female:low bestiality",
|
||||||
"female:low guro",
|
"female:low guro",
|
||||||
|
"female:low incest",
|
||||||
"female:low lolicon",
|
"female:low lolicon",
|
||||||
"female:low scat",
|
"female:low scat",
|
||||||
"female:low smegma",
|
"female:low smegma",
|
||||||
@@ -369,6 +376,7 @@ object Female : TagList {
|
|||||||
"female:muscle growth",
|
"female:muscle growth",
|
||||||
"female:mute",
|
"female:mute",
|
||||||
"female:nakadashi",
|
"female:nakadashi",
|
||||||
|
"female:navel birth",
|
||||||
"female:navel fuck",
|
"female:navel fuck",
|
||||||
"female:nazi",
|
"female:nazi",
|
||||||
"female:necrophilia",
|
"female:necrophilia",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ object Group : TagList {
|
|||||||
"group:008",
|
"group:008",
|
||||||
"group:0123456789",
|
"group:0123456789",
|
||||||
"group:0909",
|
"group:0909",
|
||||||
|
"group:1 equals 1ziz",
|
||||||
"group:1 slash 0 kansokujo",
|
"group:1 slash 0 kansokujo",
|
||||||
"group:1-up",
|
"group:1-up",
|
||||||
"group:10 slash 19",
|
"group:10 slash 19",
|
||||||
@@ -336,7 +337,6 @@ object Group : TagList {
|
|||||||
"group:alice-do",
|
"group:alice-do",
|
||||||
"group:alice.blood",
|
"group:alice.blood",
|
||||||
"group:alicegarden",
|
"group:alicegarden",
|
||||||
"group:aliceholic",
|
|
||||||
"group:alicemirror",
|
"group:alicemirror",
|
||||||
"group:alices house",
|
"group:alices house",
|
||||||
"group:alicesoft",
|
"group:alicesoft",
|
||||||
@@ -359,6 +359,7 @@ object Group : TagList {
|
|||||||
"group:alz-hammer",
|
"group:alz-hammer",
|
||||||
"group:am colon tiger",
|
"group:am colon tiger",
|
||||||
"group:am400",
|
"group:am400",
|
||||||
|
"group:am644",
|
||||||
"group:amaama-tei",
|
"group:amaama-tei",
|
||||||
"group:amaembo",
|
"group:amaembo",
|
||||||
"group:amagasa cycle",
|
"group:amagasa cycle",
|
||||||
@@ -380,6 +381,7 @@ object Group : TagList {
|
|||||||
"group:amanogawa tsuushin",
|
"group:amanogawa tsuushin",
|
||||||
"group:amapoteya",
|
"group:amapoteya",
|
||||||
"group:amarini senpaku",
|
"group:amarini senpaku",
|
||||||
|
"group:amaterasu tsukikage",
|
||||||
"group:amatoro bow",
|
"group:amatoro bow",
|
||||||
"group:amatosui",
|
"group:amatosui",
|
||||||
"group:amatouenpitsukezuri",
|
"group:amatouenpitsukezuri",
|
||||||
@@ -539,6 +541,7 @@ object Group : TagList {
|
|||||||
"group:arkham products team ankoku baitai",
|
"group:arkham products team ankoku baitai",
|
||||||
"group:armadillo",
|
"group:armadillo",
|
||||||
"group:armanium",
|
"group:armanium",
|
||||||
|
"group:arnest room",
|
||||||
"group:aroimark",
|
"group:aroimark",
|
||||||
"group:aroma gaeru",
|
"group:aroma gaeru",
|
||||||
"group:arsenothelus",
|
"group:arsenothelus",
|
||||||
@@ -557,6 +560,7 @@ object Group : TagList {
|
|||||||
"group:arukomu",
|
"group:arukomu",
|
||||||
"group:aruku denpatou no kai",
|
"group:aruku denpatou no kai",
|
||||||
"group:arumike",
|
"group:arumike",
|
||||||
|
"group:arumirua",
|
||||||
"group:aruto-ya",
|
"group:aruto-ya",
|
||||||
"group:arysuivery",
|
"group:arysuivery",
|
||||||
"group:asa made go-ya",
|
"group:asa made go-ya",
|
||||||
@@ -612,6 +616,7 @@ object Group : TagList {
|
|||||||
"group:at mztm",
|
"group:at mztm",
|
||||||
"group:at no 464",
|
"group:at no 464",
|
||||||
"group:at oz",
|
"group:at oz",
|
||||||
|
"group:at szkn",
|
||||||
"group:atara shindou",
|
"group:atara shindou",
|
||||||
"group:ataraxia",
|
"group:ataraxia",
|
||||||
"group:atelier d",
|
"group:atelier d",
|
||||||
@@ -709,6 +714,7 @@ object Group : TagList {
|
|||||||
"group:bakuhatsu brs.",
|
"group:bakuhatsu brs.",
|
||||||
"group:bakunyu fullnerson",
|
"group:bakunyu fullnerson",
|
||||||
"group:bakuretsu fusen",
|
"group:bakuretsu fusen",
|
||||||
|
"group:bakushu koujou",
|
||||||
"group:bakusou special",
|
"group:bakusou special",
|
||||||
"group:balgus rec",
|
"group:balgus rec",
|
||||||
"group:balklash.",
|
"group:balklash.",
|
||||||
@@ -746,6 +752,7 @@ object Group : TagList {
|
|||||||
"group:bbb-extra",
|
"group:bbb-extra",
|
||||||
"group:bbuttondash",
|
"group:bbuttondash",
|
||||||
"group:beaf emotion",
|
"group:beaf emotion",
|
||||||
|
"group:bear valley",
|
||||||
"group:beart",
|
"group:beart",
|
||||||
"group:beat-pop",
|
"group:beat-pop",
|
||||||
"group:beauty salon b and s",
|
"group:beauty salon b and s",
|
||||||
@@ -764,6 +771,7 @@ object Group : TagList {
|
|||||||
"group:beni namazu dan",
|
"group:beni namazu dan",
|
||||||
"group:benichigaya",
|
"group:benichigaya",
|
||||||
"group:beniiro kaitenkikou",
|
"group:beniiro kaitenkikou",
|
||||||
|
"group:benimaru suisan",
|
||||||
"group:benimomo dou",
|
"group:benimomo dou",
|
||||||
"group:benisuzumedo",
|
"group:benisuzumedo",
|
||||||
"group:beniya",
|
"group:beniya",
|
||||||
@@ -804,6 +812,7 @@ object Group : TagList {
|
|||||||
"group:biroon jr.",
|
"group:biroon jr.",
|
||||||
"group:biruban",
|
"group:biruban",
|
||||||
"group:bisaid label",
|
"group:bisaid label",
|
||||||
|
"group:bishamon.",
|
||||||
"group:bishou neko",
|
"group:bishou neko",
|
||||||
"group:bishoujo production",
|
"group:bishoujo production",
|
||||||
"group:bisketty",
|
"group:bisketty",
|
||||||
@@ -1083,6 +1092,7 @@ object Group : TagList {
|
|||||||
"group:chimamire yashiki",
|
"group:chimamire yashiki",
|
||||||
"group:chimatsuriya honpo",
|
"group:chimatsuriya honpo",
|
||||||
"group:chimchimteam",
|
"group:chimchimteam",
|
||||||
|
"group:chimeishou",
|
||||||
"group:chimere marie",
|
"group:chimere marie",
|
||||||
"group:chin soft",
|
"group:chin soft",
|
||||||
"group:chinasanchi",
|
"group:chinasanchi",
|
||||||
@@ -1243,6 +1253,7 @@ object Group : TagList {
|
|||||||
"group:coonelius",
|
"group:coonelius",
|
||||||
"group:copo deluxe",
|
"group:copo deluxe",
|
||||||
"group:coppo-otome",
|
"group:coppo-otome",
|
||||||
|
"group:coscoteikoku",
|
||||||
"group:cosmic-3d-angels",
|
"group:cosmic-3d-angels",
|
||||||
"group:cosplay kissa nyan nyan",
|
"group:cosplay kissa nyan nyan",
|
||||||
"group:cosplaydeviants",
|
"group:cosplaydeviants",
|
||||||
@@ -1301,6 +1312,7 @@ object Group : TagList {
|
|||||||
"group:d-ten",
|
"group:d-ten",
|
||||||
"group:d.d.d.b.",
|
"group:d.d.d.b.",
|
||||||
"group:d.n.a.lab.",
|
"group:d.n.a.lab.",
|
||||||
|
"group:d.o.",
|
||||||
"group:d2",
|
"group:d2",
|
||||||
"group:da hootch",
|
"group:da hootch",
|
||||||
"group:da pomb no tokoro",
|
"group:da pomb no tokoro",
|
||||||
@@ -1413,6 +1425,7 @@ object Group : TagList {
|
|||||||
"group:deucesworld",
|
"group:deucesworld",
|
||||||
"group:dewdrop",
|
"group:dewdrop",
|
||||||
"group:dex plus",
|
"group:dex plus",
|
||||||
|
"group:dez climax",
|
||||||
"group:dhr-ken",
|
"group:dhr-ken",
|
||||||
"group:diablo",
|
"group:diablo",
|
||||||
"group:dicpic studio",
|
"group:dicpic studio",
|
||||||
@@ -1424,6 +1437,7 @@ object Group : TagList {
|
|||||||
"group:digital graffiti",
|
"group:digital graffiti",
|
||||||
"group:digital lover",
|
"group:digital lover",
|
||||||
"group:digital tambourine",
|
"group:digital tambourine",
|
||||||
|
"group:dingiruutoushi",
|
||||||
"group:diogenes club",
|
"group:diogenes club",
|
||||||
"group:dioxin",
|
"group:dioxin",
|
||||||
"group:dirty",
|
"group:dirty",
|
||||||
@@ -1445,12 +1459,14 @@ object Group : TagList {
|
|||||||
"group:doing crew",
|
"group:doing crew",
|
||||||
"group:doisakaken",
|
"group:doisakaken",
|
||||||
"group:dojin otome",
|
"group:dojin otome",
|
||||||
|
"group:dojiro books",
|
||||||
"group:dokkoi-tori gomoku",
|
"group:dokkoi-tori gomoku",
|
||||||
"group:doku alice",
|
"group:doku alice",
|
||||||
"group:doku doku kinoko",
|
"group:doku doku kinoko",
|
||||||
"group:doku pepper",
|
"group:doku pepper",
|
||||||
"group:doku usagi tai",
|
"group:doku usagi tai",
|
||||||
"group:dokudenpa jushintei",
|
"group:dokudenpa jushintei",
|
||||||
|
"group:dokudoku ryouki garou",
|
||||||
"group:dokugiri",
|
"group:dokugiri",
|
||||||
"group:dokukinokosha",
|
"group:dokukinokosha",
|
||||||
"group:dokupan koubou",
|
"group:dokupan koubou",
|
||||||
@@ -1461,11 +1477,13 @@ object Group : TagList {
|
|||||||
"group:donaora",
|
"group:donaora",
|
||||||
"group:donburi beya",
|
"group:donburi beya",
|
||||||
"group:dondondon",
|
"group:dondondon",
|
||||||
|
"group:dongurineko",
|
||||||
"group:dont understand",
|
"group:dont understand",
|
||||||
"group:doomcomic",
|
"group:doomcomic",
|
||||||
"group:dopyunger oukoku",
|
"group:dopyunger oukoku",
|
||||||
"group:dorepooru",
|
"group:dorepooru",
|
||||||
"group:doro-coppelia",
|
"group:doro-coppelia",
|
||||||
|
"group:dorokuma kumaya",
|
||||||
"group:doronuma bunshitsu",
|
"group:doronuma bunshitsu",
|
||||||
"group:doronuma kyoudai",
|
"group:doronuma kyoudai",
|
||||||
"group:doropanda tours",
|
"group:doropanda tours",
|
||||||
@@ -1489,8 +1507,10 @@ object Group : TagList {
|
|||||||
"group:doujin mukashibanashi",
|
"group:doujin mukashibanashi",
|
||||||
"group:douke romance",
|
"group:douke romance",
|
||||||
"group:doumo sumimasen",
|
"group:doumo sumimasen",
|
||||||
|
"group:dounimo naranai nou",
|
||||||
"group:dourakuya honpo",
|
"group:dourakuya honpo",
|
||||||
"group:doushin chaya",
|
"group:doushin chaya",
|
||||||
|
"group:doushoku",
|
||||||
"group:douwa-kensetsu",
|
"group:douwa-kensetsu",
|
||||||
"group:doyondo.",
|
"group:doyondo.",
|
||||||
"group:dr.vermilion",
|
"group:dr.vermilion",
|
||||||
@@ -1533,6 +1553,7 @@ object Group : TagList {
|
|||||||
"group:earthlyparadise",
|
"group:earthlyparadise",
|
||||||
"group:easymode",
|
"group:easymode",
|
||||||
"group:ebimayo",
|
"group:ebimayo",
|
||||||
|
"group:ebiten kaido",
|
||||||
"group:ecchi na taikendan kokuhaku toukou otoko jyuku",
|
"group:ecchi na taikendan kokuhaku toukou otoko jyuku",
|
||||||
"group:ecchuu douga honpo",
|
"group:ecchuu douga honpo",
|
||||||
"group:eclair ringo tea",
|
"group:eclair ringo tea",
|
||||||
@@ -1622,6 +1643,7 @@ object Group : TagList {
|
|||||||
"group:etopi kan",
|
"group:etopi kan",
|
||||||
"group:eucalyptus house",
|
"group:eucalyptus house",
|
||||||
"group:euereuphorie",
|
"group:euereuphorie",
|
||||||
|
"group:euglena factory",
|
||||||
"group:eunospress",
|
"group:eunospress",
|
||||||
"group:everyday milk challenge",
|
"group:everyday milk challenge",
|
||||||
"group:evil aratame baroque store",
|
"group:evil aratame baroque store",
|
||||||
@@ -1647,6 +1669,7 @@ object Group : TagList {
|
|||||||
"group:fakereal",
|
"group:fakereal",
|
||||||
"group:fakers manual",
|
"group:fakers manual",
|
||||||
"group:fakestar",
|
"group:fakestar",
|
||||||
|
"group:falcon115",
|
||||||
"group:fallinmoon",
|
"group:fallinmoon",
|
||||||
"group:famous comics",
|
"group:famous comics",
|
||||||
"group:famous toons facial",
|
"group:famous toons facial",
|
||||||
@@ -1730,6 +1753,7 @@ object Group : TagList {
|
|||||||
"group:frill frill",
|
"group:frill frill",
|
||||||
"group:frontwing",
|
"group:frontwing",
|
||||||
"group:fruitsjam",
|
"group:fruitsjam",
|
||||||
|
"group:fu rairyuu",
|
||||||
"group:fuantei",
|
"group:fuantei",
|
||||||
"group:fudeoki seisakujo",
|
"group:fudeoki seisakujo",
|
||||||
"group:fuegerstef",
|
"group:fuegerstef",
|
||||||
@@ -1778,6 +1802,7 @@ object Group : TagList {
|
|||||||
"group:fururi.",
|
"group:fururi.",
|
||||||
"group:furuya",
|
"group:furuya",
|
||||||
"group:fuseimyaku",
|
"group:fuseimyaku",
|
||||||
|
"group:fusha fusha kingdom",
|
||||||
"group:fushichou no yoake",
|
"group:fushichou no yoake",
|
||||||
"group:fushinsya guilty",
|
"group:fushinsya guilty",
|
||||||
"group:fushizen doubutsu hogodantai",
|
"group:fushizen doubutsu hogodantai",
|
||||||
@@ -1829,10 +1854,12 @@ object Group : TagList {
|
|||||||
"group:gamera 8th army",
|
"group:gamera 8th army",
|
||||||
"group:gamma menia",
|
"group:gamma menia",
|
||||||
"group:gammaedge",
|
"group:gammaedge",
|
||||||
|
"group:ganbaru dou",
|
||||||
"group:gang bang comix",
|
"group:gang bang comix",
|
||||||
"group:ganmo-no-oyatsu",
|
"group:ganmo-no-oyatsu",
|
||||||
"group:ganryuu island",
|
"group:ganryuu island",
|
||||||
"group:ganso sonodaya",
|
"group:ganso sonodaya",
|
||||||
|
"group:gaoookyouryu",
|
||||||
"group:gara ayuri nisshi",
|
"group:gara ayuri nisshi",
|
||||||
"group:garage-talk",
|
"group:garage-talk",
|
||||||
"group:garahadoh",
|
"group:garahadoh",
|
||||||
@@ -1849,6 +1876,7 @@ object Group : TagList {
|
|||||||
"group:gas ketsu jinsei",
|
"group:gas ketsu jinsei",
|
||||||
"group:gasshuukoku netamekoru",
|
"group:gasshuukoku netamekoru",
|
||||||
"group:gate of xiii",
|
"group:gate of xiii",
|
||||||
|
"group:gatekeeper",
|
||||||
"group:gaton.",
|
"group:gaton.",
|
||||||
"group:gavial no sumika",
|
"group:gavial no sumika",
|
||||||
"group:gd-mechano",
|
"group:gd-mechano",
|
||||||
@@ -1870,12 +1898,14 @@ object Group : TagList {
|
|||||||
"group:general bacchus",
|
"group:general bacchus",
|
||||||
"group:genesys",
|
"group:genesys",
|
||||||
"group:genki no mizu no wakutokoro",
|
"group:genki no mizu no wakutokoro",
|
||||||
|
"group:genkin-dou souhonpo",
|
||||||
"group:genmaiya",
|
"group:genmaiya",
|
||||||
"group:genocidekiss",
|
"group:genocidekiss",
|
||||||
"group:gensancha",
|
"group:gensancha",
|
||||||
"group:gensou graphics",
|
"group:gensou graphics",
|
||||||
"group:gensou kuukan",
|
"group:gensou kuukan",
|
||||||
"group:gensou stlavus",
|
"group:gensou stlavus",
|
||||||
|
"group:gensou yakai",
|
||||||
"group:gensyokuhakoniwa",
|
"group:gensyokuhakoniwa",
|
||||||
"group:genwakukinema",
|
"group:genwakukinema",
|
||||||
"group:geregere negro",
|
"group:geregere negro",
|
||||||
@@ -1897,12 +1927,14 @@ object Group : TagList {
|
|||||||
"group:giantessfan",
|
"group:giantessfan",
|
||||||
"group:giftbell",
|
"group:giftbell",
|
||||||
"group:giftkuchen",
|
"group:giftkuchen",
|
||||||
|
"group:giga omaru",
|
||||||
"group:gigameka",
|
"group:gigameka",
|
||||||
"group:gikogakodo",
|
"group:gikogakodo",
|
||||||
"group:gin eiji",
|
"group:gin eiji",
|
||||||
"group:gin no hoshitei",
|
"group:gin no hoshitei",
|
||||||
"group:gin penguin",
|
"group:gin penguin",
|
||||||
"group:gin-ion",
|
"group:gin-ion",
|
||||||
|
"group:ginga no arakuremon",
|
||||||
"group:ginga no himitu kichi",
|
"group:ginga no himitu kichi",
|
||||||
"group:ginga-ryusei",
|
"group:ginga-ryusei",
|
||||||
"group:giniro noel",
|
"group:giniro noel",
|
||||||
@@ -1960,6 +1992,7 @@ object Group : TagList {
|
|||||||
"group:gouhouwakan",
|
"group:gouhouwakan",
|
||||||
"group:gouriki hyakkaten",
|
"group:gouriki hyakkaten",
|
||||||
"group:gouten doujou",
|
"group:gouten doujou",
|
||||||
|
"group:gozen 4-ji one call",
|
||||||
"group:gozen niji no ushigaeru",
|
"group:gozen niji no ushigaeru",
|
||||||
"group:gpen",
|
"group:gpen",
|
||||||
"group:gpx",
|
"group:gpx",
|
||||||
@@ -1969,6 +2002,9 @@ object Group : TagList {
|
|||||||
"group:gravidan",
|
"group:gravidan",
|
||||||
"group:great acta",
|
"group:great acta",
|
||||||
"group:great canyon",
|
"group:great canyon",
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getTags2(): List<String> = listOf(
|
||||||
"group:great dadan",
|
"group:great dadan",
|
||||||
"group:greatest18club",
|
"group:greatest18club",
|
||||||
"group:greatmanjuu",
|
"group:greatmanjuu",
|
||||||
@@ -2002,9 +2038,6 @@ object Group : TagList {
|
|||||||
"group:gyara cter",
|
"group:gyara cter",
|
||||||
"group:gyaran rose",
|
"group:gyaran rose",
|
||||||
"group:gyarandou",
|
"group:gyarandou",
|
||||||
)
|
|
||||||
|
|
||||||
override fun getTags2(): List<String> = listOf(
|
|
||||||
"group:gyogyou rengou",
|
"group:gyogyou rengou",
|
||||||
"group:gyokasuisin",
|
"group:gyokasuisin",
|
||||||
"group:gyokotsu kouzou",
|
"group:gyokotsu kouzou",
|
||||||
@@ -2018,6 +2051,7 @@ object Group : TagList {
|
|||||||
"group:h na hon. ya san.",
|
"group:h na hon. ya san.",
|
||||||
"group:h plus",
|
"group:h plus",
|
||||||
"group:h senshokutai",
|
"group:h senshokutai",
|
||||||
|
"group:h sparkle",
|
||||||
"group:h-m",
|
"group:h-m",
|
||||||
"group:h-na-ojisan",
|
"group:h-na-ojisan",
|
||||||
"group:h-sys.",
|
"group:h-sys.",
|
||||||
@@ -2208,6 +2242,7 @@ object Group : TagList {
|
|||||||
"group:hellfragrance",
|
"group:hellfragrance",
|
||||||
"group:hellter skelter",
|
"group:hellter skelter",
|
||||||
"group:helmet ga naosemasen",
|
"group:helmet ga naosemasen",
|
||||||
|
"group:henachoko-domei",
|
||||||
"group:hengen monogatari",
|
"group:hengen monogatari",
|
||||||
"group:henntai-shinshi",
|
"group:henntai-shinshi",
|
||||||
"group:henreikai",
|
"group:henreikai",
|
||||||
@@ -2223,6 +2258,7 @@ object Group : TagList {
|
|||||||
"group:hero hero tei",
|
"group:hero hero tei",
|
||||||
"group:hero oukoku",
|
"group:hero oukoku",
|
||||||
"group:heroes factory",
|
"group:heroes factory",
|
||||||
|
"group:herunian zokusei",
|
||||||
"group:heshi factory",
|
"group:heshi factory",
|
||||||
"group:heta no yoko zuki",
|
"group:heta no yoko zuki",
|
||||||
"group:hetalearts",
|
"group:hetalearts",
|
||||||
@@ -2250,6 +2286,7 @@ object Group : TagList {
|
|||||||
"group:high heel syndrome",
|
"group:high heel syndrome",
|
||||||
"group:high risk revolution",
|
"group:high risk revolution",
|
||||||
"group:high-octane",
|
"group:high-octane",
|
||||||
|
"group:high-rised fossil garden",
|
||||||
"group:high-soft",
|
"group:high-soft",
|
||||||
"group:high-spirit",
|
"group:high-spirit",
|
||||||
"group:highway-senmu",
|
"group:highway-senmu",
|
||||||
@@ -2315,7 +2352,7 @@ object Group : TagList {
|
|||||||
"group:hitsuji-1ban-shibori",
|
"group:hitsuji-1ban-shibori",
|
||||||
"group:hitsujin toko",
|
"group:hitsujin toko",
|
||||||
"group:hitujinoki",
|
"group:hitujinoki",
|
||||||
"group:hiyashi chuuka hajimemashita",
|
"group:hiyashi chuuka owarimashita",
|
||||||
"group:hiyoko no gekijoh",
|
"group:hiyoko no gekijoh",
|
||||||
"group:hiyosanchi",
|
"group:hiyosanchi",
|
||||||
"group:hizadati zekkouchou",
|
"group:hizadati zekkouchou",
|
||||||
@@ -2331,6 +2368,7 @@ object Group : TagList {
|
|||||||
"group:hokoushayou shingou",
|
"group:hokoushayou shingou",
|
||||||
"group:hokuroza",
|
"group:hokuroza",
|
||||||
"group:holiday school",
|
"group:holiday school",
|
||||||
|
"group:holy up",
|
||||||
"group:home not found",
|
"group:home not found",
|
||||||
"group:homepie koubou",
|
"group:homepie koubou",
|
||||||
"group:homerun chaya",
|
"group:homerun chaya",
|
||||||
@@ -2348,6 +2386,7 @@ object Group : TagList {
|
|||||||
"group:honeypie",
|
"group:honeypie",
|
||||||
"group:hong kong dou",
|
"group:hong kong dou",
|
||||||
"group:honnokimochiya",
|
"group:honnokimochiya",
|
||||||
|
"group:honpo kes",
|
||||||
"group:hontoinu",
|
"group:hontoinu",
|
||||||
"group:hook",
|
"group:hook",
|
||||||
"group:hooliganism",
|
"group:hooliganism",
|
||||||
@@ -2379,6 +2418,7 @@ object Group : TagList {
|
|||||||
"group:hotori bocchi",
|
"group:hotori bocchi",
|
||||||
"group:hotpink",
|
"group:hotpink",
|
||||||
"group:hougakuya",
|
"group:hougakuya",
|
||||||
|
"group:houjou-kun mania",
|
||||||
"group:houkago inokorigumi",
|
"group:houkago inokorigumi",
|
||||||
"group:houkago paradise",
|
"group:houkago paradise",
|
||||||
"group:houkaiseki.",
|
"group:houkaiseki.",
|
||||||
@@ -2424,6 +2464,7 @@ object Group : TagList {
|
|||||||
"group:ice to choco",
|
"group:ice to choco",
|
||||||
"group:ice-place",
|
"group:ice-place",
|
||||||
"group:ichachi",
|
"group:ichachi",
|
||||||
|
"group:ichi dollar kouka",
|
||||||
"group:ichi-kan",
|
"group:ichi-kan",
|
||||||
"group:ichigiteishi",
|
"group:ichigiteishi",
|
||||||
"group:ichigo maririn",
|
"group:ichigo maririn",
|
||||||
@@ -2507,6 +2548,7 @@ object Group : TagList {
|
|||||||
"group:intermikan",
|
"group:intermikan",
|
||||||
"group:interracial-comics",
|
"group:interracial-comics",
|
||||||
"group:intoku.info",
|
"group:intoku.info",
|
||||||
|
"group:inuchan equals land",
|
||||||
"group:inudrill.",
|
"group:inudrill.",
|
||||||
"group:inukamedou",
|
"group:inukamedou",
|
||||||
"group:inukichi club",
|
"group:inukichi club",
|
||||||
@@ -2587,12 +2629,15 @@ object Group : TagList {
|
|||||||
"group:jet-black baselarde",
|
"group:jet-black baselarde",
|
||||||
"group:jewel box",
|
"group:jewel box",
|
||||||
"group:jhk",
|
"group:jhk",
|
||||||
|
"group:jibaku mecha",
|
||||||
"group:jibaku-system",
|
"group:jibaku-system",
|
||||||
"group:jidaraku risutorante",
|
"group:jidaraku risutorante",
|
||||||
"group:jido-hikki",
|
"group:jido-hikki",
|
||||||
"group:jigen no wataridori",
|
"group:jigen no wataridori",
|
||||||
"group:jiggly girls",
|
"group:jiggly girls",
|
||||||
"group:jigizagi",
|
"group:jigizagi",
|
||||||
|
"group:jigoku no cakeya-san",
|
||||||
|
"group:jigoku no monban",
|
||||||
"group:jigoku potion",
|
"group:jigoku potion",
|
||||||
"group:jigyaku jihen",
|
"group:jigyaku jihen",
|
||||||
"group:jikan-ya",
|
"group:jikan-ya",
|
||||||
@@ -2697,6 +2742,7 @@ object Group : TagList {
|
|||||||
"group:kairoudou",
|
"group:kairoudou",
|
||||||
"group:kairyuu",
|
"group:kairyuu",
|
||||||
"group:kaisanbou",
|
"group:kaisanbou",
|
||||||
|
"group:kaitaiya",
|
||||||
"group:kaitatuku",
|
"group:kaitatuku",
|
||||||
"group:kaiteisinden",
|
"group:kaiteisinden",
|
||||||
"group:kaiten sommelier",
|
"group:kaiten sommelier",
|
||||||
@@ -2824,6 +2870,7 @@ object Group : TagList {
|
|||||||
"group:keiyou tsudanuma juku",
|
"group:keiyou tsudanuma juku",
|
||||||
"group:kemao coopercent",
|
"group:kemao coopercent",
|
||||||
"group:kemokomoya",
|
"group:kemokomoya",
|
||||||
|
"group:kemomimi-chan ya",
|
||||||
"group:kemominnosuke",
|
"group:kemominnosuke",
|
||||||
"group:kemono ekaki no kousoku 2",
|
"group:kemono ekaki no kousoku 2",
|
||||||
"group:kemono masshigura.",
|
"group:kemono masshigura.",
|
||||||
@@ -2849,6 +2896,7 @@ object Group : TagList {
|
|||||||
"group:khaos distance",
|
"group:khaos distance",
|
||||||
"group:khaos wind",
|
"group:khaos wind",
|
||||||
"group:kharisma jati",
|
"group:kharisma jati",
|
||||||
|
"group:khpn style",
|
||||||
"group:kibawomuku",
|
"group:kibawomuku",
|
||||||
"group:kichiku bansankai",
|
"group:kichiku bansankai",
|
||||||
"group:kichiku koubou",
|
"group:kichiku koubou",
|
||||||
@@ -2959,6 +3007,7 @@ object Group : TagList {
|
|||||||
"group:koge croquette",
|
"group:koge croquette",
|
||||||
"group:kogemaru tsuushinkyoku",
|
"group:kogemaru tsuushinkyoku",
|
||||||
"group:kogitune",
|
"group:kogitune",
|
||||||
|
"group:koh no atelier",
|
||||||
"group:kohagura.",
|
"group:kohagura.",
|
||||||
"group:kohau no heya",
|
"group:kohau no heya",
|
||||||
"group:kohitsujitei",
|
"group:kohitsujitei",
|
||||||
@@ -3010,6 +3059,7 @@ object Group : TagList {
|
|||||||
"group:komorikiri.",
|
"group:komorikiri.",
|
||||||
"group:komugiko 100 percent",
|
"group:komugiko 100 percent",
|
||||||
"group:kon no pencase",
|
"group:kon no pencase",
|
||||||
|
"group:konekoconnection",
|
||||||
"group:konekopan",
|
"group:konekopan",
|
||||||
"group:konekopunch",
|
"group:konekopunch",
|
||||||
"group:kongou rikisi",
|
"group:kongou rikisi",
|
||||||
@@ -3057,6 +3107,7 @@ object Group : TagList {
|
|||||||
"group:kousaien",
|
"group:kousaien",
|
||||||
"group:kousoku gurihari-tei",
|
"group:kousoku gurihari-tei",
|
||||||
"group:kousoku purin",
|
"group:kousoku purin",
|
||||||
|
"group:koutatsu dennou koushi",
|
||||||
"group:kouzaka-san to makino jimusho",
|
"group:kouzaka-san to makino jimusho",
|
||||||
"group:kouzu shoukai",
|
"group:kouzu shoukai",
|
||||||
"group:kouzuya",
|
"group:kouzuya",
|
||||||
@@ -3087,12 +3138,14 @@ object Group : TagList {
|
|||||||
"group:kumo to koumori",
|
"group:kumo to koumori",
|
||||||
"group:kumohitode of world",
|
"group:kumohitode of world",
|
||||||
"group:kumonosu",
|
"group:kumonosu",
|
||||||
|
"group:kuni gamma",
|
||||||
"group:kunkakunka teikoku",
|
"group:kunkakunka teikoku",
|
||||||
"group:kunon",
|
"group:kunon",
|
||||||
"group:kurage kyoudai",
|
"group:kurage kyoudai",
|
||||||
"group:kurahashi shoin",
|
"group:kurahashi shoin",
|
||||||
"group:kurai mori no soko de",
|
"group:kurai mori no soko de",
|
||||||
"group:kurakura-honey",
|
"group:kurakura-honey",
|
||||||
|
"group:kurasan",
|
||||||
"group:kuraudo.",
|
"group:kuraudo.",
|
||||||
"group:kureboti ufo",
|
"group:kureboti ufo",
|
||||||
"group:kurige wagyuu",
|
"group:kurige wagyuu",
|
||||||
@@ -3162,6 +3215,7 @@ object Group : TagList {
|
|||||||
"group:kuukiisu",
|
"group:kuukiisu",
|
||||||
"group:kuuronziyou",
|
"group:kuuronziyou",
|
||||||
"group:kuusou idol labo bellberry",
|
"group:kuusou idol labo bellberry",
|
||||||
|
"group:kuusou kouko gakkai",
|
||||||
"group:kuusou switch",
|
"group:kuusou switch",
|
||||||
"group:kuzuryuu",
|
"group:kuzuryuu",
|
||||||
"group:kyakuniku kanzume",
|
"group:kyakuniku kanzume",
|
||||||
@@ -3178,6 +3232,7 @@ object Group : TagList {
|
|||||||
"group:kyouken diners",
|
"group:kyouken diners",
|
||||||
"group:kyouki na shiunten",
|
"group:kyouki na shiunten",
|
||||||
"group:kyoumo spaghe",
|
"group:kyoumo spaghe",
|
||||||
|
"group:kyourakuen",
|
||||||
"group:kyoushuugata",
|
"group:kyoushuugata",
|
||||||
"group:kyuu no mon",
|
"group:kyuu no mon",
|
||||||
"group:kyuu tekki jidai",
|
"group:kyuu tekki jidai",
|
||||||
@@ -3246,6 +3301,7 @@ object Group : TagList {
|
|||||||
"group:lily heart",
|
"group:lily heart",
|
||||||
"group:lily lily rose",
|
"group:lily lily rose",
|
||||||
"group:lily-put",
|
"group:lily-put",
|
||||||
|
"group:lime green",
|
||||||
"group:limit break",
|
"group:limit break",
|
||||||
"group:limit max",
|
"group:limit max",
|
||||||
"group:limit over",
|
"group:limit over",
|
||||||
@@ -3260,6 +3316,7 @@ object Group : TagList {
|
|||||||
"group:little mantis",
|
"group:little mantis",
|
||||||
"group:little princess",
|
"group:little princess",
|
||||||
"group:littlehopper",
|
"group:littlehopper",
|
||||||
|
"group:littlepool.",
|
||||||
"group:littletail",
|
"group:littletail",
|
||||||
"group:live house",
|
"group:live house",
|
||||||
"group:lo likyo new",
|
"group:lo likyo new",
|
||||||
@@ -3274,6 +3331,7 @@ object Group : TagList {
|
|||||||
"group:lolipop complete",
|
"group:lolipop complete",
|
||||||
"group:lolitachannel",
|
"group:lolitachannel",
|
||||||
"group:longhorntrain",
|
"group:longhorntrain",
|
||||||
|
"group:longlong de cangku",
|
||||||
"group:looptheloop",
|
"group:looptheloop",
|
||||||
"group:lopet dan",
|
"group:lopet dan",
|
||||||
"group:lostscript",
|
"group:lostscript",
|
||||||
@@ -3284,6 +3342,7 @@ object Group : TagList {
|
|||||||
"group:love kitten",
|
"group:love kitten",
|
||||||
"group:love kyun maiden",
|
"group:love kyun maiden",
|
||||||
"group:love lily",
|
"group:love lily",
|
||||||
|
"group:love love craft",
|
||||||
"group:love nyanko",
|
"group:love nyanko",
|
||||||
"group:love scythe",
|
"group:love scythe",
|
||||||
"group:love shine",
|
"group:love shine",
|
||||||
@@ -3325,6 +3384,7 @@ object Group : TagList {
|
|||||||
"group:m kichibeya",
|
"group:m kichibeya",
|
||||||
"group:m plus dilore",
|
"group:m plus dilore",
|
||||||
"group:m slash k club",
|
"group:m slash k club",
|
||||||
|
"group:m-family",
|
||||||
"group:m-i-p",
|
"group:m-i-p",
|
||||||
"group:m-keifu",
|
"group:m-keifu",
|
||||||
"group:m-koujou",
|
"group:m-koujou",
|
||||||
@@ -3364,6 +3424,7 @@ object Group : TagList {
|
|||||||
"group:magono-tei",
|
"group:magono-tei",
|
||||||
"group:magudara kaihou doumei",
|
"group:magudara kaihou doumei",
|
||||||
"group:maguro bokujo",
|
"group:maguro bokujo",
|
||||||
|
"group:maguro fiction",
|
||||||
"group:maguro koubou",
|
"group:maguro koubou",
|
||||||
"group:mahiru no tsuki",
|
"group:mahiru no tsuki",
|
||||||
"group:mahiru nosora",
|
"group:mahiru nosora",
|
||||||
@@ -3417,9 +3478,11 @@ object Group : TagList {
|
|||||||
"group:manjuu x",
|
"group:manjuu x",
|
||||||
"group:manles laboratory",
|
"group:manles laboratory",
|
||||||
"group:manshin soui",
|
"group:manshin soui",
|
||||||
|
"group:mantohihi atoz",
|
||||||
"group:many menu",
|
"group:many menu",
|
||||||
"group:maokonzu",
|
"group:maokonzu",
|
||||||
"group:maple-go",
|
"group:maple-go",
|
||||||
|
"group:mappa namatta",
|
||||||
"group:mara apocalypse",
|
"group:mara apocalypse",
|
||||||
"group:marashion",
|
"group:marashion",
|
||||||
"group:marble kid",
|
"group:marble kid",
|
||||||
@@ -3431,6 +3494,7 @@ object Group : TagList {
|
|||||||
"group:marge-loop",
|
"group:marge-loop",
|
||||||
"group:maria system00",
|
"group:maria system00",
|
||||||
"group:mariana kaikou kikaku",
|
"group:mariana kaikou kikaku",
|
||||||
|
"group:marimo-ya",
|
||||||
"group:marinesapphire",
|
"group:marinesapphire",
|
||||||
"group:marionette soukou ryouhei",
|
"group:marionette soukou ryouhei",
|
||||||
"group:marireimari inochi",
|
"group:marireimari inochi",
|
||||||
@@ -3651,6 +3715,7 @@ object Group : TagList {
|
|||||||
"group:misaki shoujokei.",
|
"group:misaki shoujokei.",
|
||||||
"group:misakix megamix",
|
"group:misakix megamix",
|
||||||
"group:misin koujou",
|
"group:misin koujou",
|
||||||
|
"group:misobolo dou",
|
||||||
"group:misonodenpatou",
|
"group:misonodenpatou",
|
||||||
"group:misoyahonpo",
|
"group:misoyahonpo",
|
||||||
"group:misssail",
|
"group:misssail",
|
||||||
@@ -3697,6 +3762,7 @@ object Group : TagList {
|
|||||||
"group:mocha plus ccc",
|
"group:mocha plus ccc",
|
||||||
"group:mocha2popcorn",
|
"group:mocha2popcorn",
|
||||||
"group:mochi hasamiuchi da",
|
"group:mochi hasamiuchi da",
|
||||||
|
"group:mochi mochi bomb",
|
||||||
"group:mochi usagi",
|
"group:mochi usagi",
|
||||||
"group:mochi-ya",
|
"group:mochi-ya",
|
||||||
"group:mochimoonya",
|
"group:mochimoonya",
|
||||||
@@ -3716,8 +3782,10 @@ object Group : TagList {
|
|||||||
"group:mogura-dou",
|
"group:mogura-dou",
|
||||||
"group:mogyutto cheesecake",
|
"group:mogyutto cheesecake",
|
||||||
"group:mojibone",
|
"group:mojibone",
|
||||||
|
"group:mokkindo",
|
||||||
"group:mokkorihan",
|
"group:mokkorihan",
|
||||||
"group:mokkoubondobu",
|
"group:mokkoubondobu",
|
||||||
|
"group:mokkuafunfun",
|
||||||
"group:mokomaru suisan",
|
"group:mokomaru suisan",
|
||||||
"group:mokugyuutan",
|
"group:mokugyuutan",
|
||||||
"group:mokusa",
|
"group:mokusa",
|
||||||
@@ -3738,6 +3806,7 @@ object Group : TagList {
|
|||||||
"group:momoiro funenmono",
|
"group:momoiro funenmono",
|
||||||
"group:momoiro mimic",
|
"group:momoiro mimic",
|
||||||
"group:momoiro tanzaku",
|
"group:momoiro tanzaku",
|
||||||
|
"group:momoiro zundoko",
|
||||||
"group:momoiro-gekijyou",
|
"group:momoiro-gekijyou",
|
||||||
"group:momoiro-rip",
|
"group:momoiro-rip",
|
||||||
"group:momokamasu",
|
"group:momokamasu",
|
||||||
@@ -3787,8 +3856,8 @@ object Group : TagList {
|
|||||||
"group:morning star",
|
"group:morning star",
|
||||||
"group:morning tea.",
|
"group:morning tea.",
|
||||||
"group:morningmoon merchandising products",
|
"group:morningmoon merchandising products",
|
||||||
"group:morohei-ya",
|
|
||||||
"group:moroheiya break",
|
"group:moroheiya break",
|
||||||
|
"group:moroheiya no agata",
|
||||||
"group:morokochiffon cake",
|
"group:morokochiffon cake",
|
||||||
"group:morokosheet",
|
"group:morokosheet",
|
||||||
"group:moromi-ya",
|
"group:moromi-ya",
|
||||||
@@ -3802,6 +3871,7 @@ object Group : TagList {
|
|||||||
"group:mosoya",
|
"group:mosoya",
|
||||||
"group:mosquitone.",
|
"group:mosquitone.",
|
||||||
"group:motchie kingdom",
|
"group:motchie kingdom",
|
||||||
|
"group:motemote life",
|
||||||
"group:mothman",
|
"group:mothman",
|
||||||
"group:mou sukoshi hidari e",
|
"group:mou sukoshi hidari e",
|
||||||
"group:mouko mouretsu hasai dan",
|
"group:mouko mouretsu hasai dan",
|
||||||
@@ -3864,6 +3934,7 @@ object Group : TagList {
|
|||||||
"group:mukokoro no kumo",
|
"group:mukokoro no kumo",
|
||||||
"group:mukousharan",
|
"group:mukousharan",
|
||||||
"group:mukuchi na hakoniwa",
|
"group:mukuchi na hakoniwa",
|
||||||
|
"group:mukyou no utopia",
|
||||||
"group:mukyuu dynamic",
|
"group:mukyuu dynamic",
|
||||||
"group:mulberry",
|
"group:mulberry",
|
||||||
"group:multi media studio l.o.e.r.",
|
"group:multi media studio l.o.e.r.",
|
||||||
@@ -3934,6 +4005,9 @@ object Group : TagList {
|
|||||||
"group:nagomi-chaya",
|
"group:nagomi-chaya",
|
||||||
"group:nagomisui",
|
"group:nagomisui",
|
||||||
"group:nagomiyasan",
|
"group:nagomiyasan",
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getTags3(): List<String> = listOf(
|
||||||
"group:nagucha.",
|
"group:nagucha.",
|
||||||
"group:nagumo curry-bu",
|
"group:nagumo curry-bu",
|
||||||
"group:nagumoya",
|
"group:nagumoya",
|
||||||
@@ -3957,9 +4031,11 @@ object Group : TagList {
|
|||||||
"group:namahage-dou",
|
"group:namahage-dou",
|
||||||
"group:namahamu sando",
|
"group:namahamu sando",
|
||||||
"group:namaikichibi",
|
"group:namaikichibi",
|
||||||
|
"group:namaitati teishoku",
|
||||||
"group:namakemono sou",
|
"group:namakemono sou",
|
||||||
"group:namakura dou",
|
"group:namakura dou",
|
||||||
"group:namanecotei",
|
"group:namanecotei",
|
||||||
|
"group:namaniku aikoukai",
|
||||||
"group:namasute koubou",
|
"group:namasute koubou",
|
||||||
"group:namazuchaya",
|
"group:namazuchaya",
|
||||||
"group:nameco-soup",
|
"group:nameco-soup",
|
||||||
@@ -4005,9 +4081,6 @@ object Group : TagList {
|
|||||||
"group:nanto wachou ken",
|
"group:nanto wachou ken",
|
||||||
"group:nantoka ikitemasu",
|
"group:nantoka ikitemasu",
|
||||||
"group:nantoka suroun",
|
"group:nantoka suroun",
|
||||||
)
|
|
||||||
|
|
||||||
override fun getTags3(): List<String> = listOf(
|
|
||||||
"group:nappy",
|
"group:nappy",
|
||||||
"group:naraduke biyori",
|
"group:naraduke biyori",
|
||||||
"group:naragyogyo kumiai",
|
"group:naragyogyo kumiai",
|
||||||
@@ -4018,6 +4091,7 @@ object Group : TagList {
|
|||||||
"group:nasi-pasuya",
|
"group:nasi-pasuya",
|
||||||
"group:nasu no mono",
|
"group:nasu no mono",
|
||||||
"group:nasuan",
|
"group:nasuan",
|
||||||
|
"group:natadecoco company",
|
||||||
"group:natakuga-yuku",
|
"group:natakuga-yuku",
|
||||||
"group:natorina dou",
|
"group:natorina dou",
|
||||||
"group:natrinium",
|
"group:natrinium",
|
||||||
@@ -4120,8 +4194,10 @@ object Group : TagList {
|
|||||||
"group:nendo ningyo",
|
"group:nendo ningyo",
|
||||||
"group:nengaranenjuu",
|
"group:nengaranenjuu",
|
||||||
"group:neo maiden",
|
"group:neo maiden",
|
||||||
|
"group:neoniro",
|
||||||
"group:neruneru",
|
"group:neruneru",
|
||||||
"group:netemo sametemo",
|
"group:netemo sametemo",
|
||||||
|
"group:netorareru tamashigi no hitoshizuku",
|
||||||
"group:netsuzukeru ishiryoku",
|
"group:netsuzukeru ishiryoku",
|
||||||
"group:nettaigyo club",
|
"group:nettaigyo club",
|
||||||
"group:neutron city",
|
"group:neutron city",
|
||||||
@@ -4152,6 +4228,7 @@ object Group : TagList {
|
|||||||
"group:nighthawk",
|
"group:nighthawk",
|
||||||
"group:nightmare software",
|
"group:nightmare software",
|
||||||
"group:nigimitama no ya",
|
"group:nigimitama no ya",
|
||||||
|
"group:nihao series",
|
||||||
"group:nihatsu shika ataranai",
|
"group:nihatsu shika ataranai",
|
||||||
"group:nihon dandy",
|
"group:nihon dandy",
|
||||||
"group:nihon denga senmon gakkou",
|
"group:nihon denga senmon gakkou",
|
||||||
@@ -4168,6 +4245,7 @@ object Group : TagList {
|
|||||||
"group:nijiiro zakura",
|
"group:nijiiro zakura",
|
||||||
"group:nijinoren",
|
"group:nijinoren",
|
||||||
"group:nijiyome",
|
"group:nijiyome",
|
||||||
|
"group:niko-chan planning",
|
||||||
"group:nikomark",
|
"group:nikomark",
|
||||||
"group:nikomi omurice",
|
"group:nikomi omurice",
|
||||||
"group:nikoniko company",
|
"group:nikoniko company",
|
||||||
@@ -4323,6 +4401,7 @@ object Group : TagList {
|
|||||||
"group:obsession.",
|
"group:obsession.",
|
||||||
"group:ochanomizu mokujinkai",
|
"group:ochanomizu mokujinkai",
|
||||||
"group:ochawan",
|
"group:ochawan",
|
||||||
|
"group:ochi mono kanzume",
|
||||||
"group:ochikochitei",
|
"group:ochikochitei",
|
||||||
"group:ochikonium",
|
"group:ochikonium",
|
||||||
"group:ochimusha.",
|
"group:ochimusha.",
|
||||||
@@ -4339,6 +4418,7 @@ object Group : TagList {
|
|||||||
"group:office amagasa",
|
"group:office amagasa",
|
||||||
"group:ofuton de suyaa",
|
"group:ofuton de suyaa",
|
||||||
"group:ogeretsu-dan",
|
"group:ogeretsu-dan",
|
||||||
|
"group:ogon shinshi club",
|
||||||
"group:ogre no heya",
|
"group:ogre no heya",
|
||||||
"group:oh-banzai studio",
|
"group:oh-banzai studio",
|
||||||
"group:ohagi.",
|
"group:ohagi.",
|
||||||
@@ -4413,6 +4493,7 @@ object Group : TagList {
|
|||||||
"group:onsen",
|
"group:onsen",
|
||||||
"group:oobari doujou",
|
"group:oobari doujou",
|
||||||
"group:ookami no o",
|
"group:ookami no o",
|
||||||
|
"group:ookina gomibako",
|
||||||
"group:ookina kodomo no omocha bako",
|
"group:ookina kodomo no omocha bako",
|
||||||
"group:operating room",
|
"group:operating room",
|
||||||
"group:oppawi shitei",
|
"group:oppawi shitei",
|
||||||
@@ -4489,8 +4570,10 @@ object Group : TagList {
|
|||||||
"group:owlpop",
|
"group:owlpop",
|
||||||
"group:oxide.lab",
|
"group:oxide.lab",
|
||||||
"group:oyako donburi tei",
|
"group:oyako donburi tei",
|
||||||
|
"group:oyasumi kobe gyuu",
|
||||||
"group:ozawa kobo",
|
"group:ozawa kobo",
|
||||||
"group:p kikaku",
|
"group:p kikaku",
|
||||||
|
"group:p shoukai",
|
||||||
"group:p-collection",
|
"group:p-collection",
|
||||||
"group:p-forest",
|
"group:p-forest",
|
||||||
"group:p-pooh",
|
"group:p-pooh",
|
||||||
@@ -4632,6 +4715,7 @@ object Group : TagList {
|
|||||||
"group:pink-noise",
|
"group:pink-noise",
|
||||||
"group:pinkbell software",
|
"group:pinkbell software",
|
||||||
"group:pinkharlem",
|
"group:pinkharlem",
|
||||||
|
"group:pinki wana",
|
||||||
"group:pinktips.info",
|
"group:pinktips.info",
|
||||||
"group:pinkysoft",
|
"group:pinkysoft",
|
||||||
"group:pinpoint",
|
"group:pinpoint",
|
||||||
@@ -4689,6 +4773,7 @@ object Group : TagList {
|
|||||||
"group:ponchees kari",
|
"group:ponchees kari",
|
||||||
"group:ponkotuna potunoya",
|
"group:ponkotuna potunoya",
|
||||||
"group:ponpon-black",
|
"group:ponpon-black",
|
||||||
|
"group:ponponponpo",
|
||||||
"group:pons lab",
|
"group:pons lab",
|
||||||
"group:pony farm",
|
"group:pony farm",
|
||||||
"group:ponyfarm",
|
"group:ponyfarm",
|
||||||
@@ -4864,6 +4949,7 @@ object Group : TagList {
|
|||||||
"group:remora works",
|
"group:remora works",
|
||||||
"group:ren-kon-an",
|
"group:ren-kon-an",
|
||||||
"group:renai mangaka",
|
"group:renai mangaka",
|
||||||
|
"group:rengaworks",
|
||||||
"group:renge-dou",
|
"group:renge-dou",
|
||||||
"group:rengeza",
|
"group:rengeza",
|
||||||
"group:renglet",
|
"group:renglet",
|
||||||
@@ -4967,6 +5053,7 @@ object Group : TagList {
|
|||||||
"group:ruku-pusyu",
|
"group:ruku-pusyu",
|
||||||
"group:running girl",
|
"group:running girl",
|
||||||
"group:runrun soft",
|
"group:runrun soft",
|
||||||
|
"group:ruri-iro special room",
|
||||||
"group:ruriiro honpo",
|
"group:ruriiro honpo",
|
||||||
"group:rurirara star",
|
"group:rurirara star",
|
||||||
"group:ruruna and nimunimu",
|
"group:ruruna and nimunimu",
|
||||||
@@ -5053,6 +5140,7 @@ object Group : TagList {
|
|||||||
"group:sakuraproject",
|
"group:sakuraproject",
|
||||||
"group:sakurasaku",
|
"group:sakurasaku",
|
||||||
"group:sakurasaku koubou",
|
"group:sakurasaku koubou",
|
||||||
|
"group:sakurayakan no hanare",
|
||||||
"group:sakuryu",
|
"group:sakuryu",
|
||||||
"group:sakusaku kangen noushuku",
|
"group:sakusaku kangen noushuku",
|
||||||
"group:sakusakusakuchan",
|
"group:sakusakusakuchan",
|
||||||
@@ -5241,6 +5329,7 @@ object Group : TagList {
|
|||||||
"group:shiina club",
|
"group:shiina club",
|
||||||
"group:shiinotomoshibitake",
|
"group:shiinotomoshibitake",
|
||||||
"group:shiitake nouen",
|
"group:shiitake nouen",
|
||||||
|
"group:shijimi wari ningyou",
|
||||||
"group:shijou misaki",
|
"group:shijou misaki",
|
||||||
"group:shikakui tori",
|
"group:shikakui tori",
|
||||||
"group:shiki be careful",
|
"group:shiki be careful",
|
||||||
@@ -5274,6 +5363,7 @@ object Group : TagList {
|
|||||||
"group:shingeki no nameko",
|
"group:shingeki no nameko",
|
||||||
"group:shining star",
|
"group:shining star",
|
||||||
"group:shining star lilys",
|
"group:shining star lilys",
|
||||||
|
"group:shinise ikedaya",
|
||||||
"group:shinkirou akatsuki",
|
"group:shinkirou akatsuki",
|
||||||
"group:shinkuraiku",
|
"group:shinkuraiku",
|
||||||
"group:shinnihon pepsitou",
|
"group:shinnihon pepsitou",
|
||||||
@@ -5338,8 +5428,11 @@ object Group : TagList {
|
|||||||
"group:short kami",
|
"group:short kami",
|
||||||
"group:shortcut koubou",
|
"group:shortcut koubou",
|
||||||
"group:shosekido",
|
"group:shosekido",
|
||||||
|
"group:shota mangaya-san",
|
||||||
|
"group:shotacon-do",
|
||||||
"group:shouchuu mac",
|
"group:shouchuu mac",
|
||||||
"group:shoudansha",
|
"group:shoudansha",
|
||||||
|
"group:shougusha",
|
||||||
"group:shoujo 2-jou",
|
"group:shoujo 2-jou",
|
||||||
"group:shoujo gesshoku",
|
"group:shoujo gesshoku",
|
||||||
"group:shoujo kousaku",
|
"group:shoujo kousaku",
|
||||||
@@ -5356,6 +5449,7 @@ object Group : TagList {
|
|||||||
"group:showa saishuu sensen",
|
"group:showa saishuu sensen",
|
||||||
"group:showa shojo",
|
"group:showa shojo",
|
||||||
"group:showano",
|
"group:showano",
|
||||||
|
"group:shubi-ryoku 4man",
|
||||||
"group:shudoushiki denki jidousha",
|
"group:shudoushiki denki jidousha",
|
||||||
"group:shumi eshi",
|
"group:shumi eshi",
|
||||||
"group:shumisen jiru",
|
"group:shumisen jiru",
|
||||||
@@ -5384,6 +5478,7 @@ object Group : TagList {
|
|||||||
"group:silky to yukai na nakama-tachi",
|
"group:silky to yukai na nakama-tachi",
|
||||||
"group:silkys plus wasabi",
|
"group:silkys plus wasabi",
|
||||||
"group:silmaril",
|
"group:silmaril",
|
||||||
|
"group:silver rice",
|
||||||
"group:silver ring",
|
"group:silver ring",
|
||||||
"group:silver-kingdom",
|
"group:silver-kingdom",
|
||||||
"group:silver-rx",
|
"group:silver-rx",
|
||||||
@@ -5407,6 +5502,7 @@ object Group : TagList {
|
|||||||
"group:sirouto plan",
|
"group:sirouto plan",
|
||||||
"group:sirubedou",
|
"group:sirubedou",
|
||||||
"group:sisinabeya",
|
"group:sisinabeya",
|
||||||
|
"group:sistny and anasis",
|
||||||
"group:sittori oblaat",
|
"group:sittori oblaat",
|
||||||
"group:sketch-book",
|
"group:sketch-book",
|
||||||
"group:skid-mark",
|
"group:skid-mark",
|
||||||
@@ -5512,6 +5608,7 @@ object Group : TagList {
|
|||||||
"group:spicia",
|
"group:spicia",
|
||||||
"group:spig at",
|
"group:spig at",
|
||||||
"group:spiral brain",
|
"group:spiral brain",
|
||||||
|
"group:spiritguide",
|
||||||
"group:spock-san",
|
"group:spock-san",
|
||||||
"group:sponge empire",
|
"group:sponge empire",
|
||||||
"group:sql",
|
"group:sql",
|
||||||
@@ -5559,6 +5656,7 @@ object Group : TagList {
|
|||||||
"group:studio c-take",
|
"group:studio c-take",
|
||||||
"group:studio cardamom",
|
"group:studio cardamom",
|
||||||
"group:studio ciao",
|
"group:studio ciao",
|
||||||
|
"group:studio diamond",
|
||||||
"group:studio dna",
|
"group:studio dna",
|
||||||
"group:studio e.go",
|
"group:studio e.go",
|
||||||
"group:studio erohouse",
|
"group:studio erohouse",
|
||||||
@@ -5602,6 +5700,7 @@ object Group : TagList {
|
|||||||
"group:studio southpaw",
|
"group:studio southpaw",
|
||||||
"group:studio sunadokei",
|
"group:studio sunadokei",
|
||||||
"group:studio sushi kui-ne",
|
"group:studio sushi kui-ne",
|
||||||
|
"group:studio t.r.c.",
|
||||||
"group:studio tapa tapa",
|
"group:studio tapa tapa",
|
||||||
"group:studio wallaby",
|
"group:studio wallaby",
|
||||||
"group:studio waltz",
|
"group:studio waltz",
|
||||||
@@ -5615,6 +5714,7 @@ object Group : TagList {
|
|||||||
"group:studio30neko",
|
"group:studio30neko",
|
||||||
"group:studiomia",
|
"group:studiomia",
|
||||||
"group:studios",
|
"group:studios",
|
||||||
|
"group:stukitora",
|
||||||
"group:stulli-yasan",
|
"group:stulli-yasan",
|
||||||
"group:sturm no shukuten",
|
"group:sturm no shukuten",
|
||||||
"group:su kanchou koubou",
|
"group:su kanchou koubou",
|
||||||
@@ -5657,6 +5757,7 @@ object Group : TagList {
|
|||||||
"group:sumomo hana koushu",
|
"group:sumomo hana koushu",
|
||||||
"group:sunadokei to enpitsu",
|
"group:sunadokei to enpitsu",
|
||||||
"group:sunatoka aoi noyama",
|
"group:sunatoka aoi noyama",
|
||||||
|
"group:sunege6",
|
||||||
"group:sunezumi fauvism",
|
"group:sunezumi fauvism",
|
||||||
"group:sunora",
|
"group:sunora",
|
||||||
"group:sunsetmoon",
|
"group:sunsetmoon",
|
||||||
@@ -5766,8 +5867,10 @@ object Group : TagList {
|
|||||||
"group:tamago no kimi",
|
"group:tamago no kimi",
|
||||||
"group:tamanegiya",
|
"group:tamanegiya",
|
||||||
"group:tamatamasanmyaku",
|
"group:tamatamasanmyaku",
|
||||||
|
"group:tamima-ya",
|
||||||
"group:tamokuteki hall",
|
"group:tamokuteki hall",
|
||||||
"group:tamokuteki kuukan",
|
"group:tamokuteki kuukan",
|
||||||
|
"group:tana kara marriage",
|
||||||
"group:tanajou",
|
"group:tanajou",
|
||||||
"group:tanaka shouten",
|
"group:tanaka shouten",
|
||||||
"group:tanaura honpo",
|
"group:tanaura honpo",
|
||||||
@@ -5778,10 +5881,13 @@ object Group : TagList {
|
|||||||
"group:tanmatsu ijou",
|
"group:tanmatsu ijou",
|
||||||
"group:tanpatsu kikaku",
|
"group:tanpatsu kikaku",
|
||||||
"group:tansanshonen",
|
"group:tansanshonen",
|
||||||
|
"group:tanu-chan chi",
|
||||||
|
"group:tanukineiri",
|
||||||
"group:tarai death",
|
"group:tarai death",
|
||||||
"group:tarako koubou",
|
"group:tarako koubou",
|
||||||
"group:tarakospa",
|
"group:tarakospa",
|
||||||
"group:tarantula",
|
"group:tarantula",
|
||||||
|
"group:tarareba naraba",
|
||||||
"group:taromarun",
|
"group:taromarun",
|
||||||
"group:tashikani",
|
"group:tashikani",
|
||||||
"group:tasogare hakubutukan",
|
"group:tasogare hakubutukan",
|
||||||
@@ -5799,6 +5905,7 @@ object Group : TagList {
|
|||||||
"group:team dai 7 youhei shidan",
|
"group:team dai 7 youhei shidan",
|
||||||
"group:team hin ga 9",
|
"group:team hin ga 9",
|
||||||
"group:team ibm",
|
"group:team ibm",
|
||||||
|
"group:team lv",
|
||||||
"group:team okays",
|
"group:team okays",
|
||||||
"group:team tanabe",
|
"group:team tanabe",
|
||||||
"group:team z and 3n",
|
"group:team z and 3n",
|
||||||
@@ -5854,6 +5961,8 @@ object Group : TagList {
|
|||||||
"group:terolin soft",
|
"group:terolin soft",
|
||||||
"group:terra drive",
|
"group:terra drive",
|
||||||
"group:testa kitchen",
|
"group:testa kitchen",
|
||||||
|
"group:testme1111",
|
||||||
|
"group:tetora star gumi",
|
||||||
"group:tetorapotto bunsitu",
|
"group:tetorapotto bunsitu",
|
||||||
"group:tetrodotoxin",
|
"group:tetrodotoxin",
|
||||||
"group:tetsubou shounen",
|
"group:tetsubou shounen",
|
||||||
@@ -5866,6 +5975,7 @@ object Group : TagList {
|
|||||||
"group:th",
|
"group:th",
|
||||||
"group:th4",
|
"group:th4",
|
||||||
"group:the block buster destruction",
|
"group:the block buster destruction",
|
||||||
|
"group:the fourth sequence",
|
||||||
"group:the fuckin toyzaras",
|
"group:the fuckin toyzaras",
|
||||||
"group:the jolly roger",
|
"group:the jolly roger",
|
||||||
"group:the knight of the pants",
|
"group:the knight of the pants",
|
||||||
@@ -5898,115 +6008,5 @@ object Group : TagList {
|
|||||||
"group:tirol bunko",
|
"group:tirol bunko",
|
||||||
"group:tissuhaco",
|
"group:tissuhaco",
|
||||||
"group:titanaluminiden",
|
"group:titanaluminiden",
|
||||||
"group:titancolor brand",
|
|
||||||
"group:titeki-kairaku",
|
|
||||||
"group:titillatio",
|
|
||||||
"group:titokara 2nd branch",
|
|
||||||
"group:tits",
|
|
||||||
"group:tiusan kingdom",
|
|
||||||
"group:tj studio",
|
|
||||||
"group:tkh soft",
|
|
||||||
"group:tkspower",
|
|
||||||
"group:tnc.",
|
|
||||||
"group:tobihizageri",
|
|
||||||
"group:todd special",
|
|
||||||
"group:toei animation",
|
|
||||||
"group:togari-nozawa",
|
|
||||||
"group:tohonifun",
|
|
||||||
"group:tojora-men",
|
|
||||||
"group:tokaeshina koubou",
|
|
||||||
"group:tokai oohashi",
|
|
||||||
"group:tokinoame",
|
|
||||||
"group:toko-ya",
|
|
||||||
"group:tokohuyu no bakansu",
|
|
||||||
"group:tokonatsu tou",
|
|
||||||
"group:tokoroniyori-tengoku",
|
|
||||||
"group:tokumori ajillo",
|
|
||||||
"group:tokutan biyori",
|
|
||||||
"group:tokyo big eros",
|
|
||||||
"group:tokyo bungeling bay yokohama",
|
|
||||||
"group:tokyo corechica",
|
|
||||||
"group:tokyo gamachannel",
|
|
||||||
"group:tokyo kumitaisougumi",
|
|
||||||
"group:tokyo note",
|
|
||||||
"group:tokyo ponpon dou",
|
|
||||||
"group:tokyo rox",
|
|
||||||
"group:tokyo tomodachi kouen",
|
|
||||||
"group:tokyo tsunamushi land",
|
|
||||||
"group:tokyo-rozewomond club",
|
|
||||||
"group:tokyoboogienight",
|
|
||||||
"group:tokyusen",
|
|
||||||
"group:toluene ittokan",
|
|
||||||
"group:tomatogohan.",
|
|
||||||
"group:tomatohouse-905s room",
|
|
||||||
"group:tomatta tokei",
|
|
||||||
"group:tomcat",
|
|
||||||
"group:tomizofu",
|
|
||||||
"group:tomoe project",
|
|
||||||
"group:tomonokai",
|
|
||||||
"group:tomoshibi-ya",
|
|
||||||
"group:tomoshibiya koubou",
|
|
||||||
"group:tonari no dagashiya-san",
|
|
||||||
"group:tonari no machi no teishokuya",
|
|
||||||
"group:tonari no yama",
|
|
||||||
"group:tondemo 8 pun",
|
|
||||||
"group:tongari gorigori",
|
|
||||||
"group:tonikakuushi",
|
|
||||||
"group:tonkotsu fuumi",
|
|
||||||
"group:tonny club",
|
|
||||||
"group:tonpuuratei",
|
|
||||||
"group:tonteki teishoku",
|
|
||||||
"group:tonton byoushi",
|
|
||||||
"group:tontoro daiyokujou",
|
|
||||||
"group:tonyu bokujo",
|
|
||||||
"group:tonzura douchuu",
|
|
||||||
"group:top hat studios",
|
|
||||||
"group:toppuu dooro",
|
|
||||||
"group:toragoyashiki",
|
|
||||||
"group:toraisix",
|
|
||||||
"group:toraiya",
|
|
||||||
"group:torajirusi",
|
|
||||||
"group:torano ori",
|
|
||||||
"group:toratepotto",
|
|
||||||
"group:toratsugumi",
|
|
||||||
"group:tori salt",
|
|
||||||
"group:toriaezu kari",
|
|
||||||
"group:toridorinori",
|
|
||||||
"group:toriha dance",
|
|
||||||
"group:toriihime",
|
|
||||||
"group:torikaeshi no tsukanai sex",
|
|
||||||
"group:torino sunakimo",
|
|
||||||
"group:torinoya",
|
|
||||||
"group:toriten software studio.",
|
|
||||||
"group:toro plus drop",
|
|
||||||
"group:toro toro resistance",
|
|
||||||
"group:toro-chin teishoku",
|
|
||||||
"group:toro2 circus",
|
|
||||||
"group:torochidan",
|
|
||||||
"group:toruneko chaya",
|
|
||||||
"group:totencop",
|
|
||||||
"group:toto max",
|
|
||||||
"group:totocetera",
|
|
||||||
"group:totontei",
|
|
||||||
"group:totoyasu no tsf lab",
|
|
||||||
"group:totsugasa",
|
|
||||||
"group:totsugeki tonarino jo-galbi",
|
|
||||||
"group:tottoko mtarou",
|
|
||||||
"group:tottori-sabaku kingdom",
|
|
||||||
"group:tottototomekichi",
|
|
||||||
"group:touch",
|
|
||||||
"group:tougall kai",
|
|
||||||
"group:touge mine",
|
|
||||||
"group:tougenkyou",
|
|
||||||
"group:tougesakuraya",
|
|
||||||
"group:touhou marupondou",
|
|
||||||
"group:touin",
|
|
||||||
"group:toukon iwashikusa",
|
|
||||||
"group:toumei kousoku",
|
|
||||||
"group:toumei tsuushin",
|
|
||||||
"group:toushi ryoku kenkyuujo",
|
|
||||||
"group:toutaku tuyagadou",
|
|
||||||
"group:touyou bujutsu gakkou",
|
|
||||||
"group:touyu okiba kari",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,124 @@ package exh.eh.tags
|
|||||||
|
|
||||||
object Group2 : TagList {
|
object Group2 : TagList {
|
||||||
override fun getTags1(): List<String> = listOf(
|
override fun getTags1(): List<String> = listOf(
|
||||||
|
"group:titancolor brand",
|
||||||
|
"group:titeki-kairaku",
|
||||||
|
"group:titillatio",
|
||||||
|
"group:titokara 2nd branch",
|
||||||
|
"group:tits",
|
||||||
|
"group:tiusan kingdom",
|
||||||
|
"group:tj studio",
|
||||||
|
"group:tk jesus",
|
||||||
|
"group:tkh soft",
|
||||||
|
"group:tkspower",
|
||||||
|
"group:tnc.",
|
||||||
|
"group:tobihizageri",
|
||||||
|
"group:todd special",
|
||||||
|
"group:toei animation",
|
||||||
|
"group:togari-nozawa",
|
||||||
|
"group:tohonifun",
|
||||||
|
"group:tojora-men",
|
||||||
|
"group:tokaeshina koubou",
|
||||||
|
"group:tokai oohashi",
|
||||||
|
"group:tokinoame",
|
||||||
|
"group:toko-ya",
|
||||||
|
"group:tokohuyu no bakansu",
|
||||||
|
"group:tokonatsu tou",
|
||||||
|
"group:tokoroniyori-tengoku",
|
||||||
|
"group:tokumori ajillo",
|
||||||
|
"group:tokushuyokujou tondenhei",
|
||||||
|
"group:tokutan biyori",
|
||||||
|
"group:tokyo big eros",
|
||||||
|
"group:tokyo bungeling bay yokohama",
|
||||||
|
"group:tokyo corechica",
|
||||||
|
"group:tokyo gamachannel",
|
||||||
|
"group:tokyo kumitaisougumi",
|
||||||
|
"group:tokyo manga kenkyuujo",
|
||||||
|
"group:tokyo note",
|
||||||
|
"group:tokyo ponpon dou",
|
||||||
|
"group:tokyo rox",
|
||||||
|
"group:tokyo tomodachi kouen",
|
||||||
|
"group:tokyo tsunamushi land",
|
||||||
|
"group:tokyo-rozewomond club",
|
||||||
|
"group:tokyoboogienight",
|
||||||
|
"group:tokyusen",
|
||||||
|
"group:toluene ittokan",
|
||||||
|
"group:tomatogohan.",
|
||||||
|
"group:tomatohouse-905s room",
|
||||||
|
"group:tomatta tokei",
|
||||||
|
"group:tomcat",
|
||||||
|
"group:tomizofu",
|
||||||
|
"group:tomoe project",
|
||||||
|
"group:tomonokai",
|
||||||
|
"group:tomoshibi-ya",
|
||||||
|
"group:tomoshibiya koubou",
|
||||||
|
"group:tonari no dagashiya-san",
|
||||||
|
"group:tonari no machi no teishokuya",
|
||||||
|
"group:tonari no yama",
|
||||||
|
"group:tondemo 8 pun",
|
||||||
|
"group:tongari gorigori",
|
||||||
|
"group:tonikakuushi",
|
||||||
|
"group:tonkotsu fuumi",
|
||||||
|
"group:tonny club",
|
||||||
|
"group:tonpuuratei",
|
||||||
|
"group:tonteki teishoku",
|
||||||
|
"group:tonton byoushi",
|
||||||
|
"group:tontoro daiyokujou",
|
||||||
|
"group:tonyu bokujo",
|
||||||
|
"group:tonzura douchuu",
|
||||||
|
"group:top hat studios",
|
||||||
|
"group:toppuu dooro",
|
||||||
|
"group:toragoyashiki",
|
||||||
|
"group:toraisix",
|
||||||
|
"group:toraiya",
|
||||||
|
"group:torajirusi",
|
||||||
|
"group:torano ori",
|
||||||
|
"group:toratepotto",
|
||||||
|
"group:toratsugumi",
|
||||||
|
"group:tori salt",
|
||||||
|
"group:toriaezu kari",
|
||||||
|
"group:toridorinori",
|
||||||
|
"group:toriha dance",
|
||||||
|
"group:toriihime",
|
||||||
|
"group:torikaeshi no tsukanai sex",
|
||||||
|
"group:torino sunakimo",
|
||||||
|
"group:torinoya",
|
||||||
|
"group:toriten software studio.",
|
||||||
|
"group:toro plus drop",
|
||||||
|
"group:toro toro resistance",
|
||||||
|
"group:toro-chin teishoku",
|
||||||
|
"group:toro2 circus",
|
||||||
|
"group:torochidan",
|
||||||
|
"group:toruneko chaya",
|
||||||
|
"group:totencop",
|
||||||
|
"group:toto max",
|
||||||
|
"group:totocetera",
|
||||||
|
"group:totontei",
|
||||||
|
"group:totoyasu no tsf lab",
|
||||||
|
"group:totsugasa",
|
||||||
|
"group:totsugeki tonarino jo-galbi",
|
||||||
|
"group:tottoko mtarou",
|
||||||
|
"group:tottori-sabaku kingdom",
|
||||||
|
"group:tottototomekichi",
|
||||||
|
"group:touch",
|
||||||
|
"group:tougall kai",
|
||||||
|
"group:touge mine",
|
||||||
|
"group:tougenkyou",
|
||||||
|
"group:tougesakuraya",
|
||||||
|
"group:touhou marupondou",
|
||||||
|
"group:touin",
|
||||||
|
"group:toukon iwashikusa",
|
||||||
|
"group:toumei kousoku",
|
||||||
|
"group:toumei tsuushin",
|
||||||
|
"group:toushi ryoku kenkyuujo",
|
||||||
|
"group:toutaku tuyagadou",
|
||||||
|
"group:touyou bujutsu gakkou",
|
||||||
|
"group:touyu okiba kari",
|
||||||
"group:touyu stand",
|
"group:touyu stand",
|
||||||
"group:touzainanboku",
|
"group:touzainanboku",
|
||||||
"group:touzoku tachi no rakuda no mure",
|
"group:touzoku tachi no rakuda no mure",
|
||||||
"group:toxic love",
|
"group:toxic love",
|
||||||
|
"group:toyamando",
|
||||||
"group:toybox",
|
"group:toybox",
|
||||||
"group:tozan bu",
|
"group:tozan bu",
|
||||||
"group:tp",
|
"group:tp",
|
||||||
@@ -55,6 +169,7 @@ object Group2 : TagList {
|
|||||||
"group:tsuredure children",
|
"group:tsuredure children",
|
||||||
"group:tsurezurezuki",
|
"group:tsurezurezuki",
|
||||||
"group:tsurikichi doumei",
|
"group:tsurikichi doumei",
|
||||||
|
"group:tsurugashima heights",
|
||||||
"group:tsurumiku",
|
"group:tsurumiku",
|
||||||
"group:tsurupeta kenkyuusho",
|
"group:tsurupeta kenkyuusho",
|
||||||
"group:tsurutasousankai",
|
"group:tsurutasousankai",
|
||||||
@@ -70,6 +185,7 @@ object Group2 : TagList {
|
|||||||
"group:tukinon bunko",
|
"group:tukinon bunko",
|
||||||
"group:tukishitahikou",
|
"group:tukishitahikou",
|
||||||
"group:tumiribbon",
|
"group:tumiribbon",
|
||||||
|
"group:tuna ozawa",
|
||||||
"group:tunacan.",
|
"group:tunacan.",
|
||||||
"group:tunadrive",
|
"group:tunadrive",
|
||||||
"group:turuvege.",
|
"group:turuvege.",
|
||||||
@@ -141,8 +257,10 @@ object Group2 : TagList {
|
|||||||
"group:uminouie",
|
"group:uminouie",
|
||||||
"group:umon paradise",
|
"group:umon paradise",
|
||||||
"group:unagi no nedoko",
|
"group:unagi no nedoko",
|
||||||
|
"group:unagineco house",
|
||||||
"group:unaginobori",
|
"group:unaginobori",
|
||||||
"group:unceder",
|
"group:unceder",
|
||||||
|
"group:uncertain field",
|
||||||
"group:unconscious",
|
"group:unconscious",
|
||||||
"group:undamesi",
|
"group:undamesi",
|
||||||
"group:undead",
|
"group:undead",
|
||||||
@@ -201,6 +319,7 @@ object Group2 : TagList {
|
|||||||
"group:uribatakebokujou",
|
"group:uribatakebokujou",
|
||||||
"group:urondou",
|
"group:urondou",
|
||||||
"group:urusai kokuen",
|
"group:urusai kokuen",
|
||||||
|
"group:uruudoshi",
|
||||||
"group:us",
|
"group:us",
|
||||||
"group:usa daioh",
|
"group:usa daioh",
|
||||||
"group:usa.k",
|
"group:usa.k",
|
||||||
@@ -218,6 +337,7 @@ object Group2 : TagList {
|
|||||||
"group:usausa",
|
"group:usausa",
|
||||||
"group:ushi ushido",
|
"group:ushi ushido",
|
||||||
"group:ushidon-ya",
|
"group:ushidon-ya",
|
||||||
|
"group:ushikani gassen",
|
||||||
"group:uso seisakusho",
|
"group:uso seisakusho",
|
||||||
"group:uso293",
|
"group:uso293",
|
||||||
"group:usotsuki house",
|
"group:usotsuki house",
|
||||||
@@ -282,6 +402,7 @@ object Group2 : TagList {
|
|||||||
"group:wankyoku canvas",
|
"group:wankyoku canvas",
|
||||||
"group:wanwandoh",
|
"group:wanwandoh",
|
||||||
"group:warabimochi",
|
"group:warabimochi",
|
||||||
|
"group:warau kado ni wa",
|
||||||
"group:waretama",
|
"group:waretama",
|
||||||
"group:warp loop",
|
"group:warp loop",
|
||||||
"group:wasa wasa",
|
"group:wasa wasa",
|
||||||
@@ -364,6 +485,7 @@ object Group2 : TagList {
|
|||||||
"group:yakisaketeishoku",
|
"group:yakisaketeishoku",
|
||||||
"group:yakisoba pants",
|
"group:yakisoba pants",
|
||||||
"group:yakisoba rengo",
|
"group:yakisoba rengo",
|
||||||
|
"group:yakitate jamaica",
|
||||||
"group:yakiubu",
|
"group:yakiubu",
|
||||||
"group:yakousei fan club",
|
"group:yakousei fan club",
|
||||||
"group:yaku 40 man sarad",
|
"group:yaku 40 man sarad",
|
||||||
@@ -380,6 +502,7 @@ object Group2 : TagList {
|
|||||||
"group:yamakawa denenhuukei",
|
"group:yamakawa denenhuukei",
|
||||||
"group:yamami no yado",
|
"group:yamami no yado",
|
||||||
"group:yamamori gohan",
|
"group:yamamori gohan",
|
||||||
|
"group:yamamoto keiji",
|
||||||
"group:yamanaka no naka",
|
"group:yamanaka no naka",
|
||||||
"group:yamanashi musume.",
|
"group:yamanashi musume.",
|
||||||
"group:yamano murao",
|
"group:yamano murao",
|
||||||
@@ -463,10 +586,12 @@ object Group2 : TagList {
|
|||||||
"group:yoyude ikemasu",
|
"group:yoyude ikemasu",
|
||||||
"group:yozorairodrops",
|
"group:yozorairodrops",
|
||||||
"group:ys company",
|
"group:ys company",
|
||||||
|
"group:yu-chu-bu",
|
||||||
"group:yu-ta.18",
|
"group:yu-ta.18",
|
||||||
"group:yu-yu-tei",
|
"group:yu-yu-tei",
|
||||||
"group:yuasa rengou",
|
"group:yuasa rengou",
|
||||||
"group:yubidou",
|
"group:yubidou",
|
||||||
|
"group:yubunecity",
|
||||||
"group:yudenakya nama-beer",
|
"group:yudenakya nama-beer",
|
||||||
"group:yudokuya",
|
"group:yudokuya",
|
||||||
"group:yuhshiki",
|
"group:yuhshiki",
|
||||||
@@ -497,6 +622,7 @@ object Group2 : TagList {
|
|||||||
"group:yumeoikyounouta",
|
"group:yumeoikyounouta",
|
||||||
"group:yumeoukoku",
|
"group:yumeoukoku",
|
||||||
"group:yunabon",
|
"group:yunabon",
|
||||||
|
"group:yuo kurokawa",
|
||||||
"group:yurayuraseyuura",
|
"group:yurayuraseyuura",
|
||||||
"group:yurei yashiki",
|
"group:yurei yashiki",
|
||||||
"group:yureika blade",
|
"group:yureika blade",
|
||||||
@@ -564,6 +690,7 @@ object Group2 : TagList {
|
|||||||
"group:zenshuu bougyo",
|
"group:zenshuu bougyo",
|
||||||
"group:zensoku zenkai.",
|
"group:zensoku zenkai.",
|
||||||
"group:zensun habaku",
|
"group:zensun habaku",
|
||||||
|
"group:zenten ukemi tomonokai",
|
||||||
"group:zenzidou kosyubenjo",
|
"group:zenzidou kosyubenjo",
|
||||||
"group:zero byte",
|
"group:zero byte",
|
||||||
"group:zero equals mono",
|
"group:zero equals mono",
|
||||||
@@ -589,6 +716,7 @@ object Group2 : TagList {
|
|||||||
"group:zugaikotsu marudashi",
|
"group:zugaikotsu marudashi",
|
||||||
"group:zundoko sperm bank",
|
"group:zundoko sperm bank",
|
||||||
"group:zurumuke taro",
|
"group:zurumuke taro",
|
||||||
|
"group:zutto mae kara darui.",
|
||||||
"group:zvizva-dan",
|
"group:zvizva-dan",
|
||||||
"group:zydan",
|
"group:zydan",
|
||||||
"group:zyulokuya",
|
"group:zyulokuya",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ object Male : TagList {
|
|||||||
"male:balljob",
|
"male:balljob",
|
||||||
"male:balls expansion",
|
"male:balls expansion",
|
||||||
"male:bandages",
|
"male:bandages",
|
||||||
|
"male:bandaid",
|
||||||
"male:bat boy",
|
"male:bat boy",
|
||||||
"male:bbm",
|
"male:bbm",
|
||||||
"male:bdsm",
|
"male:bdsm",
|
||||||
@@ -55,7 +56,9 @@ object Male : TagList {
|
|||||||
"male:big penis",
|
"male:big penis",
|
||||||
"male:bike shorts",
|
"male:bike shorts",
|
||||||
"male:bikini",
|
"male:bikini",
|
||||||
|
"male:bird boy",
|
||||||
"male:bisexual",
|
"male:bisexual",
|
||||||
|
"male:bite mark",
|
||||||
"male:blackmail",
|
"male:blackmail",
|
||||||
"male:blind",
|
"male:blind",
|
||||||
"male:blindfold",
|
"male:blindfold",
|
||||||
@@ -111,8 +114,10 @@ object Male : TagList {
|
|||||||
"male:cockslapping",
|
"male:cockslapping",
|
||||||
"male:collar",
|
"male:collar",
|
||||||
"male:condom",
|
"male:condom",
|
||||||
|
"male:confinement",
|
||||||
"male:conjoined",
|
"male:conjoined",
|
||||||
"male:coprophagia",
|
"male:coprophagia",
|
||||||
|
"male:corpse",
|
||||||
"male:corruption",
|
"male:corruption",
|
||||||
"male:corset",
|
"male:corset",
|
||||||
"male:cosplaying",
|
"male:cosplaying",
|
||||||
@@ -157,6 +162,7 @@ object Male : TagList {
|
|||||||
"male:drill hair",
|
"male:drill hair",
|
||||||
"male:drugs",
|
"male:drugs",
|
||||||
"male:drunk",
|
"male:drunk",
|
||||||
|
"male:ear fuck",
|
||||||
"male:eel",
|
"male:eel",
|
||||||
"male:eggs",
|
"male:eggs",
|
||||||
"male:electric shocks",
|
"male:electric shocks",
|
||||||
@@ -198,6 +204,7 @@ object Male : TagList {
|
|||||||
"male:frog",
|
"male:frog",
|
||||||
"male:frog boy",
|
"male:frog boy",
|
||||||
"male:frottage",
|
"male:frottage",
|
||||||
|
"male:full tour",
|
||||||
"male:fundoshi",
|
"male:fundoshi",
|
||||||
"male:furry",
|
"male:furry",
|
||||||
"male:gag",
|
"male:gag",
|
||||||
@@ -273,6 +280,7 @@ object Male : TagList {
|
|||||||
"male:kimono",
|
"male:kimono",
|
||||||
"male:kindergarten uniform",
|
"male:kindergarten uniform",
|
||||||
"male:kissing",
|
"male:kissing",
|
||||||
|
"male:kodomo doushi",
|
||||||
"male:kunoichi",
|
"male:kunoichi",
|
||||||
"male:lab coat",
|
"male:lab coat",
|
||||||
"male:lactation",
|
"male:lactation",
|
||||||
@@ -291,6 +299,7 @@ object Male : TagList {
|
|||||||
"male:long tongue",
|
"male:long tongue",
|
||||||
"male:low bestiality",
|
"male:low bestiality",
|
||||||
"male:low guro",
|
"male:low guro",
|
||||||
|
"male:low incest",
|
||||||
"male:low scat",
|
"male:low scat",
|
||||||
"male:low shotacon",
|
"male:low shotacon",
|
||||||
"male:low smegma",
|
"male:low smegma",
|
||||||
@@ -329,6 +338,7 @@ object Male : TagList {
|
|||||||
"male:multiple assjob",
|
"male:multiple assjob",
|
||||||
"male:multiple footjob",
|
"male:multiple footjob",
|
||||||
"male:multiple handjob",
|
"male:multiple handjob",
|
||||||
|
"male:multiple nipples",
|
||||||
"male:multiple orgasms",
|
"male:multiple orgasms",
|
||||||
"male:multiple penises",
|
"male:multiple penises",
|
||||||
"male:multiple straddling",
|
"male:multiple straddling",
|
||||||
@@ -336,6 +346,7 @@ object Male : TagList {
|
|||||||
"male:muscle growth",
|
"male:muscle growth",
|
||||||
"male:mute",
|
"male:mute",
|
||||||
"male:nakadashi",
|
"male:nakadashi",
|
||||||
|
"male:navel birth",
|
||||||
"male:navel fuck",
|
"male:navel fuck",
|
||||||
"male:nazi",
|
"male:nazi",
|
||||||
"male:necrophilia",
|
"male:necrophilia",
|
||||||
@@ -496,6 +507,7 @@ object Male : TagList {
|
|||||||
"male:tracksuit",
|
"male:tracksuit",
|
||||||
"male:trampling",
|
"male:trampling",
|
||||||
"male:transformation",
|
"male:transformation",
|
||||||
|
"male:transparent clothing",
|
||||||
"male:triple anal",
|
"male:triple anal",
|
||||||
"male:triple penetration",
|
"male:triple penetration",
|
||||||
"male:tube",
|
"male:tube",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ object Mixed : TagList {
|
|||||||
"mixed:group",
|
"mixed:group",
|
||||||
"mixed:incest",
|
"mixed:incest",
|
||||||
"mixed:inseki",
|
"mixed:inseki",
|
||||||
|
"mixed:kodomo doushi",
|
||||||
|
"mixed:low incest",
|
||||||
"mixed:mmf threesome",
|
"mixed:mmf threesome",
|
||||||
"mixed:mmt threesome",
|
"mixed:mmt threesome",
|
||||||
"mixed:mtf threesome",
|
"mixed:mtf threesome",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ object Other : TagList {
|
|||||||
override fun getTags1(): List<String> = listOf(
|
override fun getTags1(): List<String> = listOf(
|
||||||
"other:3d",
|
"other:3d",
|
||||||
"other:3d imageset",
|
"other:3d imageset",
|
||||||
"other:ai generated",
|
|
||||||
"other:already uploaded",
|
"other:already uploaded",
|
||||||
"other:anaglyph",
|
"other:anaglyph",
|
||||||
"other:animated",
|
"other:animated",
|
||||||
@@ -20,21 +19,23 @@ object Other : TagList {
|
|||||||
"other:forbidden content",
|
"other:forbidden content",
|
||||||
"other:full censorship",
|
"other:full censorship",
|
||||||
"other:full color",
|
"other:full color",
|
||||||
"other:game manual",
|
|
||||||
"other:game sprite",
|
"other:game sprite",
|
||||||
"other:goudoushi",
|
"other:goudoushi",
|
||||||
"other:hardcore",
|
"other:hardcore",
|
||||||
"other:how to",
|
"other:how to",
|
||||||
"other:incomplete",
|
"other:incomplete",
|
||||||
|
"other:kodomo only",
|
||||||
"other:missing cover",
|
"other:missing cover",
|
||||||
"other:mosaic censorship",
|
"other:mosaic censorship",
|
||||||
"other:multi-work series",
|
"other:multi-work series",
|
||||||
"other:multipanel sequence",
|
"other:multipanel sequence",
|
||||||
"other:no penetration",
|
"other:no penetration",
|
||||||
|
"other:non-h game manual",
|
||||||
"other:non-h imageset",
|
"other:non-h imageset",
|
||||||
"other:non-nude",
|
"other:non-nude",
|
||||||
"other:novel",
|
"other:novel",
|
||||||
"other:nudity only",
|
"other:nudity only",
|
||||||
|
"other:object insertion only",
|
||||||
"other:out of order",
|
"other:out of order",
|
||||||
"other:paperchild",
|
"other:paperchild",
|
||||||
"other:realporn",
|
"other:realporn",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ object Parody : TagList {
|
|||||||
"parody:a vampyre story",
|
"parody:a vampyre story",
|
||||||
"parody:a.d.police",
|
"parody:a.d.police",
|
||||||
"parody:a.i. ga tomaranai",
|
"parody:a.i. ga tomaranai",
|
||||||
|
"parody:abashiri ikka",
|
||||||
"parody:abenobashi mahou shoutengai",
|
"parody:abenobashi mahou shoutengai",
|
||||||
"parody:acca 13-ku kansatsu-ka",
|
"parody:acca 13-ku kansatsu-ka",
|
||||||
"parody:accel world",
|
"parody:accel world",
|
||||||
@@ -243,7 +244,6 @@ object Parody : TagList {
|
|||||||
"parody:battle royale",
|
"parody:battle royale",
|
||||||
"parody:battle spirits",
|
"parody:battle spirits",
|
||||||
"parody:beast wars",
|
"parody:beast wars",
|
||||||
"parody:beat angel escalayer",
|
|
||||||
"parody:beat blades haruka",
|
"parody:beat blades haruka",
|
||||||
"parody:beatmania",
|
"parody:beatmania",
|
||||||
"parody:beauty and the beast",
|
"parody:beauty and the beast",
|
||||||
@@ -348,6 +348,7 @@ object Parody : TagList {
|
|||||||
"parody:burst angel",
|
"parody:burst angel",
|
||||||
"parody:busou renkin",
|
"parody:busou renkin",
|
||||||
"parody:busou shoujo machiavellianism",
|
"parody:busou shoujo machiavellianism",
|
||||||
|
"parody:buta no gotoki",
|
||||||
"parody:buzz lightyear of star command",
|
"parody:buzz lightyear of star command",
|
||||||
"parody:c cube",
|
"parody:c cube",
|
||||||
"parody:c the money of soul and possibility control",
|
"parody:c the money of soul and possibility control",
|
||||||
@@ -382,18 +383,21 @@ object Parody : TagList {
|
|||||||
"parody:childs play",
|
"parody:childs play",
|
||||||
"parody:chio-chan no tsuugakuro",
|
"parody:chio-chan no tsuugakuro",
|
||||||
"parody:chip n dale rescue rangers",
|
"parody:chip n dale rescue rangers",
|
||||||
|
"parody:chiyu mahou no machigatta tsukaikata",
|
||||||
"parody:cho aniki",
|
"parody:cho aniki",
|
||||||
"parody:chobits",
|
"parody:chobits",
|
||||||
"parody:chogattai majutsu robot ginguiser",
|
"parody:chogattai majutsu robot ginguiser",
|
||||||
"parody:chokotto sister",
|
"parody:chokotto sister",
|
||||||
"parody:chou dokyuu shoujo 4946",
|
"parody:chou dokyuu shoujo 4946",
|
||||||
"parody:chou kuse ni narisou",
|
"parody:chou kuse ni narisou",
|
||||||
|
"parody:chou-tantei jikenbo rain code",
|
||||||
"parody:choudenshi bioman",
|
"parody:choudenshi bioman",
|
||||||
"parody:chouja raideen",
|
"parody:chouja raideen",
|
||||||
"parody:choujikuu kidan southern cross",
|
"parody:choujikuu kidan southern cross",
|
||||||
"parody:choujin koukousei-tachi wa isekai demo yoyuu de ikinuku you desu",
|
"parody:choujin koukousei-tachi wa isekai demo yoyuu de ikinuku you desu",
|
||||||
"parody:choujuu kishin dancougar",
|
"parody:choujuu kishin dancougar",
|
||||||
"parody:choukou shinki ixseal",
|
"parody:choukou shinki ixseal",
|
||||||
|
"parody:choukou tenshi escalayer",
|
||||||
"parody:chronicles of the going home club",
|
"parody:chronicles of the going home club",
|
||||||
"parody:chrono cross",
|
"parody:chrono cross",
|
||||||
"parody:chrono crusade",
|
"parody:chrono crusade",
|
||||||
@@ -554,6 +558,7 @@ object Parody : TagList {
|
|||||||
"parody:dororon enma-kun",
|
"parody:dororon enma-kun",
|
||||||
"parody:dosanko gal wa namaramenkoi",
|
"parody:dosanko gal wa namaramenkoi",
|
||||||
"parody:doubutsu banchou",
|
"parody:doubutsu banchou",
|
||||||
|
"parody:doubutsu nee-chan",
|
||||||
"parody:doubutsu no oishasan",
|
"parody:doubutsu no oishasan",
|
||||||
"parody:doukyuusei 2",
|
"parody:doukyuusei 2",
|
||||||
"parody:douluo continent",
|
"parody:douluo continent",
|
||||||
@@ -565,6 +570,7 @@ object Parody : TagList {
|
|||||||
"parody:dragon age",
|
"parody:dragon age",
|
||||||
"parody:dragon ball",
|
"parody:dragon ball",
|
||||||
"parody:dragon ball gt",
|
"parody:dragon ball gt",
|
||||||
|
"parody:dragon ball heroes",
|
||||||
"parody:dragon ball super",
|
"parody:dragon ball super",
|
||||||
"parody:dragon ball z",
|
"parody:dragon ball z",
|
||||||
"parody:dragon half",
|
"parody:dragon half",
|
||||||
@@ -628,6 +634,7 @@ object Parody : TagList {
|
|||||||
"parody:elemental gelade",
|
"parody:elemental gelade",
|
||||||
"parody:elf-san wa yaserarenai.",
|
"parody:elf-san wa yaserarenai.",
|
||||||
"parody:elfen lied",
|
"parody:elfen lied",
|
||||||
|
"parody:elfquest",
|
||||||
"parody:emma a victorian romance",
|
"parody:emma a victorian romance",
|
||||||
"parody:endless frontier",
|
"parody:endless frontier",
|
||||||
"parody:enen no shouboutai",
|
"parody:enen no shouboutai",
|
||||||
@@ -705,12 +712,14 @@ object Parody : TagList {
|
|||||||
"parody:final romance",
|
"parody:final romance",
|
||||||
"parody:fire emblem",
|
"parody:fire emblem",
|
||||||
"parody:fire emblem awakening",
|
"parody:fire emblem awakening",
|
||||||
|
"parody:fire emblem fates",
|
||||||
"parody:fire emblem gaiden",
|
"parody:fire emblem gaiden",
|
||||||
"parody:fire emblem if",
|
"parody:fire emblem genealogy of the holy war",
|
||||||
"parody:fire emblem mystery of the emblem",
|
"parody:fire emblem mystery of the emblem",
|
||||||
"parody:fire emblem path of radiance",
|
"parody:fire emblem path of radiance",
|
||||||
"parody:fire emblem radiant dawn",
|
"parody:fire emblem radiant dawn",
|
||||||
"parody:fire emblem rekka no ken",
|
"parody:fire emblem the binding blade",
|
||||||
|
"parody:fire emblem the blazing blade",
|
||||||
"parody:fire emblem the sacred stones",
|
"parody:fire emblem the sacred stones",
|
||||||
"parody:fire emblem three houses",
|
"parody:fire emblem three houses",
|
||||||
"parody:fist of the north star",
|
"parody:fist of the north star",
|
||||||
@@ -773,6 +782,7 @@ object Parody : TagList {
|
|||||||
"parody:gakkou gurashi",
|
"parody:gakkou gurashi",
|
||||||
"parody:gakkou no kaidan",
|
"parody:gakkou no kaidan",
|
||||||
"parody:gakuen alice",
|
"parody:gakuen alice",
|
||||||
|
"parody:gakuen babysitters",
|
||||||
"parody:gakuen heaven",
|
"parody:gakuen heaven",
|
||||||
"parody:gakusen toshi asterisk",
|
"parody:gakusen toshi asterisk",
|
||||||
"parody:galactic drifter vifam",
|
"parody:galactic drifter vifam",
|
||||||
@@ -807,6 +817,7 @@ object Parody : TagList {
|
|||||||
"parody:genroh",
|
"parody:genroh",
|
||||||
"parody:genshiken",
|
"parody:genshiken",
|
||||||
"parody:genshin impact",
|
"parody:genshin impact",
|
||||||
|
"parody:gensou suikoden",
|
||||||
"parody:getbackers",
|
"parody:getbackers",
|
||||||
"parody:getsumen to heiki mina",
|
"parody:getsumen to heiki mina",
|
||||||
"parody:getter robo",
|
"parody:getter robo",
|
||||||
@@ -830,6 +841,7 @@ object Parody : TagList {
|
|||||||
"parody:gochuumon wa usagi desu ka",
|
"parody:gochuumon wa usagi desu ka",
|
||||||
"parody:god eater",
|
"parody:god eater",
|
||||||
"parody:god of war",
|
"parody:god of war",
|
||||||
|
"parody:goddess of victory nikke",
|
||||||
"parody:gogo sentai boukenger",
|
"parody:gogo sentai boukenger",
|
||||||
"parody:gokudou-kun manyuuki",
|
"parody:gokudou-kun manyuuki",
|
||||||
"parody:gokujou seitokai",
|
"parody:gokujou seitokai",
|
||||||
@@ -839,6 +851,7 @@ object Parody : TagList {
|
|||||||
"parody:golden sun",
|
"parody:golden sun",
|
||||||
"parody:goldfish warning",
|
"parody:goldfish warning",
|
||||||
"parody:goof troop",
|
"parody:goof troop",
|
||||||
|
"parody:gormiti",
|
||||||
"parody:gosenzo san-e",
|
"parody:gosenzo san-e",
|
||||||
"parody:goshujin-sama to kemonomimi no shoujo mel",
|
"parody:goshujin-sama to kemonomimi no shoujo mel",
|
||||||
"parody:goshuushou-sama ninomiya-kun",
|
"parody:goshuushou-sama ninomiya-kun",
|
||||||
@@ -911,12 +924,14 @@ object Parody : TagList {
|
|||||||
"parody:hakushon daimaou",
|
"parody:hakushon daimaou",
|
||||||
"parody:half-life",
|
"parody:half-life",
|
||||||
"parody:halo",
|
"parody:halo",
|
||||||
|
"parody:hametsu no oukoku",
|
||||||
"parody:hamtaro",
|
"parody:hamtaro",
|
||||||
"parody:hana no joshi announcer newscaster etsuko",
|
"parody:hana no joshi announcer newscaster etsuko",
|
||||||
"parody:hanamaru youchien",
|
"parody:hanamaru youchien",
|
||||||
"parody:hanasaku iroha",
|
"parody:hanasaku iroha",
|
||||||
"parody:hanaukyo maid tai",
|
"parody:hanaukyo maid tai",
|
||||||
"parody:hand maid may",
|
"parody:hand maid may",
|
||||||
|
"parody:hantsu x trash",
|
||||||
"parody:hanzasky",
|
"parody:hanzasky",
|
||||||
"parody:happiness",
|
"parody:happiness",
|
||||||
"parody:happinesscharge precure",
|
"parody:happinesscharge precure",
|
||||||
@@ -932,11 +947,13 @@ object Parody : TagList {
|
|||||||
"parody:hataage kemono michi",
|
"parody:hataage kemono michi",
|
||||||
"parody:hataraku onii-san",
|
"parody:hataraku onii-san",
|
||||||
"parody:hataraku saibou",
|
"parody:hataraku saibou",
|
||||||
|
"parody:hatena no tou",
|
||||||
"parody:hateshinaku aoi kono sora no shita de...",
|
"parody:hateshinaku aoi kono sora no shita de...",
|
||||||
"parody:hatsukoi limited",
|
"parody:hatsukoi limited",
|
||||||
"parody:hayate no gotoku",
|
"parody:hayate no gotoku",
|
||||||
"parody:hayate x blade",
|
"parody:hayate x blade",
|
||||||
"parody:hazun de catch",
|
"parody:hazun de catch",
|
||||||
|
"parody:hazure waku",
|
||||||
"parody:he is my master",
|
"parody:he is my master",
|
||||||
"parody:he-man and the masters of the universe",
|
"parody:he-man and the masters of the universe",
|
||||||
"parody:heartcatch precure",
|
"parody:heartcatch precure",
|
||||||
@@ -969,6 +986,7 @@ object Parody : TagList {
|
|||||||
"parody:hime kishi lilia",
|
"parody:hime kishi lilia",
|
||||||
"parody:hime-chans ribbon",
|
"parody:hime-chans ribbon",
|
||||||
"parody:himegoto",
|
"parody:himegoto",
|
||||||
|
"parody:himiko-den",
|
||||||
"parody:himitsu no akko-chan",
|
"parody:himitsu no akko-chan",
|
||||||
"parody:himitsu sentai metamor v",
|
"parody:himitsu sentai metamor v",
|
||||||
"parody:hinabita",
|
"parody:hinabita",
|
||||||
@@ -1004,6 +1022,7 @@ object Parody : TagList {
|
|||||||
"parody:how the grinch stole christmas",
|
"parody:how the grinch stole christmas",
|
||||||
"parody:how to train your dragon",
|
"parody:how to train your dragon",
|
||||||
"parody:howls moving castle",
|
"parody:howls moving castle",
|
||||||
|
"parody:hp himepara",
|
||||||
"parody:hugtto precure",
|
"parody:hugtto precure",
|
||||||
"parody:hulu xiongdi",
|
"parody:hulu xiongdi",
|
||||||
"parody:hunter x hunter",
|
"parody:hunter x hunter",
|
||||||
@@ -1092,6 +1111,7 @@ object Parody : TagList {
|
|||||||
"parody:jewelpet tinkle",
|
"parody:jewelpet tinkle",
|
||||||
"parody:jibaku shounen hanako-kun",
|
"parody:jibaku shounen hanako-kun",
|
||||||
"parody:jigoku shoujo",
|
"parody:jigoku shoujo",
|
||||||
|
"parody:jiisan baasan wakagaeru",
|
||||||
"parody:jijou wo shiranai tenkousei ga guigui kuru.",
|
"parody:jijou wo shiranai tenkousei ga guigui kuru.",
|
||||||
"parody:jikkyou powerful pro yakyuu",
|
"parody:jikkyou powerful pro yakyuu",
|
||||||
"parody:jikuu senshi spielban",
|
"parody:jikuu senshi spielban",
|
||||||
@@ -1142,6 +1162,7 @@ object Parody : TagList {
|
|||||||
"parody:kageki shojo",
|
"parody:kageki shojo",
|
||||||
"parody:kagihime monogatari eikyuu alice rondo",
|
"parody:kagihime monogatari eikyuu alice rondo",
|
||||||
"parody:kagura reimeiki",
|
"parody:kagura reimeiki",
|
||||||
|
"parody:kagurabachi",
|
||||||
"parody:kaguya-sama wa kokurasetai",
|
"parody:kaguya-sama wa kokurasetai",
|
||||||
"parody:kaichou wa maid-sama",
|
"parody:kaichou wa maid-sama",
|
||||||
"parody:kaifuku jutsushi no yarinaoshi",
|
"parody:kaifuku jutsushi no yarinaoshi",
|
||||||
@@ -1187,12 +1208,15 @@ object Parody : TagList {
|
|||||||
"parody:kappa no kaikata",
|
"parody:kappa no kaikata",
|
||||||
"parody:kara no kyoukai",
|
"parody:kara no kyoukai",
|
||||||
"parody:kara no naka no kotori",
|
"parody:kara no naka no kotori",
|
||||||
|
"parody:karakai jouzu no takagi-san",
|
||||||
"parody:karakuri kiden",
|
"parody:karakuri kiden",
|
||||||
"parody:kare kano",
|
"parody:kare kano",
|
||||||
"parody:kashimashi",
|
"parody:kashimashi",
|
||||||
"parody:kasumin",
|
"parody:kasumin",
|
||||||
"parody:katawa shoujo",
|
"parody:katawa shoujo",
|
||||||
"parody:katekyo hitman reborn",
|
"parody:katekyo hitman reborn",
|
||||||
|
"parody:katri girl of the meadows",
|
||||||
|
"parody:katsute kami datta kemono-tachi e",
|
||||||
"parody:katsute mahou shoujo to aku wa tekitai shite ita.",
|
"parody:katsute mahou shoujo to aku wa tekitai shite ita.",
|
||||||
"parody:katte ni kaizou",
|
"parody:katte ni kaizou",
|
||||||
"parody:kawaii dake ja nai shikimori-san",
|
"parody:kawaii dake ja nai shikimori-san",
|
||||||
@@ -1293,6 +1317,7 @@ object Parody : TagList {
|
|||||||
"parody:kozure ookami",
|
"parody:kozure ookami",
|
||||||
"parody:kubo-san wa mob o yurusanai",
|
"parody:kubo-san wa mob o yurusanai",
|
||||||
"parody:kumo desu ga nani ka",
|
"parody:kumo desu ga nani ka",
|
||||||
|
"parody:kung fu cooking girls",
|
||||||
"parody:kung fu panda",
|
"parody:kung fu panda",
|
||||||
"parody:kunoichi",
|
"parody:kunoichi",
|
||||||
"parody:kuon no kizuna",
|
"parody:kuon no kizuna",
|
||||||
@@ -1394,6 +1419,8 @@ object Parody : TagList {
|
|||||||
"parody:lupin iii",
|
"parody:lupin iii",
|
||||||
"parody:lux-pain",
|
"parody:lux-pain",
|
||||||
"parody:lv1 maou to one room yuusha",
|
"parody:lv1 maou to one room yuusha",
|
||||||
|
"parody:lv2 kara cheat datta motoyuusha kouho no mattari isekai life",
|
||||||
|
"parody:lydie and suelle no atelier",
|
||||||
"parody:mabinogi",
|
"parody:mabinogi",
|
||||||
"parody:macademi wasshoi",
|
"parody:macademi wasshoi",
|
||||||
"parody:machikado mazoku",
|
"parody:machikado mazoku",
|
||||||
@@ -1458,10 +1485,12 @@ object Parody : TagList {
|
|||||||
"parody:makai kishi ingrid",
|
"parody:makai kishi ingrid",
|
||||||
"parody:makai tenshi jibril",
|
"parody:makai tenshi jibril",
|
||||||
"parody:makai toushi saga",
|
"parody:makai toushi saga",
|
||||||
|
"parody:make heroine ga oosugiru",
|
||||||
"parody:maken-ki",
|
"parody:maken-ki",
|
||||||
"parody:makyou gaiden le deus",
|
"parody:makyou gaiden le deus",
|
||||||
"parody:mama is a 4th grader",
|
"parody:mama is a 4th grader",
|
||||||
"parody:mama wa poyopoyo saurus ga osuki",
|
"parody:mama wa poyopoyo saurus ga osuki",
|
||||||
|
"parody:mamahaha no tsurego ga motokano datta",
|
||||||
"parody:mamono musume zukan",
|
"parody:mamono musume zukan",
|
||||||
"parody:mamoru-kun",
|
"parody:mamoru-kun",
|
||||||
"parody:mamoru-kun ni megami no shukufuku wo",
|
"parody:mamoru-kun ni megami no shukufuku wo",
|
||||||
@@ -1471,6 +1500,7 @@ object Parody : TagList {
|
|||||||
"parody:mangaka-san to assistant-san to",
|
"parody:mangaka-san to assistant-san to",
|
||||||
"parody:manyuu hikenchou",
|
"parody:manyuu hikenchou",
|
||||||
"parody:maou gakuin no futekigousha",
|
"parody:maou gakuin no futekigousha",
|
||||||
|
"parody:maou no ore ga dorei elf o yome ni shitanda ga dou medereba ii",
|
||||||
"parody:maou to ore no hangyakuki",
|
"parody:maou to ore no hangyakuki",
|
||||||
"parody:maoujou de oyasumi",
|
"parody:maoujou de oyasumi",
|
||||||
"parody:maoyuu maou yuusha",
|
"parody:maoyuu maou yuusha",
|
||||||
@@ -1520,6 +1550,7 @@ object Parody : TagList {
|
|||||||
"parody:megami paradise",
|
"parody:megami paradise",
|
||||||
"parody:megami-ryou no ryoubo-kun.",
|
"parody:megami-ryou no ryoubo-kun.",
|
||||||
"parody:megamind",
|
"parody:megamind",
|
||||||
|
"parody:mehime no toriko",
|
||||||
"parody:meiken lassie",
|
"parody:meiken lassie",
|
||||||
"parody:meili xinshijie i",
|
"parody:meili xinshijie i",
|
||||||
"parody:melon-chan no seichouki",
|
"parody:melon-chan no seichouki",
|
||||||
@@ -1611,6 +1642,7 @@ object Parody : TagList {
|
|||||||
"parody:my life as a teenage robot",
|
"parody:my life as a teenage robot",
|
||||||
"parody:my little pony friendship is magic",
|
"parody:my little pony friendship is magic",
|
||||||
"parody:my neighbor totoro",
|
"parody:my neighbor totoro",
|
||||||
|
"parody:my wife is a demon queen",
|
||||||
"parody:myriad colors phantom world",
|
"parody:myriad colors phantom world",
|
||||||
"parody:myst",
|
"parody:myst",
|
||||||
"parody:na lesnoy trope",
|
"parody:na lesnoy trope",
|
||||||
@@ -1679,6 +1711,7 @@ object Parody : TagList {
|
|||||||
"parody:nogizaka haruka no himitsu",
|
"parody:nogizaka haruka no himitsu",
|
||||||
"parody:non anonymous instruction",
|
"parody:non anonymous instruction",
|
||||||
"parody:non non biyori",
|
"parody:non non biyori",
|
||||||
|
"parody:nora to toki no koubou kiri no mori no majo",
|
||||||
"parody:nozoki ana",
|
"parody:nozoki ana",
|
||||||
"parody:nurarihyon no mago",
|
"parody:nurarihyon no mago",
|
||||||
"parody:nurse angel ririka sos",
|
"parody:nurse angel ririka sos",
|
||||||
@@ -1733,7 +1766,9 @@ object Parody : TagList {
|
|||||||
"parody:ookiku furikabutte",
|
"parody:ookiku furikabutte",
|
||||||
"parody:oounabara to wadanohara",
|
"parody:oounabara to wadanohara",
|
||||||
"parody:ooyasan wa shishunki",
|
"parody:ooyasan wa shishunki",
|
||||||
|
"parody:operators side",
|
||||||
"parody:ore dake haireru kakushi dungeon",
|
"parody:ore dake haireru kakushi dungeon",
|
||||||
|
"parody:ore ga suki nano wa imouto dakedo imouto ja nai",
|
||||||
"parody:ore monogatari",
|
"parody:ore monogatari",
|
||||||
"parody:ore no imouto ga konna ni kawaii wake ga nai",
|
"parody:ore no imouto ga konna ni kawaii wake ga nai",
|
||||||
"parody:ore no kanojo to osananajimi ga shuraba sugiru",
|
"parody:ore no kanojo to osananajimi ga shuraba sugiru",
|
||||||
@@ -1894,6 +1929,9 @@ object Parody : TagList {
|
|||||||
"parody:resident evil",
|
"parody:resident evil",
|
||||||
"parody:resonance of fate",
|
"parody:resonance of fate",
|
||||||
"parody:ressha sentai toqger",
|
"parody:ressha sentai toqger",
|
||||||
|
"parody:return to shironagasu island",
|
||||||
|
"parody:revelation online",
|
||||||
|
"parody:reverse 1999",
|
||||||
"parody:revevolution",
|
"parody:revevolution",
|
||||||
"parody:revolutionary girl utena",
|
"parody:revolutionary girl utena",
|
||||||
"parody:rewrite",
|
"parody:rewrite",
|
||||||
@@ -1941,6 +1979,7 @@ object Parody : TagList {
|
|||||||
"parody:saenai heroine no sodatekata",
|
"parody:saenai heroine no sodatekata",
|
||||||
"parody:saga frontier",
|
"parody:saga frontier",
|
||||||
"parody:saijaku muhai no bahamut",
|
"parody:saijaku muhai no bahamut",
|
||||||
|
"parody:saijaku tamer wa gomi hiroi no tabi o hajimemashita.",
|
||||||
"parody:saijou no meii",
|
"parody:saijou no meii",
|
||||||
"parody:saikano",
|
"parody:saikano",
|
||||||
"parody:saikin yatotta maid ga ayashii",
|
"parody:saikin yatotta maid ga ayashii",
|
||||||
@@ -1963,6 +2002,9 @@ object Parody : TagList {
|
|||||||
"parody:samurai 7",
|
"parody:samurai 7",
|
||||||
"parody:samurai champloo",
|
"parody:samurai champloo",
|
||||||
"parody:samurai pizza cats",
|
"parody:samurai pizza cats",
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getTags2(): List<String> = listOf(
|
||||||
"parody:samurai sentai shinkenger",
|
"parody:samurai sentai shinkenger",
|
||||||
"parody:samurai spirits",
|
"parody:samurai spirits",
|
||||||
"parody:samurai warriors",
|
"parody:samurai warriors",
|
||||||
@@ -1989,6 +2031,7 @@ object Parody : TagList {
|
|||||||
"parody:scott pilgrim",
|
"parody:scott pilgrim",
|
||||||
"parody:scp foundation",
|
"parody:scp foundation",
|
||||||
"parody:sd gundam sangokuden",
|
"parody:sd gundam sangokuden",
|
||||||
|
"parody:seer",
|
||||||
"parody:sei juushi bismark",
|
"parody:sei juushi bismark",
|
||||||
"parody:sei senshi yariman 12",
|
"parody:sei senshi yariman 12",
|
||||||
"parody:seijo no maryoku wa bannou desu",
|
"parody:seijo no maryoku wa bannou desu",
|
||||||
@@ -2002,9 +2045,6 @@ object Parody : TagList {
|
|||||||
"parody:seirei no moribito",
|
"parody:seirei no moribito",
|
||||||
"parody:seishoujo sentai lakers",
|
"parody:seishoujo sentai lakers",
|
||||||
"parody:seishun buta yarou wa bunny girl senpai no yume o minai",
|
"parody:seishun buta yarou wa bunny girl senpai no yume o minai",
|
||||||
)
|
|
||||||
|
|
||||||
override fun getTags2(): List<String> = listOf(
|
|
||||||
"parody:seito kaichou hikaru",
|
"parody:seito kaichou hikaru",
|
||||||
"parody:seitokai no ichizon",
|
"parody:seitokai no ichizon",
|
||||||
"parody:seitokai yakuindomo",
|
"parody:seitokai yakuindomo",
|
||||||
@@ -2185,6 +2225,7 @@ object Parody : TagList {
|
|||||||
"parody:star wars",
|
"parody:star wars",
|
||||||
"parody:star-myu",
|
"parody:star-myu",
|
||||||
"parody:starcraft",
|
"parody:starcraft",
|
||||||
|
"parody:stargate",
|
||||||
"parody:starry sky",
|
"parody:starry sky",
|
||||||
"parody:station memories",
|
"parody:station memories",
|
||||||
"parody:steam detectives",
|
"parody:steam detectives",
|
||||||
@@ -2199,7 +2240,6 @@ object Parody : TagList {
|
|||||||
"parody:strike witches",
|
"parody:strike witches",
|
||||||
"parody:sucker punch",
|
"parody:sucker punch",
|
||||||
"parody:suigetsu",
|
"parody:suigetsu",
|
||||||
"parody:suikoden",
|
|
||||||
"parody:suikoden v",
|
"parody:suikoden v",
|
||||||
"parody:suisei no gargantia",
|
"parody:suisei no gargantia",
|
||||||
"parody:suite precure",
|
"parody:suite precure",
|
||||||
@@ -2297,7 +2337,9 @@ object Parody : TagList {
|
|||||||
"parody:tenkuu senki shurato",
|
"parody:tenkuu senki shurato",
|
||||||
"parody:tenkuu shinpan",
|
"parody:tenkuu shinpan",
|
||||||
"parody:tensai ryouri shounen ajinosuke",
|
"parody:tensai ryouri shounen ajinosuke",
|
||||||
|
"parody:tensei kizoku kantei skill de nariagaru",
|
||||||
"parody:tensei oujo to tensai reijou no mahou kakumei",
|
"parody:tensei oujo to tensai reijou no mahou kakumei",
|
||||||
|
"parody:tensei shitara dainana ouji datta node kimama ni majutsu o kiwamemasu",
|
||||||
"parody:tensei shitara slime datta ken",
|
"parody:tensei shitara slime datta ken",
|
||||||
"parody:tenshi na konamaiki",
|
"parody:tenshi na konamaiki",
|
||||||
"parody:tenshi ni narumon",
|
"parody:tenshi ni narumon",
|
||||||
@@ -2360,6 +2402,7 @@ object Parody : TagList {
|
|||||||
"parody:the legend of heroes",
|
"parody:the legend of heroes",
|
||||||
"parody:the legend of korra",
|
"parody:the legend of korra",
|
||||||
"parody:the legend of luo xiaohei",
|
"parody:the legend of luo xiaohei",
|
||||||
|
"parody:the legend of the condor heroes",
|
||||||
"parody:the legend of the legendary heroes",
|
"parody:the legend of the legendary heroes",
|
||||||
"parody:the legend of zelda",
|
"parody:the legend of zelda",
|
||||||
"parody:the life and times of juniper lee",
|
"parody:the life and times of juniper lee",
|
||||||
@@ -2386,6 +2429,7 @@ object Parody : TagList {
|
|||||||
"parody:the princess and the frog",
|
"parody:the princess and the frog",
|
||||||
"parody:the proud family",
|
"parody:the proud family",
|
||||||
"parody:the queen of duellist",
|
"parody:the queen of duellist",
|
||||||
|
"parody:the queens gambit",
|
||||||
"parody:the ren and stimpy show",
|
"parody:the ren and stimpy show",
|
||||||
"parody:the replacements",
|
"parody:the replacements",
|
||||||
"parody:the rescuers",
|
"parody:the rescuers",
|
||||||
@@ -2435,7 +2479,7 @@ object Parody : TagList {
|
|||||||
"parody:togainu no chi",
|
"parody:togainu no chi",
|
||||||
"parody:toheart2",
|
"parody:toheart2",
|
||||||
"parody:toji no miko",
|
"parody:toji no miko",
|
||||||
"parody:tokidoki bosotto russia-go de dereru tonari no aalya-san",
|
"parody:tokidoki bosotto russia-go de dereru tonari no alya-san",
|
||||||
"parody:tokimeki memorial",
|
"parody:tokimeki memorial",
|
||||||
"parody:tokusatsu gagaga",
|
"parody:tokusatsu gagaga",
|
||||||
"parody:tokusou sentai dekaranger",
|
"parody:tokusou sentai dekaranger",
|
||||||
@@ -2453,6 +2497,7 @@ object Parody : TagList {
|
|||||||
"parody:tonagura",
|
"parody:tonagura",
|
||||||
"parody:tonari no kaibutsu-kun",
|
"parody:tonari no kaibutsu-kun",
|
||||||
"parody:tonari no kyuuketsuki-san",
|
"parody:tonari no kyuuketsuki-san",
|
||||||
|
"parody:tongari boushi",
|
||||||
"parody:tongari boushi no atelier",
|
"parody:tongari boushi no atelier",
|
||||||
"parody:tonikaku kawaii",
|
"parody:tonikaku kawaii",
|
||||||
"parody:toradora",
|
"parody:toradora",
|
||||||
@@ -2503,6 +2548,7 @@ object Parody : TagList {
|
|||||||
"parody:uchuu no kishi tekkaman",
|
"parody:uchuu no kishi tekkaman",
|
||||||
"parody:uchuu no stellvia",
|
"parody:uchuu no stellvia",
|
||||||
"parody:uchuu senshi baldios",
|
"parody:uchuu senshi baldios",
|
||||||
|
"parody:uchuu show e youkoso",
|
||||||
"parody:uchuujin tanaka tarou",
|
"parody:uchuujin tanaka tarou",
|
||||||
"parody:ufo princess valkyrie",
|
"parody:ufo princess valkyrie",
|
||||||
"parody:ukagaka",
|
"parody:ukagaka",
|
||||||
@@ -2512,6 +2558,7 @@ object Parody : TagList {
|
|||||||
"parody:uma musume pretty derby",
|
"parody:uma musume pretty derby",
|
||||||
"parody:umi ga kikoeru",
|
"parody:umi ga kikoeru",
|
||||||
"parody:umi monogatari",
|
"parody:umi monogatari",
|
||||||
|
"parody:umibe no etranger",
|
||||||
"parody:umineko no naku koro ni",
|
"parody:umineko no naku koro ni",
|
||||||
"parody:un-go",
|
"parody:un-go",
|
||||||
"parody:unbalance x unbalance",
|
"parody:unbalance x unbalance",
|
||||||
@@ -2541,6 +2588,7 @@ object Parody : TagList {
|
|||||||
"parody:vandread",
|
"parody:vandread",
|
||||||
"parody:vanitas no carte",
|
"parody:vanitas no carte",
|
||||||
"parody:variable geo",
|
"parody:variable geo",
|
||||||
|
"parody:various",
|
||||||
"parody:vatican kiseki chousakan",
|
"parody:vatican kiseki chousakan",
|
||||||
"parody:venus and braves",
|
"parody:venus and braves",
|
||||||
"parody:venus blood -ragnarok-",
|
"parody:venus blood -ragnarok-",
|
||||||
@@ -2550,7 +2598,9 @@ object Parody : TagList {
|
|||||||
"parody:video girl ai",
|
"parody:video girl ai",
|
||||||
"parody:viewtiful joe",
|
"parody:viewtiful joe",
|
||||||
"parody:vindictus",
|
"parody:vindictus",
|
||||||
|
"parody:violated heroine",
|
||||||
"parody:violinist of hameln",
|
"parody:violinist of hameln",
|
||||||
|
"parody:viorate no atelier",
|
||||||
"parody:viper",
|
"parody:viper",
|
||||||
"parody:viper ctr",
|
"parody:viper ctr",
|
||||||
"parody:viper f40",
|
"parody:viper f40",
|
||||||
@@ -2565,6 +2615,7 @@ object Parody : TagList {
|
|||||||
"parody:vocaloid",
|
"parody:vocaloid",
|
||||||
"parody:voiceroid",
|
"parody:voiceroid",
|
||||||
"parody:voltage fighter gowcaizer",
|
"parody:voltage fighter gowcaizer",
|
||||||
|
"parody:vshojo",
|
||||||
"parody:w.i.t.c.h.",
|
"parody:w.i.t.c.h.",
|
||||||
"parody:wagaya no oinari-sama",
|
"parody:wagaya no oinari-sama",
|
||||||
"parody:waka okami wa shougakusei",
|
"parody:waka okami wa shougakusei",
|
||||||
@@ -2583,6 +2634,7 @@ object Parody : TagList {
|
|||||||
"parody:warship girls",
|
"parody:warship girls",
|
||||||
"parody:warzard",
|
"parody:warzard",
|
||||||
"parody:washio sumi wa yuusha de aru",
|
"parody:washio sumi wa yuusha de aru",
|
||||||
|
"parody:watashi ga koibito ni nareru wake nai jan muri muri muri ja nakatta",
|
||||||
"parody:watashi ga motete dousunda",
|
"parody:watashi ga motete dousunda",
|
||||||
"parody:watashi ni tenshi ga maiorita",
|
"parody:watashi ni tenshi ga maiorita",
|
||||||
"parody:watashi no ashinaga ojisan",
|
"parody:watashi no ashinaga ojisan",
|
||||||
@@ -2599,6 +2651,7 @@ object Parody : TagList {
|
|||||||
"parody:white album",
|
"parody:white album",
|
||||||
"parody:wild arms",
|
"parody:wild arms",
|
||||||
"parody:wild arms 2",
|
"parody:wild arms 2",
|
||||||
|
"parody:windbreaker",
|
||||||
"parody:wingman",
|
"parody:wingman",
|
||||||
"parody:wings of honneamise",
|
"parody:wings of honneamise",
|
||||||
"parody:winnie the pooh",
|
"parody:winnie the pooh",
|
||||||
@@ -2657,15 +2710,18 @@ object Parody : TagList {
|
|||||||
"parody:yoake mae yori ruriiro na",
|
"parody:yoake mae yori ruriiro na",
|
||||||
"parody:yofukashi no uta",
|
"parody:yofukashi no uta",
|
||||||
"parody:yokohama kaidashi kikou",
|
"parody:yokohama kaidashi kikou",
|
||||||
|
"parody:yoku wakaru gendai mahou",
|
||||||
"parody:yomawari",
|
"parody:yomawari",
|
||||||
"parody:yondemasuyo azazel-san",
|
"parody:yondemasuyo azazel-san",
|
||||||
"parody:yongbi bulpae",
|
"parody:yongbi bulpae",
|
||||||
"parody:yoroiden samurai troopers",
|
"parody:yoroiden samurai troopers",
|
||||||
"parody:yoru ga kuru",
|
"parody:yoru ga kuru",
|
||||||
|
"parody:yoru no kurage wa oyogenai",
|
||||||
"parody:yoru no yatterman",
|
"parody:yoru no yatterman",
|
||||||
"parody:yoshinaga-san chi no gargoyle",
|
"parody:yoshinaga-san chi no gargoyle",
|
||||||
"parody:yosuga no sora",
|
"parody:yosuga no sora",
|
||||||
"parody:yotsubato",
|
"parody:yotsubato",
|
||||||
|
"parody:you shou yan",
|
||||||
"parody:youjo senki",
|
"parody:youjo senki",
|
||||||
"parody:youjuu senki a.d. 2048",
|
"parody:youjuu senki a.d. 2048",
|
||||||
"parody:youkai hyakkitan",
|
"parody:youkai hyakkitan",
|
||||||
@@ -2714,7 +2770,6 @@ object Parody : TagList {
|
|||||||
"parody:zettai muteki raijin-oh",
|
"parody:zettai muteki raijin-oh",
|
||||||
"parody:zettai shougeki platonic heart",
|
"parody:zettai shougeki platonic heart",
|
||||||
"parody:zettai zetsumei toshi 3",
|
"parody:zettai zetsumei toshi 3",
|
||||||
"parody:zhongfan weilai 1999",
|
|
||||||
"parody:zoids",
|
"parody:zoids",
|
||||||
"parody:zoids genesis",
|
"parody:zoids genesis",
|
||||||
"parody:zoids new century",
|
"parody:zoids new century",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
@@ -64,18 +65,18 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
?: EHentai(0, true, context)
|
?: EHentai(0, true, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val storage = LocalFavoritesStorage()
|
private val storage by lazy { LocalFavoritesStorage() }
|
||||||
|
|
||||||
private val galleryAdder = GalleryAdder()
|
private val galleryAdder by lazy { GalleryAdder() }
|
||||||
|
|
||||||
private val throttleManager = EHentaiThrottleManager()
|
private val throttleManager by lazy { EHentaiThrottleManager() }
|
||||||
|
|
||||||
private var wifiLock: WifiManager.WifiLock? = null
|
private var wifiLock: WifiManager.WifiLock? = null
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
|
|
||||||
private val logger = xLog()
|
private val logger by lazy { xLog() }
|
||||||
|
|
||||||
val status: MutableStateFlow<FavoritesSyncStatus> = MutableStateFlow(FavoritesSyncStatus.Idle(context))
|
val status: MutableStateFlow<FavoritesSyncStatus> = MutableStateFlow(FavoritesSyncStatus.Idle)
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun runSync(scope: CoroutineScope) {
|
fun runSync(scope: CoroutineScope) {
|
||||||
@@ -83,7 +84,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status.value = FavoritesSyncStatus.Initializing(context)
|
status.value = FavoritesSyncStatus.Initializing
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) { beginSync() }
|
scope.launch(Dispatchers.IO) { beginSync() }
|
||||||
}
|
}
|
||||||
@@ -91,13 +92,12 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
private suspend fun beginSync() {
|
private suspend fun beginSync() {
|
||||||
// Check if logged in
|
// Check if logged in
|
||||||
if (!prefs.enableExhentai().get()) {
|
if (!prefs.enableExhentai().get()) {
|
||||||
status.value = FavoritesSyncStatus.Error(context.stringResource(SYMR.strings.please_login))
|
status.value = FavoritesSyncStatus.SyncError.NotLoggedInSyncError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate library state
|
// Validate library state
|
||||||
status.value =
|
status.value = FavoritesSyncStatus.Processing.VerifyingLibrary
|
||||||
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_verifying_library))
|
|
||||||
val libraryManga = getLibraryManga.await()
|
val libraryManga = getLibraryManga.await()
|
||||||
val seenManga = HashSet<Long>(libraryManga.size)
|
val seenManga = HashSet<Long>(libraryManga.size)
|
||||||
libraryManga.forEach { (manga) ->
|
libraryManga.forEach { (manga) ->
|
||||||
@@ -106,7 +106,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
if (manga.id in seenManga) {
|
if (manga.id in seenManga) {
|
||||||
val inCategories = getCategories.await(manga.id)
|
val inCategories = getCategories.await(manga.id)
|
||||||
status.value = FavoritesSyncStatus.BadLibraryState
|
status.value = FavoritesSyncStatus.BadLibraryState
|
||||||
.MangaInMultipleCategories(manga, inCategories, context)
|
.MangaInMultipleCategories(manga.id, manga.title, inCategories.map { it.name })
|
||||||
|
|
||||||
logger.w(context.stringResource(SYMR.strings.favorites_sync_gallery_multiple_categories_error, manga.id))
|
logger.w(context.stringResource(SYMR.strings.favorites_sync_gallery_multiple_categories_error, manga.id))
|
||||||
return
|
return
|
||||||
@@ -117,17 +117,15 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
|
|
||||||
// Download remote favorites
|
// Download remote favorites
|
||||||
val favorites = try {
|
val favorites = try {
|
||||||
status.value =
|
status.value = FavoritesSyncStatus.Processing.DownloadingFavorites
|
||||||
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_downloading))
|
|
||||||
exh.fetchFavorites()
|
exh.fetchFavorites()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
status.value =
|
status.value = FavoritesSyncStatus.SyncError.FailedToFetchFavorites
|
||||||
FavoritesSyncStatus.Error(context.stringResource(SYMR.strings.favorites_sync_failed_to_featch))
|
|
||||||
logger.e(context.stringResource(SYMR.strings.favorites_sync_could_not_fetch), e)
|
logger.e(context.stringResource(SYMR.strings.favorites_sync_could_not_fetch), e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val errorList = mutableListOf<String>()
|
val errorList = mutableListOf<FavoritesSyncStatus.SyncError.GallerySyncError>()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Take wake + wifi locks
|
// Take wake + wifi locks
|
||||||
@@ -157,23 +155,17 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Do not update galleries while syncing favorites
|
// Do not update galleries while syncing favorites
|
||||||
EHentaiUpdateWorker.cancelBackground(context)
|
EHentaiUpdateWorker.cancelBackground(context)
|
||||||
|
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.CalculatingRemoteChanges
|
||||||
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes),
|
|
||||||
)
|
|
||||||
val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
|
val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
|
||||||
val localChanges = if (prefs.exhReadOnlySync().get()) {
|
val localChanges = if (prefs.exhReadOnlySync().get()) {
|
||||||
null // Do not build local changes if they are not going to be applied
|
null // Do not build local changes if they are not going to be applied
|
||||||
} else {
|
} else {
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.CalculatingLocalChanges
|
||||||
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes),
|
|
||||||
)
|
|
||||||
storage.getChangedDbEntries()
|
storage.getChangedDbEntries()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply remote categories
|
// Apply remote categories
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.SyncingCategoryNames
|
||||||
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names),
|
|
||||||
)
|
|
||||||
applyRemoteCategories(favorites.second)
|
applyRemoteCategories(favorites.second)
|
||||||
|
|
||||||
// Apply change sets
|
// Apply change sets
|
||||||
@@ -182,8 +174,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
applyChangeSetToRemote(errorList, localChanges)
|
applyChangeSetToRemote(errorList, localChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
status.value =
|
status.value = FavoritesSyncStatus.Processing.CleaningUp
|
||||||
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_cleaning_up))
|
|
||||||
storage.snapshotEntries()
|
storage.snapshotEntries()
|
||||||
|
|
||||||
withUIContext {
|
withUIContext {
|
||||||
@@ -194,9 +185,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
logger.w(context.stringResource(SYMR.strings.favorites_sync_ignoring_exception), e)
|
logger.w(context.stringResource(SYMR.strings.favorites_sync_ignoring_exception), e)
|
||||||
return
|
return
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
status.value = FavoritesSyncStatus.Error(
|
status.value = FavoritesSyncStatus.SyncError.UnknownSyncError(e.message.orEmpty())
|
||||||
context.stringResource(SYMR.strings.favorites_sync_unknown_error, e.message.orEmpty()),
|
|
||||||
)
|
|
||||||
logger.e(context.stringResource(SYMR.strings.favorites_sync_sync_error), e)
|
logger.e(context.stringResource(SYMR.strings.favorites_sync_sync_error), e)
|
||||||
return
|
return
|
||||||
} finally {
|
} finally {
|
||||||
@@ -215,7 +204,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (errorList.isEmpty()) {
|
if (errorList.isEmpty()) {
|
||||||
status.value = FavoritesSyncStatus.Idle(context)
|
status.value = FavoritesSyncStatus.Idle
|
||||||
} else {
|
} else {
|
||||||
status.value = FavoritesSyncStatus.CompleteWithErrors(errorList)
|
status.value = FavoritesSyncStatus.CompleteWithErrors(errorList)
|
||||||
}
|
}
|
||||||
@@ -249,7 +238,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) {
|
private suspend fun addGalleryRemote(errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>, gallery: FavoriteEntry) {
|
||||||
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
|
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
|
||||||
|
|
||||||
val request = POST(
|
val request = POST(
|
||||||
@@ -263,13 +252,16 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!explicitlyRetryExhRequest(10, request)) {
|
if (!explicitlyRetryExhRequest(10, request)) {
|
||||||
val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!"
|
val error = FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote(
|
||||||
|
gallery.title,
|
||||||
|
gallery.gid,
|
||||||
|
)
|
||||||
|
|
||||||
if (prefs.exhLenientSync().get()) {
|
if (prefs.exhLenientSync().get()) {
|
||||||
errorList += errorString
|
errorList += error
|
||||||
} else {
|
} else {
|
||||||
status.value = FavoritesSyncStatus.Error(errorString)
|
status.value = error
|
||||||
throw IgnoredException(errorString)
|
throw IgnoredException(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,12 +285,13 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun applyChangeSetToRemote(errorList: MutableList<String>, changeSet: ChangeSet) {
|
private suspend fun applyChangeSetToRemote(
|
||||||
|
errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>,
|
||||||
|
changeSet: ChangeSet,
|
||||||
|
) {
|
||||||
// Apply removals
|
// Apply removals
|
||||||
if (changeSet.removed.isNotEmpty()) {
|
if (changeSet.removed.isNotEmpty()) {
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.RemovingRemoteGalleries(changeSet.removed.size)
|
||||||
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, changeSet.removed.size),
|
|
||||||
)
|
|
||||||
|
|
||||||
val formBody = FormBody.Builder()
|
val formBody = FormBody.Builder()
|
||||||
.add("ddact", "delete")
|
.add("ddact", "delete")
|
||||||
@@ -315,13 +308,11 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!explicitlyRetryExhRequest(10, request)) {
|
if (!explicitlyRetryExhRequest(10, request)) {
|
||||||
val errorString = context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
|
|
||||||
|
|
||||||
if (prefs.exhLenientSync().get()) {
|
if (prefs.exhLenientSync().get()) {
|
||||||
errorList += errorString
|
errorList += FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote
|
||||||
} else {
|
} else {
|
||||||
status.value = FavoritesSyncStatus.Error(errorString)
|
status.value = FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote
|
||||||
throw IgnoredException(errorString)
|
throw IgnoredException(FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,10 +320,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Apply additions
|
// Apply additions
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
changeSet.added.forEachIndexed { index, it ->
|
changeSet.added.forEachIndexed { index, it ->
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.AddingGalleryToRemote(
|
||||||
message = context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, index + 1, changeSet.added.size),
|
index = index + 1,
|
||||||
isThrottle = needWarnThrottle(),
|
total = changeSet.added.size,
|
||||||
context = context,
|
isThrottling = needWarnThrottle(),
|
||||||
title = it.title,
|
title = it.title,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -342,14 +333,17 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun applyChangeSetToLocal(errorList: MutableList<String>, changeSet: ChangeSet) {
|
private suspend fun applyChangeSetToLocal(
|
||||||
|
errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>,
|
||||||
|
changeSet: ChangeSet,
|
||||||
|
) {
|
||||||
val removedManga = mutableListOf<Manga>()
|
val removedManga = mutableListOf<Manga>()
|
||||||
|
|
||||||
// Apply removals
|
// Apply removals
|
||||||
changeSet.removed.forEachIndexed { index, it ->
|
changeSet.removed.forEachIndexed { index, it ->
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.RemovingGalleryFromLocal(
|
||||||
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, index + 1, changeSet.removed.size),
|
index = index + 1,
|
||||||
title = it.title,
|
total = changeSet.removed.size,
|
||||||
)
|
)
|
||||||
val url = it.getUrl()
|
val url = it.getUrl()
|
||||||
|
|
||||||
@@ -379,10 +373,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Apply additions
|
// Apply additions
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
changeSet.added.forEachIndexed { index, it ->
|
changeSet.added.forEachIndexed { index, it ->
|
||||||
status.value = FavoritesSyncStatus.Processing(
|
status.value = FavoritesSyncStatus.Processing.AddingGalleryToLocal(
|
||||||
message = context.stringResource(SYMR.strings.favorites_sync_add_to_local, index + 1, changeSet.added.size),
|
index = index + 1,
|
||||||
isThrottle = needWarnThrottle(),
|
total = changeSet.added.size,
|
||||||
context = context,
|
isThrottling = needWarnThrottle(),
|
||||||
title = it.title,
|
title = it.title,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -405,24 +399,23 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
return@forEachIndexed
|
return@forEachIndexed
|
||||||
}
|
}
|
||||||
|
|
||||||
val errorString = context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
|
val error = when (result) {
|
||||||
when (result) {
|
is GalleryAddEvent.Fail.Error -> FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail(
|
||||||
is GalleryAddEvent.Fail.Error -> context.stringResource(
|
it.title, result.logMessage,
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, result.logMessage,
|
)
|
||||||
)
|
is GalleryAddEvent.Fail.UnknownType -> FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail(
|
||||||
is GalleryAddEvent.Fail.UnknownType -> context.stringResource(
|
it.title, result.galleryUrl,
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, result.galleryUrl,
|
)
|
||||||
)
|
is GalleryAddEvent.Fail.UnknownSource -> FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail(
|
||||||
is GalleryAddEvent.Fail.UnknownSource -> context.stringResource(
|
it.title, result.galleryUrl,
|
||||||
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, result.galleryUrl,
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (prefs.exhLenientSync().get()) {
|
if (prefs.exhLenientSync().get()) {
|
||||||
errorList += errorString
|
errorList += error
|
||||||
} else {
|
} else {
|
||||||
status.value = FavoritesSyncStatus.Error(errorString)
|
status.value = error
|
||||||
throw IgnoredException(errorString)
|
throw IgnoredException(error)
|
||||||
}
|
}
|
||||||
} else if (result is GalleryAddEvent.Success) {
|
} else if (result is GalleryAddEvent.Success) {
|
||||||
insertedMangaCategories += categories[it.category].id to result.manga
|
insertedMangaCategories += categories[it.category].id to result.manga
|
||||||
@@ -438,59 +431,85 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
private fun needWarnThrottle() =
|
private fun needWarnThrottle() =
|
||||||
throttleManager.throttleTime >= THROTTLE_WARN
|
throttleManager.throttleTime >= THROTTLE_WARN
|
||||||
|
|
||||||
class IgnoredException(message: String) : RuntimeException(message)
|
class IgnoredException(message: FavoritesSyncStatus.SyncError.GallerySyncError) : RuntimeException(message.toString())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val THROTTLE_WARN = 1.seconds
|
private val THROTTLE_WARN = 1.seconds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class FavoritesSyncStatus() {
|
@Serializable
|
||||||
abstract val message: String
|
sealed class FavoritesSyncStatus {
|
||||||
|
@Serializable
|
||||||
|
sealed class SyncError : FavoritesSyncStatus() {
|
||||||
|
@Serializable
|
||||||
|
data object NotLoggedInSyncError : SyncError()
|
||||||
|
|
||||||
data class Error(override val message: String) : FavoritesSyncStatus()
|
@Serializable
|
||||||
data class Idle(override val message: String) : FavoritesSyncStatus() {
|
data object FailedToFetchFavorites : SyncError()
|
||||||
constructor(context: Context) : this(context.stringResource(SYMR.strings.favorites_sync_waiting_for_start))
|
|
||||||
}
|
@Serializable
|
||||||
sealed class BadLibraryState : FavoritesSyncStatus() {
|
data class UnknownSyncError(val message: String) : SyncError()
|
||||||
data class MangaInMultipleCategories(
|
|
||||||
val manga: Manga,
|
@Serializable
|
||||||
val categories: List<Category>,
|
sealed class GallerySyncError : SyncError() {
|
||||||
override val message: String,
|
@Serializable
|
||||||
) : BadLibraryState() {
|
data class UnableToAddGalleryToRemote(val title: String, val gid: String) : GallerySyncError()
|
||||||
constructor(manga: Manga, categories: List<Category>, context: Context) :
|
|
||||||
this(
|
@Serializable
|
||||||
manga = manga,
|
data object UnableToDeleteFromRemote : GallerySyncError()
|
||||||
categories = categories,
|
|
||||||
message = context.stringResource(
|
@Serializable
|
||||||
SYMR.strings.favorites_sync_gallery_in_multiple_categories, manga.title,
|
data class GalleryAddFail(val title: String, val reason: String) : GallerySyncError()
|
||||||
categories.joinToString {
|
|
||||||
it.name
|
@Serializable
|
||||||
},
|
data class InvalidGalleryFail(val title: String, val url: String) : GallerySyncError()
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data class Initializing(override val message: String) : FavoritesSyncStatus() {
|
|
||||||
constructor(context: Context) : this(context.stringResource(SYMR.strings.favorites_sync_initializing))
|
|
||||||
}
|
|
||||||
data class Processing(
|
|
||||||
override val message: String,
|
|
||||||
val title: String? = null,
|
|
||||||
) : FavoritesSyncStatus() {
|
|
||||||
constructor(message: String, isThrottle: Boolean, context: Context, title: String?) :
|
|
||||||
this(
|
|
||||||
if (isThrottle) {
|
|
||||||
context.stringResource(SYMR.strings.favorites_sync_processing_throttle, message)
|
|
||||||
} else {
|
|
||||||
message
|
|
||||||
},
|
|
||||||
title,
|
|
||||||
)
|
|
||||||
|
|
||||||
val delayedMessage get() = if (title != null) this.message + "\n\n" + title else null
|
@Serializable
|
||||||
|
data object Idle : FavoritesSyncStatus()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class BadLibraryState : FavoritesSyncStatus() {
|
||||||
|
@Serializable
|
||||||
|
data class MangaInMultipleCategories(
|
||||||
|
val mangaId: Long,
|
||||||
|
val mangaTitle: String,
|
||||||
|
val categories: List<String>,
|
||||||
|
) : BadLibraryState()
|
||||||
}
|
}
|
||||||
data class CompleteWithErrors(val messages: List<String>) : FavoritesSyncStatus() {
|
|
||||||
override val message: String = messages.joinToString("\n")
|
@Serializable
|
||||||
|
data object Initializing : FavoritesSyncStatus()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class Processing : FavoritesSyncStatus() {
|
||||||
|
data object VerifyingLibrary : Processing()
|
||||||
|
data object DownloadingFavorites : Processing()
|
||||||
|
data object CalculatingRemoteChanges : Processing()
|
||||||
|
data object CalculatingLocalChanges : Processing()
|
||||||
|
data object SyncingCategoryNames : Processing()
|
||||||
|
data class RemovingRemoteGalleries(val galleryCount: Int) : Processing()
|
||||||
|
data class AddingGalleryToRemote(
|
||||||
|
val index: Int,
|
||||||
|
val total: Int,
|
||||||
|
val isThrottling: Boolean,
|
||||||
|
val title: String,
|
||||||
|
) : Processing()
|
||||||
|
data class RemovingGalleryFromLocal(
|
||||||
|
val index: Int,
|
||||||
|
val total: Int,
|
||||||
|
) : Processing()
|
||||||
|
data class AddingGalleryToLocal(
|
||||||
|
val index: Int,
|
||||||
|
val total: Int,
|
||||||
|
val isThrottling: Boolean,
|
||||||
|
val title: String,
|
||||||
|
) : Processing()
|
||||||
|
data object CleaningUp : Processing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CompleteWithErrors(val messages: List<SyncError.GallerySyncError>) : FavoritesSyncStatus()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -25,6 +24,7 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
|||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import exh.ui.ifSourcesLoaded
|
import exh.ui.ifSourcesLoaded
|
||||||
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
@@ -65,11 +65,9 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
|
|||||||
SnackbarHost(hostState = snackbarHostState)
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
|
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
|
||||||
|
|||||||
@@ -128,6 +128,36 @@ class MangaHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaMetadata(
|
||||||
|
track: Track,
|
||||||
|
sourceId: Long,
|
||||||
|
coverQuality: String,
|
||||||
|
tryUsingFirstVolumeCover: Boolean,
|
||||||
|
altTitlesInDesc: Boolean,
|
||||||
|
): SManga? {
|
||||||
|
return withIOContext {
|
||||||
|
val mangaId = MdUtil.getMangaId(track.tracking_url)
|
||||||
|
val response = service.viewManga(mangaId)
|
||||||
|
val coverFileName = if (tryUsingFirstVolumeCover) {
|
||||||
|
service.fetchFirstVolumeCover(response)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
apiMangaParser.parseToManga(
|
||||||
|
SManga.create().apply {
|
||||||
|
url = track.tracking_url
|
||||||
|
},
|
||||||
|
sourceId,
|
||||||
|
response,
|
||||||
|
emptyList(),
|
||||||
|
null,
|
||||||
|
coverFileName,
|
||||||
|
coverQuality,
|
||||||
|
altTitlesInDesc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getSimpleChapters(manga: SManga): List<String> {
|
private suspend fun getSimpleChapters(manga: SManga): List<String> {
|
||||||
return runCatching { service.aggregateChapters(MdUtil.getMangaId(manga.url), lang) }
|
return runCatching { service.aggregateChapters(MdUtil.getMangaId(manga.url), lang) }
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ package exh.md.similar
|
|||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -16,6 +13,7 @@ import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar
|
|||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import exh.ui.ifSourcesLoaded
|
import exh.ui.ifSourcesLoaded
|
||||||
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@@ -52,11 +50,9 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
|
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = false,
|
ehentaiBrowseDisplayMode = false,
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ package exh.recs
|
|||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
@@ -17,6 +14,7 @@ import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar
|
|||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
||||||
import exh.ui.ifSourcesLoaded
|
import exh.ui.ifSourcesLoaded
|
||||||
|
import mihon.presentation.core.util.collectAsLazyPagingItems
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
@@ -51,11 +49,9 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
|
|||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
|
|
||||||
|
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
mangaList = pagingFlow.collectAsLazyPagingItems(),
|
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
// SY -->
|
// SY -->
|
||||||
ehentaiBrowseDisplayMode = false,
|
ehentaiBrowseDisplayMode = false,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class BatchAddScreen : Screen() {
|
|||||||
text = stringResource(SYMR.strings.eh_batch_add_description),
|
text = stringResource(SYMR.strings.eh_batch_add_description),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(autoCorrect = false),
|
keyboardOptions = KeyboardOptions(autoCorrectEnabled = false),
|
||||||
textStyle = MaterialTheme.typography.bodyLarge,
|
textStyle = MaterialTheme.typography.bodyLarge,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
@@ -180,15 +181,17 @@ class InterceptActivity : BaseActivity() {
|
|||||||
status.value = InterceptResult.Loading
|
status.value = InterceptResult.Loading
|
||||||
val sources = galleryAdder.pickSource(gallery)
|
val sources = galleryAdder.pickSource(gallery)
|
||||||
if (sources.size > 1) {
|
if (sources.size > 1) {
|
||||||
MaterialAlertDialogBuilder(this)
|
withUIContext {
|
||||||
.setTitle(MR.strings.label_sources.getString(this))
|
MaterialAlertDialogBuilder(this@InterceptActivity)
|
||||||
.setSingleChoiceItems(sources.map { it.toString() }.toTypedArray(), 0) { dialog, index ->
|
.setTitle(MR.strings.label_sources.getString(this@InterceptActivity))
|
||||||
dialog.dismiss()
|
.setSingleChoiceItems(sources.map { it.toString() }.toTypedArray(), 0) { dialog, index ->
|
||||||
lifecycleScope.launchIO {
|
dialog.dismiss()
|
||||||
loadGalleryEnd(gallery, sources[index])
|
lifecycleScope.launchIO {
|
||||||
|
loadGalleryEnd(gallery, sources[index])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.show()
|
||||||
.show()
|
}
|
||||||
} else {
|
} else {
|
||||||
loadGalleryEnd(gallery)
|
loadGalleryEnd(gallery)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,24 +130,40 @@
|
|||||||
android:layout_marginBottom="12dp" />
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/reset_tags"
|
android:id="@+id/autofill_from_tracker"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:text="@string/reset_tags"
|
android:text="@string/fill_from_tracker"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
<Button
|
<LinearLayout
|
||||||
android:id="@+id/reset_info"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:orientation="horizontal">
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
<Button
|
||||||
android:text="@string/reset_info"
|
android:id="@+id/reset_tags"
|
||||||
android:textAllCaps="false" />
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:text="@string/reset_tags"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/reset_info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:text="@string/reset_info"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<changelog bulletedList="true">
|
<changelog bulletedList="true">
|
||||||
|
<changelogversion versionName="1.11.0" changeDate="Oct 27,2024">
|
||||||
|
<changelogtext>[b]Based on Mihon stable 0.17.0(from 0.16.4)[/b]</changelogtext>
|
||||||
|
<changelogtext>[b]Add Cross-Device Sync (Thanks @kaiserbh)[/b]</changelogtext>
|
||||||
|
<changelogtext>Add mature to the lewd tag filter(Thanks @gelionexists)</changelogtext>
|
||||||
|
<changelogtext>Add reset custom manga info (Thanks @LetrixZ)</changelogtext>
|
||||||
|
<changelogtext>Add multiple tags at once in edit manga info(Thanks @NGB-Was-Taken)</changelogtext>
|
||||||
|
<changelogtext>Fix sources loading too late for some screens</changelogtext>
|
||||||
|
<changelogtext>Show Downloaded stage in reader chapter list(Thanks @NGB-Was-Taken)</changelogtext>
|
||||||
|
<changelogtext>Fix Anilist</changelogtext>
|
||||||
|
<changelogtext>Fix MAL</changelogtext>
|
||||||
|
<changelogtext>Add an option to only show entries with new chapters(Thanks @timschneeb)</changelogtext>
|
||||||
|
<changelogtext>Fix Merged Source chapters not showing source name</changelogtext>
|
||||||
|
<changelogtext>Use DownloadChapters merged manga setting(Thanks @cuong-tran)</changelogtext>
|
||||||
|
<changelogtext>Show local chapters as downloaded in Merged Manga(Thanks @NGB-Was-Taken)</changelogtext>
|
||||||
|
<changelogtext>Fix ExHentai Page List errors</changelogtext>
|
||||||
|
<changelogtext>Improve E-H/Exh chapter downloading</changelogtext>
|
||||||
|
<changelogtext>Make manga page preview rows configurable(Thanks @LetrixZ)</changelogtext>
|
||||||
|
<changelogtext>Use more of Mangadex Extension settings(Thanks @NGB-Was-Taken)</changelogtext>
|
||||||
|
<changelogtext>Improve Encrypted Downloads(Thanks @Shamicen)</changelogtext>
|
||||||
|
<changelogtext>Delete duplicate chapters when they are marked as read(Thanks @NGB-Was-Taken)</changelogtext>
|
||||||
|
</changelogversion>
|
||||||
<changelogversion versionName="1.10.5" changeDate="Mar 2,2024">
|
<changelogversion versionName="1.10.5" changeDate="Mar 2,2024">
|
||||||
<changelogtext>[b]Based on Mihon stable 0.16.4(from 0.16.3)[/b]</changelogtext>
|
<changelogtext>[b]Based on Mihon stable 0.16.4(from 0.16.3)[/b]</changelogtext>
|
||||||
<changelogtext>Minor fix for mark duplicate chapters as read</changelogtext>
|
<changelogtext>Minor fix for mark duplicate chapters as read</changelogtext>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import okio.source
|
|||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.koin.core.context.startKoin
|
||||||
|
import org.koin.dsl.module
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
|
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
|
||||||
@@ -26,8 +28,6 @@ import tachiyomi.domain.manga.model.CustomMangaInfo
|
|||||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.repository.CustomMangaRepository
|
import tachiyomi.domain.manga.repository.CustomMangaRepository
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.addSingletonFactory
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class Tester {
|
class Tester {
|
||||||
@@ -165,11 +165,17 @@ class Tester {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
fun before() {
|
fun before() {
|
||||||
Injekt.addSingletonFactory {
|
startKoin {
|
||||||
GetCustomMangaInfo(
|
modules(
|
||||||
object : CustomMangaRepository {
|
module {
|
||||||
override fun get(mangaId: Long) = null
|
single {
|
||||||
override fun set(mangaInfo: CustomMangaInfo) = Unit
|
GetCustomMangaInfo(
|
||||||
|
object : CustomMangaRepository {
|
||||||
|
override fun get(mangaId: Long) = null
|
||||||
|
override fun set(mangaInfo: CustomMangaInfo) = Unit
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *,
|
|||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(AndroidConfig.JvmTarget)
|
jvmTarget.set(AndroidConfig.JvmTarget)
|
||||||
freeCompilerArgs.addAll(
|
freeCompilerArgs.addAll(
|
||||||
"-opt-in=kotlin.RequiresOptIn",
|
|
||||||
"-Xcontext-receivers",
|
"-Xcontext-receivers",
|
||||||
|
"-opt-in=kotlin.RequiresOptIn",
|
||||||
)
|
)
|
||||||
|
|
||||||
// Treat all Kotlin warnings as errors (disabled by default)
|
// Treat all Kotlin warnings as errors (disabled by default)
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "eu.kanade.tachiyomi.core.common"
|
namespace = "eu.kanade.tachiyomi.core.common"
|
||||||
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlin {
|
||||||
freeCompilerArgs += listOf(
|
compilerOptions {
|
||||||
"-Xcontext-receivers",
|
freeCompilerArgs.addAll(
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class NetworkPreferences(
|
|||||||
fun defaultUserAgent(): Preference<String> {
|
fun defaultUserAgent(): Preference<String> {
|
||||||
return preferenceStore.getString(
|
return preferenceStore.getString(
|
||||||
"default_user_agent",
|
"default_user_agent",
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ object DeviceUtil {
|
|||||||
|
|
||||||
val invalidDefaultBrowsers = listOf(
|
val invalidDefaultBrowsers = listOf(
|
||||||
"android",
|
"android",
|
||||||
|
"com.hihonor.android.internal.app",
|
||||||
"com.huawei.android.internal.app",
|
"com.huawei.android.internal.app",
|
||||||
"com.zui.resolver",
|
"com.zui.resolver",
|
||||||
)
|
)
|
||||||
|
|||||||
+18
-5
@@ -6,7 +6,7 @@ import javax.microedition.khronos.egl.EGLContext
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
object GLUtil {
|
object GLUtil {
|
||||||
val maxTextureSize: Int by lazy {
|
val DEVICE_TEXTURE_LIMIT: Int by lazy {
|
||||||
// Get EGL Display
|
// Get EGL Display
|
||||||
val egl = EGLContext.getEGL() as EGL10
|
val egl = EGLContext.getEGL() as EGL10
|
||||||
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
|
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
|
||||||
@@ -38,10 +38,23 @@ object GLUtil {
|
|||||||
// Release
|
// Release
|
||||||
egl.eglTerminate(display)
|
egl.eglTerminate(display)
|
||||||
|
|
||||||
// Return largest texture size found, or default
|
// Return largest texture size found (after making it a multiplier of [Multiplier]), or default
|
||||||
max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION)
|
max(maximumTextureSize, SAFE_TEXTURE_LIMIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
const val SAFE_TEXTURE_LIMIT: Int = 2048
|
||||||
|
|
||||||
|
val CUSTOM_TEXTURE_LIMIT_OPTIONS: List<Int> by lazy {
|
||||||
|
val steps = DEVICE_TEXTURE_LIMIT / MULTIPLIER
|
||||||
|
buildList(steps) {
|
||||||
|
add(DEVICE_TEXTURE_LIMIT)
|
||||||
|
for (step in steps downTo 2) {
|
||||||
|
val value = step * MULTIPLIER
|
||||||
|
if (value >= DEVICE_TEXTURE_LIMIT) continue
|
||||||
|
add(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safe minimum default size
|
private const val MULTIPLIER: Int = 1024
|
||||||
private const val IMAGE_MAX_BITMAP_DIMENSION = 2048
|
|
||||||
@@ -73,7 +73,6 @@ fun WebView.setDefaultSettings() {
|
|||||||
with(settings) {
|
with(settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
domStorageEnabled = true
|
domStorageEnabled = true
|
||||||
databaseEnabled = true
|
|
||||||
useWideViewPort = true
|
useWideViewPort = true
|
||||||
loadWithOverviewMode = true
|
loadWithOverviewMode = true
|
||||||
cacheMode = WebSettings.LOAD_DEFAULT
|
cacheMode = WebSettings.LOAD_DEFAULT
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.core.graphics.green
|
|||||||
import androidx.core.graphics.red
|
import androidx.core.graphics.red
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
@@ -359,6 +360,23 @@ object ImageUtil {
|
|||||||
val bottomOffset = topOffset + splitHeight
|
val bottomOffset = topOffset + splitHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canUseHardwareBitmap(bitmap: Bitmap): Boolean {
|
||||||
|
return canUseHardwareBitmap(bitmap.width, bitmap.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun canUseHardwareBitmap(imageSource: BufferedSource): Boolean {
|
||||||
|
return with(extractImageOptions(imageSource)) {
|
||||||
|
canUseHardwareBitmap(outWidth, outHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hardwareBitmapThreshold: Int = GLUtil.SAFE_TEXTURE_LIMIT
|
||||||
|
|
||||||
|
private fun canUseHardwareBitmap(width: Int, height: Int): Boolean {
|
||||||
|
if (HARDWARE_BITMAP_UNSUPPORTED) return false
|
||||||
|
return maxOf(width, height) <= hardwareBitmapThreshold
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Algorithm for determining what background to accompany a comic/manga page
|
* Algorithm for determining what background to accompany a comic/manga page
|
||||||
*/
|
*/
|
||||||
@@ -623,6 +641,121 @@ object ImageUtil {
|
|||||||
|
|
||||||
private val optimalImageHeight = getDisplayMaxHeightInPx * 2
|
private val optimalImageHeight = getDisplayMaxHeightInPx * 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken from Coil
|
||||||
|
* (https://github.com/coil-kt/coil/blob/1674d3516f061aeacbe749a435b1924f9648fd41/coil-core/src/androidMain/kotlin/coil3/util/hardwareBitmaps.kt)
|
||||||
|
* ---
|
||||||
|
* Maintains a list of devices with broken/incomplete/unstable hardware bitmap implementations.
|
||||||
|
*
|
||||||
|
* Model names are retrieved from
|
||||||
|
* [Google's official device list](https://support.google.com/googleplay/answer/1727131?hl=en).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
val HARDWARE_BITMAP_UNSUPPORTED = when (Build.VERSION.SDK_INT) {
|
||||||
|
26 -> run {
|
||||||
|
val model = Build.MODEL ?: return@run false
|
||||||
|
|
||||||
|
// Samsung Galaxy (ALL)
|
||||||
|
if (model.removePrefix("SAMSUNG-").startsWith("SM-")) return@run true
|
||||||
|
|
||||||
|
val device = Build.DEVICE ?: return@run false
|
||||||
|
|
||||||
|
return@run device in arrayOf(
|
||||||
|
"nora", "nora_8917", "nora_8917_n", // Moto E5
|
||||||
|
"james", "rjames_f", "rjames_go", "pettyl", // Moto E5 Play
|
||||||
|
"hannah", "ahannah", "rhannah", // Moto E5 Plus
|
||||||
|
|
||||||
|
"ali", "ali_n", // Moto G6
|
||||||
|
"aljeter", "aljeter_n", "jeter", // Moto G6 Play
|
||||||
|
"evert", "evert_n", "evert_nt", // Moto G6 Plus
|
||||||
|
|
||||||
|
"G3112", "G3116", "G3121", "G3123", "G3125", // Xperia XA1
|
||||||
|
"G3412", "G3416", "G3421", "G3423", "G3426", // Xperia XA1 Plus
|
||||||
|
"G3212", "G3221", "G3223", "G3226", // Xperia XA1 Ultra
|
||||||
|
|
||||||
|
"BV6800Pro", // BlackView BV6800Pro
|
||||||
|
"CatS41", // Cat S41
|
||||||
|
"Hi9Pro", // CHUWI Hi9 Pro
|
||||||
|
"manning", // Lenovo K8 Note
|
||||||
|
"N5702L", // NUU Mobile G3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
27 -> run {
|
||||||
|
val device = Build.DEVICE ?: return@run false
|
||||||
|
|
||||||
|
return@run device in arrayOf(
|
||||||
|
"mcv1s", // LG Tribute Empire
|
||||||
|
"mcv3", // LG K11
|
||||||
|
"mcv5a", // LG Q7
|
||||||
|
"mcv7a", // LG Stylo 4
|
||||||
|
|
||||||
|
"A30ATMO", // T-Mobile REVVL 2
|
||||||
|
"A70AXLTMO", // T-Mobile REVVL 2 PLUS
|
||||||
|
|
||||||
|
"A3A_8_4G_TMO", // Alcatel 9027W
|
||||||
|
"Edison_CKT", // Alcatel ONYX
|
||||||
|
"EDISON_TF", // Alcatel TCL XL2
|
||||||
|
"FERMI_TF", // Alcatel A501DL
|
||||||
|
"U50A_ATT", // Alcatel TETRA
|
||||||
|
"U50A_PLUS_ATT", // Alcatel 5059R
|
||||||
|
"U50A_PLUS_TF", // Alcatel TCL LX
|
||||||
|
"U50APLUSTMO", // Alcatel 5059Z
|
||||||
|
"U5A_PLUS_4G", // Alcatel 1X
|
||||||
|
|
||||||
|
"RCT6513W87DK5e", // RCA Galileo Pro
|
||||||
|
"RCT6873W42BMF9A", // RCA Voyager
|
||||||
|
"RCT6A03W13", // RCA 10 Viking
|
||||||
|
"RCT6B03W12", // RCA Atlas 10 Pro
|
||||||
|
"RCT6B03W13", // RCA Atlas 10 Pro+
|
||||||
|
"RCT6T06E13", // RCA Artemis 10
|
||||||
|
|
||||||
|
"A3_Pro", // Umidigi A3 Pro
|
||||||
|
"One", // Umidigi One
|
||||||
|
"One_Max", // Umidigi One Max
|
||||||
|
"One_Pro", // Umidigi One Pro
|
||||||
|
"Z2", // Umidigi Z2
|
||||||
|
"Z2_PRO", // Umidigi Z2 Pro
|
||||||
|
|
||||||
|
"Armor_3", // Ulefone Armor 3
|
||||||
|
"Armor_6", // Ulefone Armor 6
|
||||||
|
|
||||||
|
"Blackview", // Blackview BV6000
|
||||||
|
"BV9500", // Blackview BV9500
|
||||||
|
"BV9500Pro", // Blackview BV9500Pro
|
||||||
|
|
||||||
|
"A6L-C", // Nuu A6L-C
|
||||||
|
"N5002LA", // Nuu A7L
|
||||||
|
"N5501LA", // Nuu A5L
|
||||||
|
|
||||||
|
"Power_2_Pro", // Leagoo Power 2 Pro
|
||||||
|
"Power_5", // Leagoo Power 5
|
||||||
|
"Z9", // Leagoo Z9
|
||||||
|
|
||||||
|
"V0310WW", // Blu VIVO VI+
|
||||||
|
"V0330WW", // Blu VIVO XI
|
||||||
|
|
||||||
|
"A3", // BenQ A3
|
||||||
|
"ASUS_X018_4", // Asus ZenFone Max Plus M1 (ZB570TL)
|
||||||
|
"C210AE", // Wiko Life
|
||||||
|
"fireball", // DROID Incredible 4G LTE
|
||||||
|
"ILA_X1", // iLA X1
|
||||||
|
"Infinix-X605_sprout", // Infinix NOTE 5 Stylus
|
||||||
|
"j7maxlte", // Samsung Galaxy J7 Max
|
||||||
|
"KING_KONG_3", // Cubot King Kong 3
|
||||||
|
"M10500", // Packard Bell M10500
|
||||||
|
"S70", // Altice ALTICE S70
|
||||||
|
"S80Lite", // Doogee S80Lite
|
||||||
|
"SGINO6", // SGiNO 6
|
||||||
|
"st18c10bnn", // Barnes and Noble BNTV650
|
||||||
|
"TECNO-CA8", // Tecno CAMON X Pro,
|
||||||
|
"SHIFT6m", // SHIFT 6m
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
fun mergeBitmaps(
|
fun mergeBitmaps(
|
||||||
imageBitmap: Bitmap,
|
imageBitmap: Bitmap,
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs.add("-opt-in=kotlinx.serialization.ExperimentalSerializationApi")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.sourceApi)
|
implementation(projects.sourceApi)
|
||||||
implementation(projects.domain)
|
implementation(projects.domain)
|
||||||
@@ -30,12 +36,3 @@ dependencies {
|
|||||||
|
|
||||||
api(libs.bundles.sqldelight)
|
api(libs.bundles.sqldelight)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
|
||||||
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
compilerOptions.freeCompilerArgs.addAll(
|
|
||||||
"-Xcontext-receivers",
|
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.sourceApi)
|
implementation(projects.sourceApi)
|
||||||
implementation(projects.core.common)
|
implementation(projects.core.common)
|
||||||
@@ -34,12 +40,3 @@ dependencies {
|
|||||||
testImplementation(libs.bundles.test)
|
testImplementation(libs.bundles.test)
|
||||||
testImplementation(kotlinx.coroutines.test)
|
testImplementation(kotlinx.coroutines.test)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
|
||||||
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
compilerOptions.freeCompilerArgs.addAll(
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-Xcontext-receivers",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import mihon.domain.extensionrepo.exception.SaveExtensionRepoException
|
|||||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||||
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
|
||||||
class CreateExtensionRepo(
|
class CreateExtensionRepo(
|
||||||
@@ -13,12 +14,13 @@ class CreateExtensionRepo(
|
|||||||
) {
|
) {
|
||||||
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
|
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
|
||||||
|
|
||||||
suspend fun await(repoUrl: String): Result {
|
suspend fun await(indexUrl: String): Result {
|
||||||
if (!repoUrl.matches(repoRegex)) {
|
val formattedIndexUrl = indexUrl.toHttpUrlOrNull()
|
||||||
return Result.InvalidUrl
|
?.toString()
|
||||||
}
|
?.takeIf { it.matches(repoRegex) }
|
||||||
|
?: return Result.InvalidUrl
|
||||||
|
|
||||||
val baseUrl = repoUrl.removeSuffix("/index.min.json")
|
val baseUrl = formattedIndexUrl.removeSuffix("/index.min.json")
|
||||||
return service.fetchRepoDetails(baseUrl)?.let { insert(it) } ?: Result.InvalidUrl
|
return service.fetchRepoDetails(baseUrl)?.let { insert(it) } ?: Result.InvalidUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package mihon.domain.extensionrepo.service
|
package mihon.domain.extensionrepo.service
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
@@ -21,11 +20,9 @@ class ExtensionRepoService(
|
|||||||
repo: String,
|
repo: String,
|
||||||
): ExtensionRepo? {
|
): ExtensionRepo? {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val url = "$repo/repo.json".toUri()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
with(json) {
|
with(json) {
|
||||||
client.newCall(GET(url.toString()))
|
client.newCall(GET("$repo/repo.json"))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<ExtensionRepoMetaDto>()
|
.parseAs<ExtensionRepoMetaDto>()
|
||||||
.toExtensionRepo(baseUrl = repo)
|
.toExtensionRepo(baseUrl = repo)
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp_version = "8.7.1"
|
agp_version = "8.7.2"
|
||||||
lifecycle_version = "2.8.6"
|
lifecycle_version = "2.8.7"
|
||||||
paging_version = "3.3.2"
|
paging_version = "3.3.4"
|
||||||
interpolator_version = "1.0.0"
|
interpolator_version = "1.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
|
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
|
||||||
|
|
||||||
annotation = "androidx.annotation:annotation:1.9.0"
|
annotation = "androidx.annotation:annotation:1.9.1"
|
||||||
appcompat = "androidx.appcompat:appcompat:1.7.0"
|
appcompat = "androidx.appcompat:appcompat:1.7.0"
|
||||||
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||||
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.0"
|
||||||
corektx = "androidx.core:core-ktx:1.13.1"
|
corektx = "androidx.core:core-ktx:1.15.0"
|
||||||
splashscreen = "androidx.core:core-splashscreen:1.0.1"
|
splashscreen = "androidx.core:core-splashscreen:1.0.1"
|
||||||
recyclerview = "androidx.recyclerview:recyclerview:1.3.2"
|
recyclerview = "androidx.recyclerview:recyclerview:1.3.2"
|
||||||
viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01"
|
viewpager = "androidx.viewpager:viewpager:1.1.0-rc01"
|
||||||
profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1"
|
profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1"
|
||||||
|
|
||||||
lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" }
|
lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" }
|
||||||
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" }
|
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" }
|
||||||
lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" }
|
lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" }
|
||||||
|
|
||||||
workmanager = "androidx.work:work-runtime:2.9.1"
|
workmanager = "androidx.work:work-runtime:2.10.0"
|
||||||
|
|
||||||
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
|
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
|
||||||
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
|
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user