Compare commits

...
19 Commits
Author SHA1 Message Date
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
72 changed files with 295 additions and 354 deletions

No files matched your search

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