Compare commits

...
29 Commits
Author SHA1 Message Date
FooIbar 8cde607e9a Release 1.8.10.3
Releases / Build (push) Has been cancelled
2023-10-29 03:33:24 +08:00
FooIbar 2f39359b47 Fix reader not loading under certain circumstances (#128)
* Revert "Fix reader bug on certain orientation settings"

This reverts commit 6e59583dea.

* Revert "Skip `buildProvider` if the activity will restart"

This reverts commit a880f801fb.

* Preserve gallery provider in view model

(cherry picked from commit e2f024ae29)
2023-10-29 03:29:34 +08:00
FooIbar d1e3965d62 Add missing color filter modes
(cherry picked from commit 6458eb090a)
2023-10-29 03:28:57 +08:00
FooIbar 10c7019aeb Release 1.8.10.2
Releases / Build (push) Has been cancelled
2023-10-27 19:47:48 +08:00
FooIbar 76aa401800 Sync with upstream API changes
(cherry picked from commit 44a6253bfc)
2023-10-27 19:46:50 +08:00
FooIbar dab5878f74 Fix unexpected touch scale behavior in PageViewer
(cherry picked from commit 0c78673870)
2023-10-27 19:46:42 +08:00
FooIbar ab8671cc3b Revert "Exclude some libraries from aboutLibraries"
This reverts commit bc34f25a3c.

Reason for revert: Licenses should be included as per Apache 2.0
2023-10-24 23:53:25 +08:00
FooIbar 2e52dda208 Downgrade AGP to stable version but use newer lint (#81)
* Downgrade AGP to stable version but use newer lint

* Downgrade Java to 17 and use newer r8
2023-10-24 13:08:23 +00:00
renovate[bot] 1ab08d0ae1 Update Rust crate quick-xml to 0.31 2023-10-23 20:22:16 +00:00
FooIbar 702be0ed43 Use eh API as well if forceEhThumb is enabled 2023-10-23 22:37:55 +08:00
FooIbar 4067543701 Cleanup code 2023-10-23 16:03:01 +08:00
FooIbar 074689c60e Fall back if failed to retrieve data from API 2023-10-23 15:10:18 +08:00
FooIbar 2beb715df3 Cleanup code 2023-10-23 14:36:28 +08:00
FooIbar 7ea2597899 Fix large torrent list parsing 2023-10-23 02:25:17 +08:00
FooIbar 34a8f92865 Show meaningful error message if it's available 2023-10-23 02:25:17 +08:00
FooIbar 6e2a2de13f Reformat imports alphabetically
https://developer.android.com/kotlin/style-guide#import_statements
2023-10-22 22:13:45 +08:00
FooIbar e073232352 Fix cronet support detection 2023-10-22 17:52:16 +08:00
FooIbar 6f49b4d23e Fix deprecated 2023-10-21 18:22:45 +08:00
FooIbar 97cad20ab1 It's affected by AGP and NDK versions as well 2023-10-21 17:38:29 +08:00
FooIbar 9300a3d9ce Don't write cmake cache every time 2023-10-21 16:26:14 +08:00
FooIbar 28b7f80a8f Fix content behind navigation bar 2023-10-21 16:13:27 +08:00
FooIbar a06d23753a Lock navi drawer on where doesn't have navi icon 2023-10-21 13:10:46 +08:00
FooIbar 98d66b3e6c Release 1.8.10.1
Releases / Build (push) Has been cancelled
2023-10-21 00:14:03 +08:00
FooIbar 6e59583dea Fix reader bug on certain orientation settings
Resolve #68
2023-10-21 00:12:52 +08:00
FooIbar 2f0571e384 Implement splash screen 2023-10-20 17:36:59 +08:00
renovate[bot] 64a6074e02 Update dependency org.jsoup:jsoup to v1.16.2 2023-10-20 06:11:31 +00:00
renovate[bot] 0b7cb4cf41 Update dependency com.android.application to v8.3.0-alpha09 2023-10-20 01:48:36 +00:00
renovate[bot] e620832e33 Update dependency com.google.android.material:material to v1.11.0-beta01 2023-10-19 23:03:41 +00:00
renovate[bot] f82e4af0e3 Update dependency com.android.application to v8.3.0-alpha08 2023-10-19 20:36:34 +00:00
78 changed files with 361 additions and 374 deletions

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
[*.{kt,kts}]
ij_kotlin_imports_layout = *
ktlint_code_style = intellij_idea
ktlint_function_naming_ignore_when_annotated_with = Composable
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+4 -13
View File
@@ -64,8 +64,8 @@ android {
applicationId = "moe.tarsin.ehviewer"
minSdk = 28
targetSdk = 34
versionCode = 180044
versionName = "1.8.10.0"
versionCode = 180045
versionName = "1.8.10.3"
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)
}
+1
View File
@@ -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" />
@@ -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()
@@ -28,8 +28,10 @@ import java.util.Locale
class EhPageLoader(private val mGalleryInfo: GalleryInfo) : PageLoader2(), OnSpiderListener {
private lateinit var mSpiderQueen: SpiderQueen
override fun start() {
mSpiderQueen = obtainSpiderQueen(mGalleryInfo, SpiderQueen.MODE_READ)
mSpiderQueen.addOnSpiderListener(this)
if (!::mSpiderQueen.isInitialized) {
mSpiderQueen = obtainSpiderQueen(mGalleryInfo, SpiderQueen.MODE_READ)
mSpiderQueen.addOnSpiderListener(this)
}
}
override fun stop() {
@@ -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 {
@@ -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 {
/**
@@ -45,6 +45,7 @@ import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.activity.viewModels
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
@@ -59,6 +60,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import com.google.android.material.shape.MaterialShapeDrawable
import com.hippo.ehviewer.BuildConfig
@@ -78,9 +80,12 @@ import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.getParcelableCompat
import com.hippo.ehviewer.util.getParcelableExtraCompat
import com.hippo.ehviewer.util.getValue
import com.hippo.ehviewer.util.isAtLeastO
import com.hippo.ehviewer.util.isAtLeastP
import com.hippo.ehviewer.util.lazyMut
import com.hippo.ehviewer.util.sendTo
import com.hippo.ehviewer.util.setValue
import com.hippo.unifile.UniFile
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@@ -98,6 +103,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 +120,15 @@ 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 GalleryModel : ViewModel() {
var galleryProvider: PageLoader2? = null
override fun onCleared() {
super.onCleared()
galleryProvider?.stop()
galleryProvider = null
}
}
class ReaderActivity : EhActivity() {
lateinit var binding: ReaderActivityBinding
@@ -124,6 +138,7 @@ class ReaderActivity : EhActivity() {
private var mGalleryInfo: BaseGalleryInfo? = null
private var mPage: Int = 0
private var mCacheFileName: String? = null
private val vm: GalleryModel by viewModels()
/**
* Whether the menu is currently visible.
@@ -159,7 +174,7 @@ class ReaderActivity : EhActivity() {
}
}
}
var mGalleryProvider: PageLoader2? = null
var mGalleryProvider by lazyMut { vm::galleryProvider }
private var mCurrentIndex: Int = 0
private var mSavingPage = -1
private lateinit var builder: EditTextDialogBuilder
@@ -190,8 +205,6 @@ class ReaderActivity : EhActivity() {
}
private fun buildProvider(replace: Boolean = false) {
if (setOrientation(ReaderPreferences.defaultOrientationType().get())) return
if (mGalleryProvider != null) {
if (replace) mGalleryProvider!!.stop() else return
}
@@ -266,7 +279,6 @@ class ReaderActivity : EhActivity() {
private fun onInit() {
handleIntent(intent)
buildProvider()
}
private fun onRestore(savedInstanceState: Bundle) {
@@ -276,7 +288,6 @@ class ReaderActivity : EhActivity() {
mGalleryInfo = savedInstanceState.getParcelableCompat(KEY_GALLERY_INFO)
mPage = savedInstanceState.getInt(KEY_PAGE, -1)
mCurrentIndex = savedInstanceState.getInt(KEY_CURRENT_INDEX)
buildProvider()
}
override fun onNewIntent(intent: Intent?) {
@@ -323,6 +334,7 @@ class ReaderActivity : EhActivity() {
} else {
onRestore(savedInstanceState)
}
buildProvider()
binding = ReaderActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
builder = EditTextDialogBuilder(this, null, getString(R.string.archive_passwd))
@@ -333,6 +345,7 @@ class ReaderActivity : EhActivity() {
mGalleryProvider.let {
if (it == null) {
finish()
return
}
lifecycleScope.launchIO {
@@ -363,7 +376,6 @@ class ReaderActivity : EhActivity() {
updateViewerInset(ReaderPreferences.fullscreen().get())
binding.viewerContainer.removeAllViews()
setOrientation(ReaderPreferences.defaultOrientationType().get())
updateOrientationShortcut(ReaderPreferences.defaultOrientationType().get())
binding.viewerContainer.addView(viewer?.getView())
viewer?.setGalleryProvider(mGalleryProvider!!)
moveToPageIndex(mCurrentIndex)
@@ -374,10 +386,6 @@ class ReaderActivity : EhActivity() {
config = null
viewer?.destroy()
viewer = null
if (mGalleryProvider != null) {
mGalleryProvider!!.stop()
mGalleryProvider = null
}
readerSettingSheetDialog?.dismiss()
readerSettingSheetDialog = null
}
@@ -851,15 +859,13 @@ class ReaderActivity : EhActivity() {
/**
* Forces the user preferred [orientation] on the activity.
* Returns true if a restart is required.
*/
private fun setOrientation(orientation: Int): Boolean {
private fun setOrientation(orientation: Int) {
val newOrientation = OrientationType.fromPreference(orientation)
val restart = newOrientation.flag != requestedOrientation
if (restart) {
if (newOrientation.flag != requestedOrientation) {
requestedOrientation = newOrientation.flag
}
return restart
updateOrientationShortcut(ReaderPreferences.defaultOrientationType().get())
}
/**
@@ -46,7 +46,7 @@ fun CombinedCircularProgressIndicator(progress: Float) {
label = "rotation",
)
CircularProgressIndicator(
progress = animatedProgress,
progress = { animatedProgress },
modifier = Modifier.rotate(rotation),
)
}
@@ -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>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="color_filter_modes">
<item>@string/label_default</item>
<item>@string/filter_mode_multiply</item>
<item>@string/filter_mode_screen</item>
<!-- Attributes specific for SDK 28 and up -->
<item>@string/filter_mode_overlay</item>
<item>@string/filter_mode_lighten</item>
<item>@string/filter_mode_darken</item>
</string-array>
</resources>
+5 -2
View File
@@ -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>
@@ -585,6 +585,9 @@
<string name="pref_color_filter_mode">滤镜调和模式</string>
<string name="filter_mode_multiply">色彩增值</string>
<string name="filter_mode_screen">滤色</string>
<string name="filter_mode_overlay">覆盖</string>
<string name="filter_mode_lighten">减淡/变亮</string>
<string name="filter_mode_darken">加深/变暗</string>
<string name="pref_cutout_short">在刘海屏区域显示内容</string>
<string name="action_menu">菜单</string>
<string name="webtoon_side_padding_25">25%</string>
@@ -496,6 +496,9 @@
<string name="pref_color_filter_mode">濾鏡融合機制</string>
<string name="filter_mode_multiply">色彩增值</string>
<string name="filter_mode_screen">濾色</string>
<string name="filter_mode_overlay">覆蓋</string>
<string name="filter_mode_lighten">減淡/變亮</string>
<string name="filter_mode_darken">加深/變暗</string>
<string name="pref_read_with_long_tap">長按顯示</string>
<string name="pref_reader_theme">背景顏色</string>
<string name="white_background">白色</string>
@@ -554,6 +554,9 @@
<string name="pref_color_filter_mode">濾鏡融合機制</string>
<string name="filter_mode_multiply">色彩增值</string>
<string name="filter_mode_screen">濾色</string>
<string name="filter_mode_overlay">覆蓋</string>
<string name="filter_mode_lighten">減淡/變亮</string>
<string name="filter_mode_darken">加深/變暗</string>
<string name="pref_cutout_short">使用螢幕凹口區域</string>
<string name="action_menu">選單</string>
<string name="webtoon_side_padding_25">25%</string>
+5 -2
View File
@@ -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>
@@ -635,6 +635,9 @@
<string name="pref_color_filter_mode">Color filter blend mode</string>
<string name="filter_mode_multiply">Multiply</string>
<string name="filter_mode_screen">Screen</string>
<string name="filter_mode_overlay">Overlay</string>
<string name="filter_mode_lighten">Dodge / Lighten</string>
<string name="filter_mode_darken">Burn / Darken</string>
<string name="pref_keep_screen_on">Keep screen on</string>
<string name="pref_read_with_tapping_inverted">Invert tap zones</string>
<string name="tapping_inverted_none">None</string>
+5
View File
@@ -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>
+1 -1
View File
@@ -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)
+10
View File
@@ -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)
}
}
+1
View File
@@ -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
+8 -3
View File
@@ -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"
+1
View File
@@ -3,6 +3,7 @@ pluginManagement {
gradlePluginPortal()
google()
mavenCentral()
maven("https://storage.googleapis.com/r8-releases/raw")
}
}