Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10c7019aeb | ||
|
|
76aa401800 | ||
|
|
dab5878f74 | ||
|
|
ab8671cc3b | ||
|
|
2e52dda208 | ||
|
|
1ab08d0ae1 | ||
|
|
702be0ed43 | ||
|
|
4067543701 | ||
|
|
074689c60e | ||
|
|
2beb715df3 | ||
|
|
7ea2597899 | ||
|
|
34a8f92865 | ||
|
|
6e2a2de13f | ||
|
|
e073232352 | ||
|
|
6f49b4d23e | ||
|
|
97cad20ab1 | ||
|
|
9300a3d9ce | ||
|
|
28b7f80a8f | ||
|
|
a06d23753a | ||
|
|
98d66b3e6c | ||
|
|
6e59583dea | ||
|
|
2f0571e384 | ||
|
|
64a6074e02 | ||
|
|
0b7cb4cf41 | ||
|
|
e620832e33 | ||
|
|
f82e4af0e3 |
No files matched your search
@@ -1,3 +1,4 @@
|
||||
[*.{kt,kts}]
|
||||
ij_kotlin_imports_layout = *
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||
@@ -18,11 +18,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 21
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
java-version: 17
|
||||
|
||||
- name: Install Rust Toolchain
|
||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
path: |
|
||||
app/.cxx
|
||||
app/src/main/cpp/nettle/nettle
|
||||
key: ${{ runner.os }}-cmake-${{ github.run_id }}
|
||||
key: ${{ runner.os }}-cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cmake-
|
||||
|
||||
- name: Build OSS
|
||||
|
||||
@@ -15,11 +15,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 21
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
java-version: 17
|
||||
|
||||
- name: Install Rust Toolchain
|
||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
path: |
|
||||
app/.cxx
|
||||
app/src/main/cpp/nettle/nettle
|
||||
key: ${{ runner.os }}-cmake-${{ github.run_id }}
|
||||
key: ${{ runner.os }}-cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cmake-
|
||||
|
||||
- name: Gradle Build
|
||||
|
||||
+3
-12
@@ -65,7 +65,7 @@ android {
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = 180044
|
||||
versionName = "1.8.10.0"
|
||||
versionName = "1.8.10.2"
|
||||
resourceConfigurations.addAll(
|
||||
listOf(
|
||||
"zh",
|
||||
@@ -209,6 +209,7 @@ dependencies {
|
||||
implementation(libs.bundles.compose)
|
||||
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
|
||||
implementation(libs.androidx.constraintlayout.compose)
|
||||
implementation(libs.androidx.coordinatorlayout)
|
||||
@@ -275,7 +276,7 @@ dependencies {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
ksp {
|
||||
@@ -286,16 +287,6 @@ ksp {
|
||||
aboutLibraries {
|
||||
duplicationMode = MERGE
|
||||
duplicationRule = GROUP
|
||||
exclusionPatterns = listOf(
|
||||
"androidx\\..*".toPattern(),
|
||||
".*annotations".toPattern(),
|
||||
"com\\.google\\.code\\..*".toPattern(),
|
||||
"com\\.google\\.guava:listenablefuture".toPattern(),
|
||||
"com\\.google\\.protobuf:protobuf-javalite".toPattern(),
|
||||
"org\\.checkerframework:checker-qual".toPattern(),
|
||||
"org\\.chromium\\.net:cronet-embedded".toPattern(),
|
||||
"org\\.slf4j:slf4j-api".toPattern(),
|
||||
)
|
||||
}
|
||||
|
||||
cargo {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.hippo.ehviewer.cronet
|
||||
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.client.CHROME_ACCEPT
|
||||
import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
@@ -17,16 +20,6 @@ import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import splitties.init.appCtx
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
private const val TAG = "CronetRequest"
|
||||
val pool = DirectByteBufferPool(32)
|
||||
|
||||
val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
|
||||
enableBrotli(true)
|
||||
@@ -36,9 +29,6 @@ val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
|
||||
setUserAgent(CHROME_USER_AGENT)
|
||||
}.build()
|
||||
|
||||
val cronetHttpClientExecutor = EhApplication.baseOkHttpClient.dispatcher.executorService
|
||||
|
||||
// TODO: Rewrite this to use android.net.http.HttpEngine and make it Android 14 only when released
|
||||
class CronetRequest {
|
||||
lateinit var consumer: (ByteBuffer) -> Unit
|
||||
lateinit var onResponse: CronetRequest.(UrlResponseInfo) -> Unit
|
||||
@@ -47,7 +37,6 @@ class CronetRequest {
|
||||
lateinit var readerCont: Continuation<Unit>
|
||||
val callback = object : UrlRequest.Callback() {
|
||||
override fun onRedirectReceived(req: UrlRequest, info: UrlResponseInfo, url: String) {
|
||||
logcat(tag = TAG) { "Redirected to $url" }
|
||||
req.followRedirect()
|
||||
}
|
||||
|
||||
@@ -79,29 +68,6 @@ inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.
|
||||
}.apply(conf).build()
|
||||
}
|
||||
|
||||
suspend inline fun CronetRequest.awaitBodyFully(crossinline callback: (ByteBuffer) -> Unit) {
|
||||
val buffer = pool.borrow()
|
||||
return try {
|
||||
suspendCancellableCoroutine { cont ->
|
||||
consumer = {
|
||||
callback(it)
|
||||
buffer.clear()
|
||||
request.read(buffer)
|
||||
}
|
||||
onError = { readerCont.resumeWithException(it) }
|
||||
readerCont = cont
|
||||
request.read(buffer)
|
||||
}
|
||||
} finally {
|
||||
pool.recycle(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun CronetRequest.copyToChannel(chan: FileChannel, crossinline listener: ((Int) -> Unit) = {}) = awaitBodyFully {
|
||||
val bytes = chan.write(it)
|
||||
listener(bytes)
|
||||
}
|
||||
|
||||
suspend inline fun <R> CronetRequest.execute(crossinline callback: suspend CronetRequest.(UrlResponseInfo) -> R): R {
|
||||
contract {
|
||||
callsInPlace(callback, InvocationKind.EXACTLY_ONCE)
|
||||
|
||||
@@ -2,10 +2,11 @@ package com.hippo.ehviewer.util
|
||||
|
||||
import com.google.android.gms.net.CronetProviderInstaller
|
||||
import com.hippo.ehviewer.Settings
|
||||
import org.chromium.net.CronetProvider
|
||||
import splitties.init.appCtx
|
||||
|
||||
val isCronetSupported: Boolean
|
||||
get() = Settings.enableCronet && CronetProviderInstaller.isInstalled().also {
|
||||
get() = Settings.enableCronet && CronetProvider.getAllProviders(appCtx).any { it.isEnabled }.also {
|
||||
if (!it) {
|
||||
CronetProviderInstaller.installProvider(appCtx)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
android:configChanges="screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme.Splash"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
+11
-11
@@ -23,14 +23,6 @@ import com.davemorrissey.labs.subscaleview.decoder.Decoder
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.locks.ReadWriteLock
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
@@ -39,6 +31,14 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sqrt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Displays an image subsampled as necessary to avoid loading too much image data into memory. After zooming in,
|
||||
@@ -601,8 +601,8 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
|
||||
private fun onTouchEventInternal(event: MotionEvent): Boolean {
|
||||
val touchCount = event.pointerCount
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_1_DOWN, MotionEvent.ACTION_POINTER_2_DOWN -> {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
anim = null
|
||||
requestDisallowInterceptTouchEvent(true)
|
||||
maxTouchCount = maxTouchCount.coerceAtLeast(touchCount)
|
||||
@@ -773,7 +773,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
return true
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_POINTER_2_UP -> {
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
|
||||
longClickJob?.cancel()
|
||||
if (isQuickScaling) {
|
||||
isQuickScaling = false
|
||||
|
||||
@@ -4,6 +4,8 @@ package com.hippo.ehviewer
|
||||
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import java.util.Locale
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
@@ -12,8 +14,6 @@ import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import splitties.preferences.DataStorePreferences
|
||||
import splitties.preferences.edit
|
||||
import java.util.Locale
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
object Settings : DataStorePreferences(null) {
|
||||
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import android.webkit.CookieManager
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import androidx.compose.ui.util.fastFilter
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastJoinToString
|
||||
import androidx.compose.ui.util.fastMapNotNull
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
@@ -24,13 +29,15 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
object EhCookieStore : CookieJar {
|
||||
private val manager = CookieManager.getInstance()
|
||||
fun signOut() = manager.removeAllCookies(null)
|
||||
fun contains(url: HttpUrl, name: String) = get(url).any { it.name == name }
|
||||
fun contains(url: HttpUrl, name: String) = get(url).fastAny { it.name == name }
|
||||
|
||||
fun get(url: HttpUrl): List<Cookie> {
|
||||
val cookies = manager.getCookie(url.toString())
|
||||
|
||||
return if (cookies != null && cookies.isNotEmpty()) {
|
||||
cookies.split(";").mapNotNull { Cookie.parse(url, it) }.filterNot { it.name == KEY_UTMP_NAME }
|
||||
return if (!cookies.isNullOrEmpty()) {
|
||||
cookies.split(";").fastMapNotNull { setCookie ->
|
||||
Cookie.parse(url, setCookie)?.takeUnless { it.name == KEY_UTMP_NAME }
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
@@ -57,9 +64,12 @@ object EhCookieStore : CookieJar {
|
||||
}.build()
|
||||
|
||||
fun copyNecessaryCookies() {
|
||||
val cookie = get(EhUrl.HOST_E.toHttpUrl()).filter { it.name == KEY_STAR || it.name == KEY_IPB_MEMBER_ID || it.name == KEY_IPB_PASS_HASH || it.name == KEY_IGNEOUS }
|
||||
cookie.forEach { manager.setCookie(EhUrl.HOST_EX, it.toString()) }
|
||||
flush()
|
||||
val cookies = get(EhUrl.HOST_E.toHttpUrl())
|
||||
cookies.fastForEach {
|
||||
if (it.name == KEY_STAR || it.name == KEY_IPB_MEMBER_ID || it.name == KEY_IPB_PASS_HASH) {
|
||||
manager.setCookie(EhUrl.HOST_EX, it.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCookie(url: HttpUrl, name: String) {
|
||||
@@ -67,35 +77,29 @@ object EhCookieStore : CookieJar {
|
||||
}
|
||||
|
||||
fun addCookie(cookie: Cookie) {
|
||||
if (EhUrl.DOMAIN_E in cookie.domain) manager.setCookie(EhUrl.HOST_E, cookie.toString()) else manager.setCookie(EhUrl.DOMAIN_EX, cookie.toString())
|
||||
manager.setCookie(if (EhUrl.DOMAIN_E == cookie.domain) EhUrl.HOST_E else EhUrl.HOST_EX, cookie.toString())
|
||||
}
|
||||
|
||||
fun flush() = manager.flush()
|
||||
|
||||
fun getCookieHeader(url: HttpUrl): String {
|
||||
val cookies = loadForRequest(url)
|
||||
val cookieHeader = StringBuilder()
|
||||
var i = 0
|
||||
val size = cookies.size
|
||||
while (i < size) {
|
||||
if (i > 0) {
|
||||
cookieHeader.append("; ")
|
||||
}
|
||||
val cookie = cookies[i]
|
||||
cookieHeader.append(cookie.name).append('=').append(cookie.value)
|
||||
i++
|
||||
return loadForRequest(url).fastJoinToString("; ") {
|
||||
"${it.name}=${it.value}"
|
||||
}
|
||||
return cookieHeader.toString()
|
||||
}
|
||||
|
||||
// See https://github.com/Ehviewer-Overhauled/Ehviewer/issues/873
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) = cookies.filterNot { it.name == KEY_UTMP_NAME }.forEach { manager.setCookie(url.toString(), it.toString()) }.also { flush() }
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) = cookies.fastForEach {
|
||||
if (it.name != KEY_UTMP_NAME) {
|
||||
manager.setCookie(url.toString(), it.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
val checkTips = EhUrl.DOMAIN_E in url.host
|
||||
return get(url).run {
|
||||
if (checkTips) {
|
||||
filterNot { it.name == KEY_CONTENT_WARNING }.toMutableList().apply { add(sTipsCookie) }
|
||||
fastFilter { it.name != KEY_CONTENT_WARNING }.toMutableList().apply { add(sTipsCookie) }
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.StatusCodeException
|
||||
import com.hippo.ehviewer.util.isCronetSupported
|
||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
||||
import io.ktor.utils.io.pool.useInstance
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.ceil
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.json.add
|
||||
@@ -68,9 +72,6 @@ import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import org.jsoup.Jsoup
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.ceil
|
||||
|
||||
private const val TAG = "EhEngine"
|
||||
private const val MAX_REQUEST_SIZE = 25
|
||||
@@ -116,33 +117,27 @@ private fun rethrowExactly(code: Int, body: String, e: Throwable): Nothing {
|
||||
throw e
|
||||
}
|
||||
|
||||
val httpContentPool = DirectByteBufferPool(16, 16384)
|
||||
val httpContentPool = DirectByteBufferPool(16, 0x20000)
|
||||
|
||||
suspend inline fun <T> fetchCompat(url: String, referer: String? = null, crossinline parser: (ByteBuffer) -> T): T {
|
||||
return if (isCronetSupported) {
|
||||
cronetRequest(url, referer).execute {
|
||||
val buffer = httpContentPool.borrow()
|
||||
try {
|
||||
httpContentPool.useInstance { buffer ->
|
||||
awaitBodyFully {
|
||||
buffer.put(it)
|
||||
}
|
||||
buffer.flip()
|
||||
parser(buffer)
|
||||
} finally {
|
||||
httpContentPool.recycle(buffer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ehRequest(url, referer).execute {
|
||||
val buffer = httpContentPool.borrow()
|
||||
try {
|
||||
httpContentPool.useInstance { buffer ->
|
||||
while (true) {
|
||||
if (body.source().read(buffer) == -1) break
|
||||
}
|
||||
buffer.flip()
|
||||
parser(buffer)
|
||||
} finally {
|
||||
httpContentPool.recycle(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ import android.util.Log
|
||||
import com.hippo.ehviewer.EhApplication.Companion.baseOkHttpClient
|
||||
import com.hippo.ehviewer.EhApplication.Companion.noRedirectOkHttpClient
|
||||
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
@@ -38,10 +42,6 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.executeAsync
|
||||
import okio.BufferedSource
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
inline fun ehRequest(url: String, referer: String? = null, origin: String? = null, builder: Request.Builder.() -> Unit = {}) = Request.Builder().url(url).apply {
|
||||
addHeader("User-Agent", CHROME_USER_AGENT)
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.copyToFile
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -42,8 +44,6 @@ import okio.blackholeSink
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
object EhTagDatabase : CoroutineScope {
|
||||
private const val NAMESPACE_PREFIX = "n"
|
||||
|
||||
@@ -64,10 +64,14 @@ object EhUrl {
|
||||
}
|
||||
|
||||
val apiUrl: String
|
||||
get() = when (Settings.gallerySite) {
|
||||
SITE_E -> API_E
|
||||
SITE_EX -> API_EX
|
||||
else -> API_E
|
||||
get() = if (Settings.forceEhThumb) {
|
||||
API_E
|
||||
} else {
|
||||
when (Settings.gallerySite) {
|
||||
SITE_E -> API_E
|
||||
SITE_EX -> API_EX
|
||||
else -> API_E
|
||||
}
|
||||
}
|
||||
|
||||
val referer: String
|
||||
|
||||
@@ -25,9 +25,9 @@ import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.ui.main.AdvanceTable
|
||||
import com.hippo.ehviewer.util.encodeUTF8
|
||||
import com.hippo.ehviewer.util.toIntOrDefault
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URLDecoder
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class ListUrlBuilder(
|
||||
@@ -320,66 +320,67 @@ data class ListUrlBuilder(
|
||||
// Pages
|
||||
if (pageFrom != -1 || pageTo != -1) {
|
||||
addQueryParameter("f_sp", "on")
|
||||
addQueryParameter(
|
||||
addQueryParameterIfNotBlank(
|
||||
"f_spf",
|
||||
(if (pageFrom != -1) pageFrom.toString() else "").toString(),
|
||||
pageFrom.takeUnless { it == -1 }?.toString(),
|
||||
)
|
||||
addQueryParameter(
|
||||
addQueryParameterIfNotBlank(
|
||||
"f_spt",
|
||||
(if (pageTo != -1) pageTo.toString() else "").toString(),
|
||||
pageTo.takeUnless { it == -1 }?.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
|
||||
MODE_UPLOADER -> {
|
||||
val sb = StringBuilder(EhUrl.host)
|
||||
mKeyword?.let {
|
||||
sb.append("uploader/")
|
||||
sb.append(encodeUTF8(it))
|
||||
buildString {
|
||||
append(EhUrl.host)
|
||||
mKeyword?.let {
|
||||
append("uploader/").append(encodeUTF8(it))
|
||||
}
|
||||
mPrev?.let {
|
||||
append("?prev=").append(it)
|
||||
}
|
||||
mNext?.let {
|
||||
append("?next=").append(it)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
append("&seek=").append(it)
|
||||
}
|
||||
}
|
||||
mPrev?.let {
|
||||
sb.append("?prev=").append(it)
|
||||
}
|
||||
mNext?.let {
|
||||
sb.append("?next=").append(it)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
sb.append("&seek=").append(it)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
MODE_TAG -> {
|
||||
val sb = StringBuilder(EhUrl.host)
|
||||
mKeyword?.let {
|
||||
sb.append("tag/")
|
||||
sb.append(encodeUTF8(it))
|
||||
buildString {
|
||||
append(EhUrl.host)
|
||||
mKeyword?.let {
|
||||
append("tag/").append(encodeUTF8(it))
|
||||
}
|
||||
mPrev?.let {
|
||||
append("?prev=").append(it)
|
||||
}
|
||||
mNext?.let {
|
||||
append("?next=").append(it)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
append("&seek=").append(it)
|
||||
}
|
||||
}
|
||||
mPrev?.let {
|
||||
sb.append("?prev=").append(it)
|
||||
}
|
||||
mNext?.let {
|
||||
sb.append("?next=").append(it)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
sb.append("&seek=").append(it)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
MODE_WHATS_HOT -> EhUrl.popularUrl
|
||||
MODE_IMAGE_SEARCH -> EhUrl.imageSearchUrl
|
||||
MODE_TOPLIST -> {
|
||||
val sb = StringBuilder(EhUrl.HOST_E)
|
||||
sb.append("toplist.php?tl=")
|
||||
mKeyword.orEmpty().let {
|
||||
sb.append(encodeUTF8(it))
|
||||
buildString {
|
||||
append(EhUrl.HOST_E)
|
||||
append("toplist.php?tl=")
|
||||
mKeyword.orEmpty().let {
|
||||
append(encodeUTF8(it))
|
||||
}
|
||||
mJumpTo?.let {
|
||||
append("&p=").append(it)
|
||||
}
|
||||
}
|
||||
mJumpTo?.let {
|
||||
sb.append("&p=").append(it)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unexpected value: $mode")
|
||||
|
||||
@@ -41,6 +41,10 @@ import com.hippo.ehviewer.util.toFloatOrDefault
|
||||
import com.hippo.ehviewer.util.toIntOrDefault
|
||||
import com.hippo.ehviewer.util.trimAnd
|
||||
import com.hippo.ehviewer.util.unescapeXml
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
@@ -48,10 +52,6 @@ import org.jsoup.nodes.Node
|
||||
import org.jsoup.select.Elements
|
||||
import org.jsoup.select.NodeTraversor
|
||||
import org.jsoup.select.NodeVisitor
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
object GalleryDetailParser {
|
||||
private val PATTERN_ERROR = Regex("<div class=\"d\">\n<p>([^<]+)</p>")
|
||||
|
||||
@@ -31,10 +31,10 @@ object GalleryPageApiParser {
|
||||
val imageUrl = PATTERN_IMAGE_URL.find(res.imageUrl)?.run {
|
||||
groupValues[1].trim().unescapeXml()
|
||||
}
|
||||
val skipHathKey = PATTERN_SKIP_HATH_KEY.find(res.skipHathKey)?.run {
|
||||
val skipHathKey = PATTERN_SKIP_HATH_KEY.find(res.skipHathKeyAndOriginImageUrl)?.run {
|
||||
groupValues[1].trim().unescapeXml()
|
||||
}
|
||||
val originImageUrl = PATTERN_ORIGIN_IMAGE_URL.find(res.originImageUrl)?.run {
|
||||
val originImageUrl = PATTERN_ORIGIN_IMAGE_URL.find(res.skipHathKeyAndOriginImageUrl)?.run {
|
||||
groupValues[1].unescapeXml() + "fullimg.php" + groupValues[2].unescapeXml()
|
||||
}
|
||||
check(!imageUrl.isNullOrEmpty())
|
||||
@@ -49,9 +49,7 @@ object GalleryPageApiParser {
|
||||
@SerialName("i3")
|
||||
val imageUrl: String,
|
||||
@SerialName("i6")
|
||||
val skipHathKey: String,
|
||||
@SerialName("i7")
|
||||
val originImageUrl: String,
|
||||
val skipHathKeyAndOriginImageUrl: String,
|
||||
)
|
||||
|
||||
class Result(val imageUrl: String, val skipHathKey: String?, val originImageUrl: String?)
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.hippo.ehviewer.client.parser
|
||||
import android.os.Parcelable
|
||||
import com.hippo.ehviewer.client.exception.InsufficientFundsException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
object HomeParser {
|
||||
private val PATTERN_FUNDS = Regex("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", RegexOption.DOT_MATCHES_ALL)
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.hippo.ehviewer.coil
|
||||
import coil.intercept.Interceptor
|
||||
import coil.request.ImageResult
|
||||
import com.hippo.ehviewer.Settings
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -11,8 +13,6 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
object MergeInterceptor : Interceptor {
|
||||
private val pendingContinuationMap: HashMap<String, MutableList<CancellableContinuation<Unit>>> = hashMapOf()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.hippo.ehviewer.cronet
|
||||
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
||||
import io.ktor.utils.io.pool.useInstance
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
val pool = DirectByteBufferPool(32)
|
||||
|
||||
val cronetHttpClientExecutor = EhApplication.baseOkHttpClient.dispatcher.executorService
|
||||
|
||||
suspend inline fun CronetRequest.awaitBodyFully(crossinline callback: (ByteBuffer) -> Unit) {
|
||||
return pool.useInstance { buffer ->
|
||||
suspendCancellableCoroutine { cont ->
|
||||
consumer = {
|
||||
callback(it)
|
||||
buffer.clear()
|
||||
request.read(buffer)
|
||||
}
|
||||
onError = { readerCont.resumeWithException(it) }
|
||||
readerCont = cont
|
||||
request.read(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun CronetRequest.copyToChannel(chan: FileChannel, crossinline listener: ((Int) -> Unit) = {}) = awaitBodyFully {
|
||||
val bytes = chan.write(it)
|
||||
listener(bytes)
|
||||
}
|
||||
@@ -24,12 +24,12 @@ import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import splitties.init.appCtx
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.concurrent.TimeUnit
|
||||
import splitties.init.appCtx
|
||||
|
||||
private val signedIn
|
||||
get() = EhCookieStore.hasSignedIn()
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.hippo.ehviewer.util.runAssertingNotMainThread
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import java.util.LinkedList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -50,7 +51,6 @@ import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.init.appCtx
|
||||
import splitties.preferences.edit
|
||||
import java.util.LinkedList
|
||||
|
||||
object DownloadManager : OnSpiderListener {
|
||||
// All download info list
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.hippo.ehviewer.jni.providePassword
|
||||
import com.hippo.ehviewer.jni.releaseByteBuffer
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.unifile.UniFile
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -48,7 +49,6 @@ import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import moe.tarsin.coroutines.NamedMutex
|
||||
import moe.tarsin.coroutines.withLock
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class ArchivePageLoader(context: Context, private val uri: Uri, passwdFlow: Flow<String>) : PageLoader2(), CoroutineScope {
|
||||
override val coroutineContext = Dispatchers.IO + Job()
|
||||
|
||||
@@ -46,11 +46,11 @@ import com.hippo.ehviewer.util.isAtLeastP
|
||||
import com.hippo.ehviewer.util.isAtLeastU
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.openInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.min
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import splitties.init.appCtx
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.min
|
||||
|
||||
class Image private constructor(drawable: Drawable, private val src: AutoCloseable) {
|
||||
var mObtainedDrawable: Drawable? = drawable
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.hippo.ehviewer.legacy
|
||||
|
||||
import android.app.Application
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
|
||||
private val OBSOLETE_CACHE_DIRS = arrayOf(
|
||||
"image",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hippo.ehviewer.legacy
|
||||
|
||||
import com.hippo.ehviewer.spider.SpiderInfo
|
||||
import java.io.InputStream
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import java.io.InputStream
|
||||
|
||||
fun readLegacySpiderInfo(inputStream: InputStream): SpiderInfo {
|
||||
val source = inputStream.source().buffer()
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.hippo.ehviewer.spider
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.MODE_READ_WRITE
|
||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache as sCache
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
@@ -38,14 +39,13 @@ import com.hippo.ehviewer.util.isCronetSupported
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.openOutputStream
|
||||
import java.util.Locale
|
||||
import kotlin.io.path.readText
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Response
|
||||
import okio.Buffer
|
||||
import okio.ForwardingSink
|
||||
import okio.sink
|
||||
import java.util.Locale
|
||||
import kotlin.io.path.readText
|
||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache as sCache
|
||||
|
||||
class SpiderDen(mGalleryInfo: GalleryInfo) {
|
||||
private val mGid = mGalleryInfo.gid
|
||||
|
||||
@@ -7,13 +7,13 @@ import com.hippo.ehviewer.legacy.readLegacySpiderInfo
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.openInputStream
|
||||
import com.hippo.unifile.openOutputStream
|
||||
import java.io.File
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.cbor.Cbor
|
||||
import kotlinx.serialization.decodeFromByteArray
|
||||
import kotlinx.serialization.encodeToByteArray
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
|
||||
@Serializable
|
||||
class SpiderInfo(
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.hippo.ehviewer.client.EhUrl.getGalleryMultiPageViewerUrl
|
||||
import com.hippo.ehviewer.client.EhUrl.referer
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.ehRequest
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import com.hippo.ehviewer.client.exception.QuotaExceededException
|
||||
import com.hippo.ehviewer.client.execute
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailParser.parsePages
|
||||
@@ -42,6 +41,9 @@ import com.hippo.ehviewer.image.Image
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -59,9 +61,6 @@ import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import splitties.init.appCtx
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineScope {
|
||||
override val coroutineContext = Dispatchers.IO + Job()
|
||||
@@ -650,7 +649,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
}
|
||||
|
||||
if (imageUrl == null) {
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
EhEngine.getGalleryPageApi(
|
||||
mSpiderInfo.gid,
|
||||
index,
|
||||
@@ -664,13 +663,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
originImageUrl = it.originImageUrl
|
||||
}
|
||||
}.onFailure {
|
||||
if (it is ParseException && "Key mismatch" == it.message) {
|
||||
// Show key is wrong, enter a new loop to get the new show key
|
||||
if (showKey == localShowKey) showKey = null
|
||||
return@repeat
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
forceHtml = true
|
||||
return@repeat
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.customview.widget.Openable
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -68,12 +69,12 @@ import com.hippo.ehviewer.util.getParcelableExtraCompat
|
||||
import com.hippo.ehviewer.util.getUrlFromClipboard
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.systemservices.clipboardManager
|
||||
import splitties.systemservices.connectivityManager
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class MainActivity : EhActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -182,6 +183,7 @@ class MainActivity : EhActivity() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
@@ -16,8 +16,8 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SecondaryTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -123,7 +123,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
TabRow(
|
||||
SecondaryTabRow(
|
||||
selectedTabIndex = if (vm.isNormalMode) 0 else 1,
|
||||
divider = {},
|
||||
) {
|
||||
|
||||
@@ -64,10 +64,10 @@ import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cookie
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun CookieSignInScene(windowSizeClass: WindowSizeClass) {
|
||||
@@ -101,7 +101,6 @@ fun CookieSignInScene(windowSizeClass: WindowSizeClass) {
|
||||
EhCookieStore.addCookie(newCookie(EhCookieStore.KEY_IPB_PASS_HASH, hash, EhUrl.DOMAIN_E))
|
||||
EhCookieStore.addCookie(newCookie(EhCookieStore.KEY_IPB_PASS_HASH, hash, EhUrl.DOMAIN_EX))
|
||||
if (igneous.isNotBlank()) EhCookieStore.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_EX))
|
||||
EhCookieStore.flush()
|
||||
}
|
||||
|
||||
fun login() {
|
||||
|
||||
@@ -27,6 +27,7 @@ suspend fun postLogin() = coroutineScope {
|
||||
launch {
|
||||
runCatching {
|
||||
EhEngine.getUConfig(EhUrl.URL_UCONFIG_E)
|
||||
EhCookieStore.flush()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
@@ -36,6 +37,7 @@ suspend fun postLogin() = coroutineScope {
|
||||
Settings.gallerySite = EhUrl.SITE_EX
|
||||
// Explicitly use ex url since https://github.com/Ehviewer-Overhauled/Ehviewer/issues/1239#issuecomment-1632584525
|
||||
EhEngine.getUConfig(EhUrl.URL_UCONFIG_EX)
|
||||
EhCookieStore.flush()
|
||||
}.onFailure {
|
||||
Settings.gallerySite = EhUrl.SITE_E
|
||||
Settings.needSignIn = false
|
||||
|
||||
@@ -120,7 +120,7 @@ abstract class BaseScene : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
open val showLeftDrawer = true
|
||||
open val showLeftDrawer = false
|
||||
|
||||
private fun createDrawerView(savedInstanceState: Bundle?): View? {
|
||||
drawerView = onCreateDrawerView(layoutInflater)
|
||||
|
||||
@@ -81,6 +81,7 @@ import com.hippo.ehviewer.databinding.ItemDownloadBinding
|
||||
import com.hippo.ehviewer.databinding.ItemDrawerListBinding
|
||||
import com.hippo.ehviewer.databinding.SceneDownloadBinding
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager as downloadManager
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.DownloadService.Companion.clear
|
||||
import com.hippo.ehviewer.download.downloadDir
|
||||
@@ -115,7 +116,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import rikka.core.res.resolveColor
|
||||
import com.hippo.ehviewer.download.DownloadManager as downloadManager
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
class DownloadsScene :
|
||||
|
||||
@@ -88,17 +88,17 @@ import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
// Note that we do not really follow mvvm structure, just use it as ... storage
|
||||
class VMStorage : ViewModel() {
|
||||
|
||||
@@ -74,31 +74,15 @@ class GalleryAdapter(
|
||||
layoutManager.setStrategy(STRATEGY_MIN_SIZE)
|
||||
if (null != mGirdDecoration) {
|
||||
recyclerView.removeItemDecoration(mGirdDecoration!!)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_v)
|
||||
recyclerView.setPadding(
|
||||
recyclerView.paddingLeft - paddingH,
|
||||
recyclerView.paddingTop - paddingV,
|
||||
recyclerView.paddingRight - paddingH,
|
||||
recyclerView.paddingBottom - paddingV,
|
||||
)
|
||||
mGirdDecoration = null
|
||||
}
|
||||
if (null == mListDecoration) {
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
|
||||
mListDecoration = object : ItemDecoration() {
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
outRect.set(0, interval / 2, 0, interval / 2)
|
||||
}
|
||||
}
|
||||
recyclerView.setPadding(
|
||||
recyclerView.paddingLeft + paddingH,
|
||||
recyclerView.paddingTop + paddingV,
|
||||
recyclerView.paddingRight + paddingH,
|
||||
recyclerView.paddingBottom + paddingV,
|
||||
)
|
||||
recyclerView.addItemDecoration(mListDecoration!!)
|
||||
}
|
||||
}
|
||||
@@ -109,27 +93,11 @@ class GalleryAdapter(
|
||||
layoutManager.setStrategy(STRATEGY_SUITABLE_SIZE)
|
||||
if (null != mListDecoration) {
|
||||
recyclerView.removeItemDecoration(mListDecoration!!)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
|
||||
recyclerView.setPadding(
|
||||
recyclerView.paddingLeft - paddingH,
|
||||
recyclerView.paddingTop - paddingV,
|
||||
recyclerView.paddingRight - paddingH,
|
||||
recyclerView.paddingBottom - paddingV,
|
||||
)
|
||||
mListDecoration = null
|
||||
}
|
||||
if (null == mGirdDecoration) {
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_grid_interval)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_v)
|
||||
mGirdDecoration = MarginItemDecoration(interval, 0, 0, 0, 0)
|
||||
recyclerView.setPadding(
|
||||
recyclerView.paddingLeft + paddingH,
|
||||
recyclerView.paddingTop + paddingV,
|
||||
recyclerView.paddingRight + paddingH,
|
||||
recyclerView.paddingBottom + paddingV,
|
||||
)
|
||||
recyclerView.addItemDecoration(mGirdDecoration!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +91,9 @@ import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import com.hippo.ehviewer.util.toBBCode
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlin.math.hypot
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
import kotlin.math.hypot
|
||||
|
||||
class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefreshListener {
|
||||
private var _binding: SceneGalleryCommentsBinding? = null
|
||||
|
||||
@@ -136,6 +136,7 @@ import com.hippo.ehviewer.databinding.DialogArchiveListBinding
|
||||
import com.hippo.ehviewer.databinding.DialogRateBinding
|
||||
import com.hippo.ehviewer.databinding.DialogTorrentListBinding
|
||||
import com.hippo.ehviewer.databinding.ItemGalleryCommentBinding
|
||||
import com.hippo.ehviewer.download.DownloadManager as EhDownloadManager
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.spider.SpiderQueen
|
||||
import com.hippo.ehviewer.spider.SpiderQueen.Companion.MODE_READ
|
||||
@@ -172,6 +173,7 @@ import com.hippo.ehviewer.util.addTextToClipboard
|
||||
import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -179,8 +181,6 @@ import kotlinx.coroutines.sync.withLock
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import splitties.systemservices.downloadManager
|
||||
import kotlin.math.roundToInt
|
||||
import com.hippo.ehviewer.download.DownloadManager as EhDownloadManager
|
||||
|
||||
class GalleryDetailScene : BaseScene() {
|
||||
private var mDownloadState = 0
|
||||
|
||||
@@ -108,12 +108,6 @@ import com.hippo.ehviewer.util.setValue
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
@@ -121,6 +115,12 @@ import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
class VMStorage1 : ViewModel() {
|
||||
var urlBuilder = ListUrlBuilder()
|
||||
|
||||
@@ -73,6 +73,8 @@ import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class HistoryScene : BaseScene() {
|
||||
override val showLeftDrawer = true
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = ComposeView(inflater.context).apply {
|
||||
setMD3Content {
|
||||
val dialogState = rememberDialogState()
|
||||
|
||||
@@ -34,8 +34,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
class ProgressScene : BaseScene() {
|
||||
override val showLeftDrawer = false
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return ComposeView(inflater.context).apply {
|
||||
setMD3Content {
|
||||
|
||||
@@ -75,6 +75,8 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
}
|
||||
}
|
||||
|
||||
override val showLeftDrawer = true
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = SceneSearchbarBinding.inflate(inflater, container, false)
|
||||
binding.appbar.statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
|
||||
|
||||
@@ -43,10 +43,10 @@ import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import com.hippo.ehviewer.util.installPackage
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import java.io.File
|
||||
|
||||
private const val REPO_URL = "https://github.com/${BuildConfig.REPO_NAME}"
|
||||
private const val RELEASE_URL = "$REPO_URL/releases"
|
||||
|
||||
@@ -38,13 +38,13 @@ import com.hippo.ehviewer.ui.LocalNavController
|
||||
import com.hippo.ehviewer.ui.tools.observed
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@Composable
|
||||
fun AdvancedScreen() {
|
||||
|
||||
@@ -50,13 +50,13 @@ import com.jamal.composeprefs3.ui.prefs.SliderPref
|
||||
import com.jamal.composeprefs3.ui.prefs.SpannedTextPref
|
||||
import com.jamal.composeprefs3.ui.prefs.SwitchPref
|
||||
import com.jamal.composeprefs3.ui.prefs.TextPref
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object PreferenceTokens {
|
||||
val PreferenceHeaderHeight = 56.dp
|
||||
|
||||
@@ -47,9 +47,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.jamal.composeprefs3.ui.ifNotNullThen
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
interface DialogScope<R> {
|
||||
var expectedValue: R
|
||||
|
||||
@@ -46,14 +46,14 @@ import androidx.compose.ui.util.fastFirstOrNull
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastMaxBy
|
||||
import com.hippo.ehviewer.ui.tools.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
|
||||
/**
|
||||
* Draws vertical fast scroller to a lazy list
|
||||
|
||||
@@ -6,6 +6,10 @@ import com.hippo.ehviewer.client.execute
|
||||
import com.hippo.ehviewer.client.executeAndParseAs
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.util.encodeBase64
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.zip.ZipInputStream
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import okhttp3.Request
|
||||
import okio.sink
|
||||
@@ -14,10 +18,6 @@ import tachiyomi.data.release.GithubArtifacts
|
||||
import tachiyomi.data.release.GithubCommitComparison
|
||||
import tachiyomi.data.release.GithubRelease
|
||||
import tachiyomi.data.release.GithubWorkflowRuns
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
private const val API_URL = "https://api.github.com/repos/${BuildConfig.REPO_NAME}"
|
||||
private const val LATEST_RELEASE_URL = "$API_URL/releases/latest"
|
||||
|
||||
@@ -17,8 +17,8 @@ package com.hippo.ehviewer.util
|
||||
|
||||
import android.os.Environment
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import splitties.init.appCtx
|
||||
|
||||
fun File.ensureDirectory() = if (exists()) isDirectory else mkdirs()
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ fun Spanned.toBBCode(): String {
|
||||
}
|
||||
if (next < text.length) processOneSpanTransition(next)
|
||||
}
|
||||
return StringBuilder().apply {
|
||||
return buildString {
|
||||
processOneSpanTransition()
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import splitties.init.appCtx
|
||||
import java.io.IOException
|
||||
import java.net.MalformedURLException
|
||||
import java.net.ProtocolException
|
||||
@@ -25,6 +23,7 @@ import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import javax.net.ssl.SSLException
|
||||
import splitties.init.appCtx
|
||||
|
||||
object ExceptionUtils {
|
||||
fun getReadableString(e: Throwable): String {
|
||||
@@ -43,12 +42,12 @@ object ExceptionUtils {
|
||||
is SocketTimeoutException -> appCtx.getString(R.string.error_timeout)
|
||||
is UnknownHostException -> appCtx.getString(R.string.error_unknown_host)
|
||||
is StatusCodeException -> {
|
||||
val sb = StringBuilder()
|
||||
sb.append(appCtx.getString(R.string.error_bad_status_code, e.responseCode))
|
||||
if (e.isIdentifiedResponseCode) {
|
||||
sb.append(", ").append(e.message)
|
||||
buildString {
|
||||
append(appCtx.getString(R.string.error_bad_status_code, e.responseCode))
|
||||
if (e.isIdentifiedResponseCode) {
|
||||
append(", ").append(e.message)
|
||||
}
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
is ProtocolException -> {
|
||||
@@ -60,8 +59,7 @@ object ExceptionUtils {
|
||||
}
|
||||
|
||||
is SocketException, is SSLException -> appCtx.getString(R.string.error_socket)
|
||||
is EhException -> e.message!!
|
||||
else -> appCtx.getString(R.string.error_unknown)
|
||||
else -> e.message ?: appCtx.getString(R.string.error_unknown)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import java.io.File
|
||||
import okhttp3.ResponseBody
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import java.io.File
|
||||
|
||||
fun ResponseBody.copyToFile(file: File) {
|
||||
file.outputStream().use { os ->
|
||||
|
||||
@@ -22,10 +22,10 @@ import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import arrow.atomic.AtomicInt
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import rikka.core.util.ContextUtils.requireActivity
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import rikka.core.util.ContextUtils.requireActivity
|
||||
|
||||
// Fuck off the silly Android launcher and callback :)
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.os.Looper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.BufferedReader
|
||||
import java.io.FileReader
|
||||
import java.util.regex.Pattern
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object OSUtils {
|
||||
private const val PROCFS_MEMFILE = "/proc/meminfo"
|
||||
|
||||
@@ -101,19 +101,19 @@ object ReadableTime {
|
||||
}
|
||||
|
||||
fun getShortTimeInterval(time: Long): String {
|
||||
val sb = StringBuilder()
|
||||
val resources = sResources
|
||||
for (i in 0 until SIZE) {
|
||||
val multiple = MULTIPLES[i]
|
||||
val quotient = time / multiple
|
||||
if (time > multiple * 1.5 || i == SIZE - 1) {
|
||||
sb.append(quotient)
|
||||
.append(" ")
|
||||
.append(resources!!.getQuantityString(UNITS[i], quotient.toInt()))
|
||||
break
|
||||
return buildString {
|
||||
for (i in 0 until SIZE) {
|
||||
val multiple = MULTIPLES[i]
|
||||
val quotient = time / multiple
|
||||
if (time > multiple * 1.5 || i == SIZE - 1) {
|
||||
append(quotient)
|
||||
.append(" ")
|
||||
.append(resources!!.getQuantityString(UNITS[i], quotient.toInt()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun getFilenamableTime(time: Long): String {
|
||||
|
||||
+2
-2
@@ -8,12 +8,12 @@ import android.webkit.WebViewClient
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.exception.CloudflareBypassException
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
|
||||
private val executor = ContextCompat.getMainExecutor(context)
|
||||
|
||||
@@ -4,13 +4,13 @@ import android.content.Context
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import com.hippo.ehviewer.util.setDefaultSettings
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class WebViewInterceptor(private val context: Context) : Interceptor {
|
||||
/**
|
||||
|
||||
@@ -98,6 +98,12 @@ import eu.kanade.tachiyomi.util.view.copy
|
||||
import eu.kanade.tachiyomi.util.view.popupMenu
|
||||
import eu.kanade.tachiyomi.util.view.setTooltip
|
||||
import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.abs
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.ensureActive
|
||||
@@ -109,12 +115,6 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import splitties.systemservices.clipboardManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.abs
|
||||
|
||||
class ReaderActivity : EhActivity() {
|
||||
lateinit var binding: ReaderActivityBinding
|
||||
@@ -190,7 +190,12 @@ class ReaderActivity : EhActivity() {
|
||||
}
|
||||
|
||||
private fun buildProvider(replace: Boolean = false) {
|
||||
if (setOrientation(ReaderPreferences.defaultOrientationType().get())) return
|
||||
// TODO: Better solution for this dirty workaround
|
||||
if (setOrientation(ReaderPreferences.defaultOrientationType().get())) {
|
||||
// Orientation changed, we don't know if the activity will recreate, just recreate anyway
|
||||
recreate()
|
||||
return
|
||||
}
|
||||
|
||||
if (mGalleryProvider != null) {
|
||||
if (replace) mGalleryProvider!!.stop() else return
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ fun CombinedCircularProgressIndicator(progress: Float) {
|
||||
label = "rotation",
|
||||
)
|
||||
CircularProgressIndicator(
|
||||
progress = animatedProgress,
|
||||
progress = { animatedProgress },
|
||||
modifier = Modifier.rotate(rotation),
|
||||
)
|
||||
}
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ
|
||||
/**
|
||||
* Returns the custom extra layout space.
|
||||
*/
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun getExtraLayoutSpace(state: RecyclerView.State): Int {
|
||||
return extraLayoutSpace
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import com.hippo.ehviewer.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import splitties.systemservices.windowManager
|
||||
import kotlin.math.roundToInt
|
||||
import com.hippo.ehviewer.Settings as AppSettings
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import kotlin.math.roundToInt
|
||||
import splitties.systemservices.windowManager
|
||||
|
||||
/**
|
||||
* Returns the color for the given attribute.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package moe.tarsin.coroutines
|
||||
|
||||
import arrow.core.memoize
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
class NamedMutex<T> {
|
||||
val innerMutexGetter = { _: T -> Mutex() }.memoize()
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="6dp"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin"
|
||||
app:fitsSystemWindowsInsets="top|bottom">
|
||||
|
||||
@@ -52,7 +52,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false" />
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/gallery_list_margin_h"
|
||||
android:paddingVertical="@dimen/gallery_list_margin_v"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout>
|
||||
|
||||
@@ -63,8 +66,8 @@
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="@dimen/gallery_padding_top_search_bar"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingBottom="8dp" />
|
||||
android:paddingVertical="8dp"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -329,8 +329,8 @@
|
||||
<string name="settings_eh_thumb_resolution">缩略图分辨率</string>
|
||||
<string name="settings_eh_thumb_resolution_summary">%s,指定分辨率可能使获取缩略图失败</string>
|
||||
<string name="settings_eh_thumb_resolution_auto">自动</string>
|
||||
<string name="settings_eh_force_eh_thumb">强制使用 e-hentai 缩略图服务器</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">若 exhentai 缩略图加载失败可尝试此项</string>
|
||||
<string name="settings_eh_force_eh_thumb">强制使用 e-hentai 缩略图/API服务器</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">若 exhentai 加载失败可尝试此项</string>
|
||||
<string name="settings_eh_show_jpn_title">显示日文标题</string>
|
||||
<string name="settings_eh_show_jpn_title_summary">需同时在 EHentai 网站设置中启用 Japanese Title</string>
|
||||
<string name="settings_eh_show_gallery_pages">显示画廊页数</string>
|
||||
|
||||
@@ -371,8 +371,8 @@
|
||||
<string name="settings_eh_thumb_resolution">Thumb resolution</string>
|
||||
<string name="settings_eh_thumb_resolution_summary">Currently %s. Certain resolutions might cause getting thumb failed</string>
|
||||
<string name="settings_eh_thumb_resolution_auto">Auto</string>
|
||||
<string name="settings_eh_force_eh_thumb">Force e-hentai thumb server</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">Try this if you have trouble loading thumbs from exhentai</string>
|
||||
<string name="settings_eh_force_eh_thumb">Force e-hentai thumb/API server</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">Try this if you have trouble loading from exhentai</string>
|
||||
<string name="settings_eh_show_jpn_title">Show Japanese title</string>
|
||||
<string name="settings_eh_show_jpn_title_summary">Require enabling Japanese Title in Settings on EHentai website</string>
|
||||
<string name="settings_eh_show_gallery_pages">Show gallery pages</string>
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
<style name="AppTheme" parent="Base.AppTheme" />
|
||||
|
||||
<style name="AppTheme.Splash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="ProgressView">
|
||||
<item name="android:indeterminate">true</item>
|
||||
<item name="android:minWidth">48dp</item>
|
||||
|
||||
@@ -13,7 +13,7 @@ jnix-macros = "0.4"
|
||||
catch_panic = { path = "catch_panic" }
|
||||
catch_panic_macros = { path = "catch_panic_macros" }
|
||||
jni_fn = "0.1.2"
|
||||
quick-xml = { default-features = false, version = "0.30" }
|
||||
quick-xml = { default-features = false, version = "0.31" }
|
||||
tl = "0.7"
|
||||
log = "0.4"
|
||||
android_logger = { default-features = false, version = "0.13" }
|
||||
|
||||
@@ -8,29 +8,22 @@ import android.net.http.UrlRequest
|
||||
import android.net.http.UrlResponseInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresExtension
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.client.CHROME_ACCEPT
|
||||
import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import splitties.init.appCtx
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
private const val TAG = "CronetRequest"
|
||||
val pool = DirectByteBufferPool(32)
|
||||
|
||||
val cronetHttpClient: HttpEngine = HttpEngine.Builder(appCtx).apply {
|
||||
setEnableBrotli(true)
|
||||
@@ -40,8 +33,6 @@ val cronetHttpClient: HttpEngine = HttpEngine.Builder(appCtx).apply {
|
||||
setUserAgent(CHROME_USER_AGENT)
|
||||
}.build()
|
||||
|
||||
val cronetHttpClientExecutor = EhApplication.baseOkHttpClient.dispatcher.executorService
|
||||
|
||||
class CronetRequest {
|
||||
lateinit var consumer: (ByteBuffer) -> Unit
|
||||
lateinit var onResponse: CronetRequest.(UrlResponseInfo) -> Unit
|
||||
@@ -50,7 +41,6 @@ class CronetRequest {
|
||||
lateinit var readerCont: Continuation<Unit>
|
||||
val callback = object : UrlRequest.Callback {
|
||||
override fun onRedirectReceived(req: UrlRequest, info: UrlResponseInfo, url: String) {
|
||||
logcat(tag = TAG) { "Redirected to $url" }
|
||||
req.followRedirect()
|
||||
}
|
||||
|
||||
@@ -86,29 +76,6 @@ inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.
|
||||
}.apply(conf).build()
|
||||
}
|
||||
|
||||
suspend inline fun CronetRequest.awaitBodyFully(crossinline callback: (ByteBuffer) -> Unit) {
|
||||
val buffer = pool.borrow()
|
||||
return try {
|
||||
suspendCancellableCoroutine { cont ->
|
||||
consumer = {
|
||||
callback(it)
|
||||
buffer.clear()
|
||||
request.read(buffer)
|
||||
}
|
||||
onError = { readerCont.resumeWithException(it) }
|
||||
readerCont = cont
|
||||
request.read(buffer)
|
||||
}
|
||||
} finally {
|
||||
pool.recycle(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun CronetRequest.copyToChannel(chan: FileChannel, crossinline listener: ((Int) -> Unit) = {}) = awaitBodyFully {
|
||||
val bytes = chan.write(it)
|
||||
listener(bytes)
|
||||
}
|
||||
|
||||
suspend inline fun <R> CronetRequest.execute(crossinline callback: suspend CronetRequest.(UrlResponseInfo) -> R): R {
|
||||
contract {
|
||||
callsInPlace(callback, InvocationKind.EXACTLY_ONCE)
|
||||
|
||||
@@ -9,3 +9,13 @@ plugins {
|
||||
alias(libs.plugins.rustAndroidPlugin) apply false
|
||||
alias(libs.plugins.composeCompilerReportGenerator) apply false
|
||||
}
|
||||
|
||||
tasks.register("Delete", Delete::class) {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(libs.r8)
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,6 @@ org.gradle.caching=true
|
||||
android.useAndroidX=true
|
||||
android.enableAppCompileTimeRClass=true
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
android.experimental.lint.version=8.3.0-alpha09
|
||||
android.lint.useK2Uast=true
|
||||
kotlin.experimental.tryK2=true
|
||||
@@ -1,6 +1,7 @@
|
||||
[versions]
|
||||
aboutlibraries = "10.9.1"
|
||||
agp = "8.3.0-alpha07"
|
||||
agp = "8.1.2"
|
||||
android-tools-r8 = "8.3.12-dev"
|
||||
androidx-activity = "1.8.0"
|
||||
androidx-appcompat = "1.6.1"
|
||||
androidx-biometric = "1.2.0-alpha05"
|
||||
@@ -9,6 +10,7 @@ androidx-collection = "1.3.0"
|
||||
androidx-constraintlayout-compose = "1.1.0-alpha13"
|
||||
androidx-coordinatorlayout = "1.2.0"
|
||||
androidx-core = "1.12.0"
|
||||
androidx-core-splashscreen = "1.1.0-alpha02"
|
||||
androidx-fragment = "1.7.0-alpha06"
|
||||
androidx-lifecycle = "2.6.2"
|
||||
androidx-navigation = "2.7.4"
|
||||
@@ -24,7 +26,7 @@ coil = "2.4.0"
|
||||
compose-bom = "2023.11.00-alpha02"
|
||||
compose-compiler = "1.5.4-dev-k1.9.20-RC-1edce5fd625"
|
||||
google-accompanist = "0.32.0"
|
||||
google-android-material = "1.11.0-alpha03"
|
||||
google-android-material = "1.11.0-beta01"
|
||||
kotlin = "1.9.20-RC"
|
||||
kotlinx-coroutines = "1.7.3"
|
||||
kotlinx-serialization = "1.6.0"
|
||||
@@ -50,6 +52,7 @@ androidx-collection = { module = "androidx.collection:collection-ktx", version.r
|
||||
androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "androidx-constraintlayout-compose" }
|
||||
androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinatorlayout" }
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" }
|
||||
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" }
|
||||
|
||||
@@ -95,7 +98,7 @@ desugar = "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
||||
drawer = "com.drakeet.drawer:drawer:1.0.3"
|
||||
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||
jsoup = "org.jsoup:jsoup:1.16.1"
|
||||
jsoup = "org.jsoup:jsoup:1.16.2"
|
||||
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" }
|
||||
@@ -119,6 +122,8 @@ okio-jvm = { module = "com.squareup.okio:okio-jvm", version.ref = "okio" }
|
||||
|
||||
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
||||
|
||||
r8 = { module = "com.android.tools:r8", version.ref = "android-tools-r8" }
|
||||
|
||||
rikkax-core = "dev.rikka.rikkax.core:core-ktx:1.4.1"
|
||||
rikkax-insets = "dev.rikka.rikkax.insets:insets:1.3.0"
|
||||
rikkax-layoutinflater = "dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0"
|
||||
|
||||
@@ -3,6 +3,7 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
maven("https://storage.googleapis.com/r8-releases/raw")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user