Compare commits
41
Commits
1.8.8.0-alpha
...
1.8.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25d4d5e393 | ||
|
|
1743caa869 | ||
|
|
958fdb19b4 | ||
|
|
4b63648a79 | ||
|
|
498b84df37 | ||
|
|
7a4e65259d | ||
|
|
8cb8dcbe0f | ||
|
|
da37b3cdff | ||
|
|
0a1768e481 | ||
|
|
818d16a32d | ||
|
|
e9a5d1cc7c | ||
|
|
dac56f3e32 | ||
|
|
d6cbcdc6cd | ||
|
|
c5c1170383 | ||
|
|
6ed3fb29d9 | ||
|
|
74c6bfa479 | ||
|
|
d44981a70c | ||
|
|
06e411d332 | ||
|
|
f419189749 | ||
|
|
aa1e2102fc | ||
|
|
306ce9ed21 | ||
|
|
544fcb2e88 | ||
|
|
cb01df4071 | ||
|
|
38310b7c5a | ||
|
|
fcd56ff4e9 | ||
|
|
41300a9fd4 | ||
|
|
0bd5f9dc31 | ||
|
|
004ecd0dd3 | ||
|
|
fc24a18ac5 | ||
|
|
a9a8c68b94 | ||
|
|
193d2f8d9e | ||
|
|
538da9b98f | ||
|
|
071b597552 | ||
|
|
8926d74378 | ||
|
|
39a4b03bc3 | ||
|
|
9202bbc7dc | ||
|
|
f5601ebca1 | ||
|
|
2f978b554e | ||
|
|
f48a90d941 | ||
|
|
4f0d9fa83c | ||
|
|
57a747ed9c |
No files matched your search
+17
-13
@@ -57,8 +57,8 @@ android {
|
|||||||
applicationId = "moe.tarsin.ehviewer"
|
applicationId = "moe.tarsin.ehviewer"
|
||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
versionCode = 180038
|
versionCode = 180039
|
||||||
versionName = "1.8.8.0-alpha"
|
versionName = "1.8.8.0"
|
||||||
resourceConfigurations.addAll(
|
resourceConfigurations.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
"zh",
|
"zh",
|
||||||
@@ -95,6 +95,8 @@ android {
|
|||||||
freeCompilerArgs = listOf(
|
freeCompilerArgs = listOf(
|
||||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||||
"-progressive",
|
"-progressive",
|
||||||
|
"-Xjvm-default=all",
|
||||||
|
"-Xlambdas=indy",
|
||||||
|
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
@@ -156,22 +158,22 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||||
implementation("androidx.activity:activity-compose:1.7.1")
|
implementation("androidx.activity:activity-compose:1.7.2")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
|
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
|
||||||
implementation("androidx.browser:browser:1.5.0")
|
implementation("androidx.browser:browser:1.5.0")
|
||||||
implementation("androidx.collection:collection-ktx:1.3.0-alpha04")
|
implementation("androidx.collection:collection-ktx:1.3.0-alpha04")
|
||||||
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/compose-material3
|
// https://developer.android.com/jetpack/androidx/releases/compose-material3
|
||||||
// api(platform("androidx.compose:compose-bom:2023.03.00"))
|
// api(platform("androidx.compose:compose-bom:2023.05.00"))
|
||||||
api(platform("dev.chrisbanes.compose:compose-bom:2023.04.00-SNAPSHOT"))
|
api(platform("dev.chrisbanes.compose:compose-bom:2023.04.00-beta01"))
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.material3:material3-window-size-class")
|
implementation("androidx.compose.material3:material3-window-size-class")
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.11.0-alpha04")
|
implementation("androidx.core:core-ktx:1.11.0-beta01")
|
||||||
|
|
||||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha09")
|
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha10")
|
||||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
||||||
implementation("androidx.drawerlayout:drawerlayout:1.2.0")
|
implementation("androidx.drawerlayout:drawerlayout:1.2.0")
|
||||||
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
||||||
@@ -184,8 +186,8 @@ dependencies {
|
|||||||
implementation("androidx.navigation:navigation-compose:$nav_version")
|
implementation("androidx.navigation:navigation-compose:$nav_version")
|
||||||
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/paging
|
// https://developer.android.com/jetpack/androidx/releases/paging
|
||||||
implementation("androidx.paging:paging-common-ktx:3.2.0-alpha05")
|
implementation("androidx.paging:paging-common-ktx:3.2.0-alpha06")
|
||||||
implementation("androidx.paging:paging-compose:1.0.0-alpha19")
|
implementation("androidx.paging:paging-compose:1.0.0-alpha20")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||||
|
|
||||||
@@ -205,7 +207,7 @@ dependencies {
|
|||||||
implementation("com.google.android.material:material:1.9.0")
|
implementation("com.google.android.material:material:1.9.0")
|
||||||
|
|
||||||
// https://square.github.io/okhttp/changelogs/changelog/
|
// https://square.github.io/okhttp/changelogs/changelog/
|
||||||
implementation("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11")
|
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11"))
|
||||||
implementation("com.squareup.okhttp3:okhttp-coroutines")
|
implementation("com.squareup.okhttp3:okhttp-coroutines")
|
||||||
|
|
||||||
implementation("com.squareup.okio:okio-jvm:3.3.0")
|
implementation("com.squareup.okio:okio-jvm:3.3.0")
|
||||||
@@ -217,12 +219,14 @@ dependencies {
|
|||||||
implementation("dev.rikka.rikkax.preference:simplemenu-preference:1.0.3")
|
implementation("dev.rikka.rikkax.preference:simplemenu-preference:1.0.3")
|
||||||
implementation("dev.rikka.rikkax.material:material-preference:2.0.0")
|
implementation("dev.rikka.rikkax.material:material-preference:2.0.0")
|
||||||
|
|
||||||
implementation(platform("io.arrow-kt:arrow-stack:2.0.0-SNAPSHOT"))
|
implementation(platform("io.arrow-kt:arrow-stack:1.2.0-RC"))
|
||||||
implementation("io.arrow-kt:arrow-functions")
|
|
||||||
implementation("io.arrow-kt:arrow-fx-coroutines")
|
implementation("io.arrow-kt:arrow-fx-coroutines")
|
||||||
|
|
||||||
// https://coil-kt.github.io/coil/changelog/
|
// https://coil-kt.github.io/coil/changelog/
|
||||||
implementation("io.coil-kt:coil-compose:2.3.0")
|
implementation(platform("io.coil-kt:coil-bom:2.4.0"))
|
||||||
|
implementation("io.coil-kt:coil-compose")
|
||||||
|
implementation("io.coil-kt:coil-gif")
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.5.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.5.1")
|
||||||
implementation("org.jsoup:jsoup:1.16.1")
|
implementation("org.jsoup:jsoup:1.16.1")
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.ImageLoaderFactory
|
import coil.ImageLoaderFactory
|
||||||
|
import coil.decode.ImageDecoderDecoder
|
||||||
import coil.util.DebugLogger
|
import coil.util.DebugLogger
|
||||||
import com.hippo.ehviewer.client.EhCookieStore
|
import com.hippo.ehviewer.client.EhCookieStore
|
||||||
import com.hippo.ehviewer.client.EhDns
|
import com.hippo.ehviewer.client.EhDns
|
||||||
@@ -137,7 +138,10 @@ class EhApplication : Application(), DefaultLifecycleObserver, ImageLoaderFactor
|
|||||||
override fun newImageLoader(): ImageLoader {
|
override fun newImageLoader(): ImageLoader {
|
||||||
return ImageLoader.Builder(this).apply {
|
return ImageLoader.Builder(this).apply {
|
||||||
okHttpClient(nonCacheOkHttpClient)
|
okHttpClient(nonCacheOkHttpClient)
|
||||||
components { add(MergeInterceptor) }
|
components {
|
||||||
|
add { result, options, _ -> ImageDecoderDecoder(result.source, options, false) }
|
||||||
|
add(MergeInterceptor)
|
||||||
|
}
|
||||||
diskCache(imageCache)
|
diskCache(imageCache)
|
||||||
crossfade(300)
|
crossfade(300)
|
||||||
error(R.drawable.image_failed)
|
error(R.drawable.image_failed)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import java.net.Socket
|
|||||||
import javax.net.ssl.SSLSocket
|
import javax.net.ssl.SSLSocket
|
||||||
import javax.net.ssl.SSLSocketFactory
|
import javax.net.ssl.SSLSocketFactory
|
||||||
|
|
||||||
|
private const val EXCEPTIONAL_DOMAIN = "hath.network"
|
||||||
|
|
||||||
object EhSSLSocketFactory : SSLSocketFactory() {
|
object EhSSLSocketFactory : SSLSocketFactory() {
|
||||||
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
||||||
override fun getDefaultCipherSuites(): Array<String> {
|
override fun getDefaultCipherSuites(): Array<String> {
|
||||||
@@ -34,7 +36,7 @@ object EhSSLSocketFactory : SSLSocketFactory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
||||||
val address = s.inetAddress.hostAddress.takeIf { EhDns.isInHosts(host) }
|
val address = s.inetAddress.hostAddress.takeIf { EhDns.isInHosts(host) || EXCEPTIONAL_DOMAIN in host }
|
||||||
val socket = sslSocketFactory.createSocket(s, address ?: host, port, autoClose) as SSLSocket
|
val socket = sslSocketFactory.createSocket(s, address ?: host, port, autoClose) as SSLSocket
|
||||||
val sslSession = socket.session
|
val sslSession = socket.session
|
||||||
Log.d(
|
Log.d(
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.hippo.ehviewer.coil
|
|
||||||
|
|
||||||
import coil.disk.DiskCache
|
|
||||||
|
|
||||||
inline fun DiskCache.edit(key: String, block: DiskCache.Editor.() -> Unit): Boolean {
|
|
||||||
val editor = edit(key) ?: return false
|
|
||||||
editor.runCatching {
|
|
||||||
block(this)
|
|
||||||
}.onFailure {
|
|
||||||
editor.abort()
|
|
||||||
throw it
|
|
||||||
}.onSuccess {
|
|
||||||
editor.commit()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun DiskCache.read(key: String, block: DiskCache.Snapshot.() -> Unit): Boolean {
|
|
||||||
(get(key) ?: return false).use { block(it) }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hippo.ehviewer.coil
|
||||||
|
|
||||||
|
import coil.disk.DiskCache
|
||||||
|
|
||||||
|
suspend inline fun DiskCache.edit(key: String, crossinline block: suspend DiskCache.Editor.() -> Unit): Boolean {
|
||||||
|
val editor = openEditor(key) ?: return false
|
||||||
|
editor.runCatching {
|
||||||
|
block(this)
|
||||||
|
}.onFailure {
|
||||||
|
editor.abort()
|
||||||
|
throw it
|
||||||
|
}.onSuccess {
|
||||||
|
editor.commit()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> DiskCache.read(key: String, block: DiskCache.Snapshot.() -> T): T? = openSnapshot(key)?.use(block)
|
||||||
@@ -11,6 +11,7 @@ import coil.request.ImageRequest
|
|||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||||
|
import com.hippo.ehviewer.client.data.NormalGalleryPreview
|
||||||
import com.hippo.ehviewer.client.thumbUrl
|
import com.hippo.ehviewer.client.thumbUrl
|
||||||
import com.hippo.ehviewer.client.url
|
import com.hippo.ehviewer.client.url
|
||||||
|
|
||||||
@@ -19,14 +20,13 @@ fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
|
|||||||
data(info.thumbUrl)
|
data(info.thumbUrl)
|
||||||
memoryCacheKey(key)
|
memoryCacheKey(key)
|
||||||
diskCacheKey(key)
|
diskCacheKey(key)
|
||||||
size(Size.ORIGINAL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
|
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
|
||||||
data(preview.url)
|
data(preview.url)
|
||||||
memoryCacheKey(preview.imageKey)
|
memoryCacheKey(preview.imageKey)
|
||||||
diskCacheKey(preview.imageKey)
|
diskCacheKey(preview.imageKey)
|
||||||
size(Size.ORIGINAL)
|
if (preview is NormalGalleryPreview) size(Size.ORIGINAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val stubResult = DecodeResult(ColorDrawable(Color.BLACK), false)
|
private val stubResult = DecodeResult(ColorDrawable(Color.BLACK), false)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
|
|||||||
|
|
||||||
private fun containInCache(index: Int): Boolean {
|
private fun containInCache(index: Int): Boolean {
|
||||||
val key = getImageKey(mGid, index)
|
val key = getImageKey(mGid, index)
|
||||||
return sCache[key]?.use { true } ?: false
|
return sCache.read(key) { true } ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun containInDownloadDir(index: Int): Boolean {
|
private fun containInDownloadDir(index: Int): Boolean {
|
||||||
@@ -84,11 +84,11 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
|
|||||||
it sendTo outFd
|
it sendTo outFd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
true
|
||||||
}.getOrElse {
|
} ?: false
|
||||||
|
}.onFailure {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
}.getOrDefault(false)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun contains(index: Int): Boolean {
|
operator fun contains(index: Int): Boolean {
|
||||||
@@ -199,12 +199,11 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
|
|||||||
val key = getImageKey(mGid, index)
|
val key = getImageKey(mGid, index)
|
||||||
|
|
||||||
// Read from diskCache first
|
// Read from diskCache first
|
||||||
sCache[key]?.use { snapshot ->
|
sCache.read(key) {
|
||||||
runCatching {
|
runCatching {
|
||||||
UniFile.fromFile(snapshot.data.toFile())!!.openFileDescriptor("r").use {
|
UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use {
|
||||||
it sendTo toFd
|
it sendTo toFd
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
|
||||||
return true
|
return true
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
@@ -235,15 +234,14 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
|
|||||||
|
|
||||||
fun getExtension(index: Int): String? {
|
fun getExtension(index: Int): String? {
|
||||||
val key = getImageKey(mGid, index)
|
val key = getImageKey(mGid, index)
|
||||||
return sCache[key]?.use { it.metadata.toNioPath().readText() }
|
return sCache.read(key) { metadata.toNioPath().readText() }
|
||||||
?: downloadDir?.let { findImageFile(it, index) }
|
?: downloadDir?.let { findImageFile(it, index) }?.name.let { FileUtils.getExtensionFromFilename(it) }
|
||||||
?.name.let { FileUtils.getExtensionFromFilename(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getImageSource(index: Int): CloseableSource? {
|
fun getImageSource(index: Int): CloseableSource? {
|
||||||
if (mode == SpiderQueen.MODE_READ) {
|
if (mode == SpiderQueen.MODE_READ) {
|
||||||
val key = getImageKey(mGid, index)
|
val key = getImageKey(mGid, index)
|
||||||
val snapshot = sCache[key]
|
val snapshot = sCache.openSnapshot(key)
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
val source = ImageDecoder.createSource(snapshot.data.toFile())
|
val source = ImageDecoder.createSource(snapshot.data.toFile())
|
||||||
return object : CloseableSource, AutoCloseable by snapshot {
|
return object : CloseableSource, AutoCloseable by snapshot {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.hippo.ehviewer.spider
|
|||||||
|
|
||||||
import coil.disk.DiskCache
|
import coil.disk.DiskCache
|
||||||
import com.hippo.ehviewer.EhApplication
|
import com.hippo.ehviewer.EhApplication
|
||||||
|
import com.hippo.ehviewer.coil.edit
|
||||||
|
import com.hippo.ehviewer.coil.read
|
||||||
import com.hippo.ehviewer.legacy.readLegacySpiderInfo
|
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
|
||||||
@@ -10,6 +12,7 @@ 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 java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -35,15 +38,13 @@ fun SpiderInfo.write(file: UniFile) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SpiderInfo.saveToCache() {
|
suspend fun SpiderInfo.saveToCache() {
|
||||||
val entry = spiderInfoCache.edit(gid.toString()) ?: return
|
runSuspendCatching {
|
||||||
runCatching {
|
spiderInfoCache.edit(gid.toString()) {
|
||||||
entry.data.toFile().writeBytes(Cbor.encodeToByteArray(this))
|
data.toFile().writeBytes(Cbor.encodeToByteArray(this))
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
entry.abort()
|
|
||||||
}.onSuccess {
|
|
||||||
entry.commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,14 +55,13 @@ private val spiderInfoCache by lazy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun readFromCache(gid: Long): SpiderInfo? {
|
fun readFromCache(gid: Long): SpiderInfo? {
|
||||||
val snapshot = spiderInfoCache[gid.toString()] ?: return null
|
return spiderInfoCache.read(gid.toString()) {
|
||||||
return runCatching {
|
runCatching {
|
||||||
snapshot.use {
|
Cbor.decodeFromByteArray<SpiderInfo>(data.toFile().readBytes())
|
||||||
return Cbor.decodeFromByteArray(it.data.toFile().readBytes())
|
}.onFailure {
|
||||||
}
|
it.printStackTrace()
|
||||||
}.onFailure {
|
}.getOrNull()
|
||||||
it.printStackTrace()
|
}
|
||||||
}.getOrNull()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readCompatFromUniFile(file: UniFile): SpiderInfo? {
|
fun readCompatFromUniFile(file: UniFile): SpiderInfo? {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import kotlinx.coroutines.currentCoroutineContext
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@@ -335,8 +336,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
|||||||
it.gid == galleryInfo.gid && it.token == galleryInfo.token
|
it.gid == galleryInfo.gid && it.token == galleryInfo.token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} ?: readFromCache(galleryInfo.gid)?.takeIf { it.gid == galleryInfo.gid && it.token == galleryInfo.token }
|
||||||
?: readFromCache(galleryInfo.gid)?.takeIf { it.gid == galleryInfo.gid && it.token == galleryInfo.token }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(ParseException::class)
|
@Throws(ParseException::class)
|
||||||
@@ -427,7 +427,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
|||||||
private fun writeSpiderInfoToLocal() {
|
private fun writeSpiderInfoToLocal() {
|
||||||
if (!isReady) return
|
if (!isReady) return
|
||||||
mSpiderDen.downloadDir?.run { createFile(SPIDER_INFO_FILENAME).also { mSpiderInfo.write(it) } }
|
mSpiderDen.downloadDir?.run { createFile(SPIDER_INFO_FILENAME).also { mSpiderInfo.write(it) } }
|
||||||
mSpiderInfo.saveToCache()
|
runBlocking { mSpiderInfo.saveToCache() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isStateDone(state: Int): Boolean {
|
private fun isStateDone(state: Int): Boolean {
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ package com.hippo.ehviewer.ui
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
@@ -103,7 +106,10 @@ fun GalleryInfoBottomSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalBottomSheet(onDismissRequest) {
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
windowInsets = WindowInsets(0),
|
||||||
|
) {
|
||||||
check(data.size == keys.size)
|
check(data.size == keys.size)
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
@@ -114,23 +120,25 @@ fun GalleryInfoBottomSheet(
|
|||||||
Text(text = stringResource(id = R.string.gallery_info), style = MaterialTheme.typography.titleLarge)
|
Text(text = stringResource(id = R.string.gallery_info), style = MaterialTheme.typography.titleLarge)
|
||||||
}
|
}
|
||||||
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.labelLarge) {
|
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.labelLarge) {
|
||||||
keys.forEachIndexed { index, i ->
|
LazyColumn {
|
||||||
val key = stringResource(i)
|
itemsIndexed(keys) { index, i ->
|
||||||
Row(
|
val key = stringResource(i)
|
||||||
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)).clickable {
|
Row(
|
||||||
if (index == INDEX_PARENT) {
|
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)).clickable {
|
||||||
data[index]?.let { navController.navWithUrl(it) }
|
if (index == INDEX_PARENT) {
|
||||||
} else {
|
data[index]?.let { navController.navWithUrl(it) }
|
||||||
context tellClipboardWithToast data[index]
|
} else {
|
||||||
if (index == INDEX_URL) {
|
context tellClipboardWithToast data[index]
|
||||||
// Save it to avoid detect the gallery
|
if (index == INDEX_URL) {
|
||||||
Settings.putClipboardTextHashCode(data[index].hashCode())
|
// Save it to avoid detect the gallery
|
||||||
|
Settings.putClipboardTextHashCode(data[index].hashCode())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}.fillMaxWidth(),
|
||||||
}.fillMaxWidth(),
|
) {
|
||||||
) {
|
Text(key, modifier = Modifier.width(90.dp).padding(8.dp))
|
||||||
Text(key, modifier = Modifier.width(90.dp).padding(8.dp))
|
Text(data[index].orEmpty(), modifier = Modifier.padding(8.dp))
|
||||||
Text(data[index].orEmpty(), modifier = Modifier.padding(8.dp))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ import com.hippo.ehviewer.util.addTextToClipboard
|
|||||||
import com.hippo.ehviewer.util.getClipboardManager
|
import com.hippo.ehviewer.util.getClipboardManager
|
||||||
import com.hippo.ehviewer.util.getParcelableExtraCompat
|
import com.hippo.ehviewer.util.getParcelableExtraCompat
|
||||||
import com.hippo.ehviewer.util.getUrlFromClipboard
|
import com.hippo.ehviewer.util.getUrlFromClipboard
|
||||||
import com.hippo.ehviewer.util.initLauncher
|
|
||||||
import com.hippo.ehviewer.yorozuya.IOUtils
|
import com.hippo.ehviewer.yorozuya.IOUtils
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
@@ -91,10 +90,6 @@ class MainActivity : EhActivity() {
|
|||||||
private val connectivityManager by lazy { getSystemService<ConnectivityManager>()!! }
|
private val connectivityManager by lazy { getSystemService<ConnectivityManager>()!! }
|
||||||
private val availableNetworks = mutableListOf<Network>()
|
private val availableNetworks = mutableListOf<Network>()
|
||||||
|
|
||||||
init {
|
|
||||||
initLauncher()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveImageToTempFile(uri: Uri): File? {
|
private fun saveImageToTempFile(uri: Uri): File? {
|
||||||
val src = ImageDecoder.createSource(contentResolver, uri)
|
val src = ImageDecoder.createSource(contentResolver, uri)
|
||||||
val bitmap = runCatching {
|
val bitmap = runCatching {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.hippo.ehviewer.ui.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
|
||||||
|
|
||||||
|
private val remembered = hashMapOf<String, Any>()
|
||||||
|
|
||||||
|
// This works mostly like [rememberSaveable], but saves data in global hashmap, to avoid [TransactionTooLargeException]
|
||||||
|
@Composable
|
||||||
|
fun <T : Any> rememberMemorized(
|
||||||
|
vararg inputs: Any?,
|
||||||
|
key: String? = null,
|
||||||
|
init: () -> T,
|
||||||
|
): T {
|
||||||
|
// key is the one provided by the user or the one generated by the compose runtime
|
||||||
|
val finalKey = if (!key.isNullOrEmpty()) {
|
||||||
|
key
|
||||||
|
} else {
|
||||||
|
// currentCompositeKeyHash is unreliable here
|
||||||
|
// Is this Composable inlined or its groups elided?
|
||||||
|
// Or a compose compiler bug?
|
||||||
|
init.javaClass.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val registry = LocalSaveableStateRegistry.current
|
||||||
|
// value is restored using the registry or created via [init] lambda
|
||||||
|
val value = remember(*inputs) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val restored = registry?.consumeRestored(finalKey)?.let {
|
||||||
|
remembered.remove(it as String)
|
||||||
|
} as T?
|
||||||
|
restored ?: init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-register if the registry or key has been changed
|
||||||
|
if (registry != null) {
|
||||||
|
// we want to use the latest instances of saver and value in the valueProvider lambda
|
||||||
|
// without restarting DisposableEffect as it would cause re-registering the provider in
|
||||||
|
// the different order. so we use rememberUpdatedState.
|
||||||
|
val valueState = rememberUpdatedState(value)
|
||||||
|
|
||||||
|
DisposableEffect(registry, finalKey) {
|
||||||
|
val valueProvider = {
|
||||||
|
finalKey.also { remembered[it] = valueState.value }
|
||||||
|
}
|
||||||
|
val entry = registry.registerProvider(finalKey, valueProvider)
|
||||||
|
onDispose {
|
||||||
|
entry.unregister()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
|
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||||
import com.hippo.ehviewer.databinding.WidgetContentLayoutBinding
|
import com.hippo.ehviewer.databinding.WidgetContentLayoutBinding
|
||||||
import com.hippo.ehviewer.ui.legacy.ViewTransition.OnShowViewListener
|
import com.hippo.ehviewer.ui.legacy.ViewTransition.OnShowViewListener
|
||||||
import com.hippo.ehviewer.ui.legacy.easyrecyclerview.HandlerDrawable
|
import com.hippo.ehviewer.ui.legacy.easyrecyclerview.HandlerDrawable
|
||||||
@@ -41,7 +42,7 @@ import com.hippo.ehviewer.yorozuya.collect.IntList
|
|||||||
import rikka.core.res.resolveColor
|
import rikka.core.res.resolveColor
|
||||||
|
|
||||||
class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
|
class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
|
||||||
private lateinit var mContentHelper: ContentHelper<*>
|
private lateinit var mContentHelper: ContentHelper
|
||||||
private val mRecyclerViewOriginBottom: Int
|
private val mRecyclerViewOriginBottom: Int
|
||||||
private val mFastScrollerOriginBottom: Int
|
private val mFastScrollerOriginBottom: Int
|
||||||
private val binding: WidgetContentLayoutBinding
|
private val binding: WidgetContentLayoutBinding
|
||||||
@@ -64,7 +65,7 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
val fastScroller
|
val fastScroller
|
||||||
get() = binding.fastScroller
|
get() = binding.fastScroller
|
||||||
|
|
||||||
fun setHelper(helper: ContentHelper<*>) {
|
fun setHelper(helper: ContentHelper) {
|
||||||
mContentHelper = helper
|
mContentHelper = helper
|
||||||
helper.init(this)
|
helper.init(this)
|
||||||
}
|
}
|
||||||
@@ -128,7 +129,7 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
super.onRestoreInstanceState(mContentHelper.restoreInstanceState(state))
|
super.onRestoreInstanceState(mContentHelper.restoreInstanceState(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ContentHelper<E : Parcelable> : OnShowViewListener {
|
abstract class ContentHelper : OnShowViewListener {
|
||||||
/**
|
/**
|
||||||
* Generate task id
|
* Generate task id
|
||||||
*/
|
*/
|
||||||
@@ -143,7 +144,7 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
/**
|
/**
|
||||||
* Store data
|
* Store data
|
||||||
*/
|
*/
|
||||||
private var mData = ArrayList<E>()
|
private var mData = ArrayList<GalleryInfo>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the page divider index
|
* Store the page divider index
|
||||||
@@ -287,10 +288,10 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
mEmptyString = str
|
mEmptyString = str
|
||||||
}
|
}
|
||||||
|
|
||||||
val data: List<E>
|
val data: List<GalleryInfo>
|
||||||
get() = mData
|
get() = mData
|
||||||
|
|
||||||
fun getDataAtEx(location: Int): E? {
|
fun getDataAtEx(location: Int): GalleryInfo? {
|
||||||
return if (location >= 0 && location < mData.size) {
|
return if (location >= 0 && location < mData.size) {
|
||||||
mData[location]
|
mData[location]
|
||||||
} else {
|
} else {
|
||||||
@@ -298,7 +299,7 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val firstVisibleItem: E?
|
val firstVisibleItem: GalleryInfo?
|
||||||
get() = getDataAtEx(LayoutManagerUtils.getFirstVisibleItemPosition(binding.recyclerView.layoutManager))
|
get() = getDataAtEx(LayoutManagerUtils.getFirstVisibleItemPosition(binding.recyclerView.layoutManager))
|
||||||
|
|
||||||
fun size(): Int {
|
fun size(): Int {
|
||||||
@@ -309,8 +310,8 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
return mCurrentTaskId == taskId
|
return mCurrentTaskId == taskId
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun isDuplicate(d1: E, d2: E): Boolean
|
protected abstract fun isDuplicate(d1: GalleryInfo, d2: GalleryInfo): Boolean
|
||||||
private fun removeDuplicateData(data: List<E>, start: Int, end: Int) {
|
private fun removeDuplicateData(data: List<GalleryInfo>, start: Int, end: Int) {
|
||||||
val slicedData = mData.slice(start.coerceAtLeast(0) until end.coerceAtMost(mData.size))
|
val slicedData = mData.slice(start.coerceAtLeast(0) until end.coerceAtMost(mData.size))
|
||||||
data.dropWhile { d1 ->
|
data.dropWhile { d1 ->
|
||||||
slicedData.any { d2 ->
|
slicedData.any { d2 ->
|
||||||
@@ -319,8 +320,8 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onAddData(data: List<E>) {}
|
protected open fun onAddData(data: List<GalleryInfo>) {}
|
||||||
protected open fun onRemoveData(data: List<E>) {}
|
protected open fun onRemoveData(data: List<GalleryInfo>) {}
|
||||||
protected open fun onClearData() {}
|
protected open fun onClearData() {}
|
||||||
fun onGetPageData(
|
fun onGetPageData(
|
||||||
taskId: Int,
|
taskId: Int,
|
||||||
@@ -328,7 +329,7 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
nextPage: Int,
|
nextPage: Int,
|
||||||
prev: String?,
|
prev: String?,
|
||||||
next: String?,
|
next: String?,
|
||||||
data: List<E>,
|
data: List<GalleryInfo>,
|
||||||
) {
|
) {
|
||||||
if (mCurrentTaskId == taskId) {
|
if (mCurrentTaskId == taskId) {
|
||||||
val dataSize: Int
|
val dataSize: Int
|
||||||
@@ -734,7 +735,11 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
val shownView = mViewTransition.shownViewIndex
|
val shownView = mViewTransition.shownViewIndex
|
||||||
bundle.putInt(KEY_SHOWN_VIEW, shownView)
|
bundle.putInt(KEY_SHOWN_VIEW, shownView)
|
||||||
bundle.putString(KEY_TIP, binding.tip.text.toString())
|
bundle.putString(KEY_TIP, binding.tip.text.toString())
|
||||||
bundle.putParcelableArrayList(KEY_DATA, mData)
|
synchronized(contentMap) {
|
||||||
|
val size = contentMap.keys.takeIf { it.isNotEmpty() }?.max()?.inc() ?: 0
|
||||||
|
contentMap[size] = mData
|
||||||
|
bundle.putInt(KEY_DATA, size)
|
||||||
|
}
|
||||||
bundle.putInt(KEY_NEXT_ID, mIdGenerator.nextId())
|
bundle.putInt(KEY_NEXT_ID, mIdGenerator.nextId())
|
||||||
bundle.putParcelable(KEY_PAGE_DIVIDER, mPageDivider)
|
bundle.putParcelable(KEY_PAGE_DIVIDER, mPageDivider)
|
||||||
bundle.putInt(KEY_START_PAGE, mStartPage)
|
bundle.putInt(KEY_START_PAGE, mStartPage)
|
||||||
@@ -749,11 +754,12 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
return if (state is Bundle) {
|
return if (state is Bundle) {
|
||||||
mViewTransition.showView(state.getInt(KEY_SHOWN_VIEW), false)
|
mViewTransition.showView(state.getInt(KEY_SHOWN_VIEW), false)
|
||||||
binding.tip.text = state.getString(KEY_TIP)
|
binding.tip.text = state.getString(KEY_TIP)
|
||||||
@Suppress("DEPRECATION")
|
val data = state.getInt(KEY_DATA)
|
||||||
val newData = state.getParcelableArrayList<E>(KEY_DATA)
|
synchronized(contentMap) {
|
||||||
newData?.let { mData = it }
|
mData = contentMap.remove(data) ?: ArrayList()
|
||||||
|
}
|
||||||
mIdGenerator.setNextId(state.getInt(KEY_NEXT_ID))
|
mIdGenerator.setNextId(state.getInt(KEY_NEXT_ID))
|
||||||
mPageDivider = state.getParcelableCompat(KEY_PAGE_DIVIDER)!!
|
mPageDivider = state.getParcelableCompat(KEY_PAGE_DIVIDER) ?: IntList()
|
||||||
mStartPage = state.getInt(KEY_START_PAGE)
|
mStartPage = state.getInt(KEY_START_PAGE)
|
||||||
mEndPage = state.getInt(KEY_END_PAGE)
|
mEndPage = state.getInt(KEY_END_PAGE)
|
||||||
pages = state.getInt(KEY_PAGES)
|
pages = state.getInt(KEY_PAGES)
|
||||||
@@ -761,7 +767,7 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
mNext = state.getString(KEY_NEXT)
|
mNext = state.getString(KEY_NEXT)
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
notifyDataSetChanged {}
|
notifyDataSetChanged {}
|
||||||
if (newData == null) {
|
if (mData.size == 0) {
|
||||||
mPageDivider.clear()
|
mPageDivider.clear()
|
||||||
mStartPage = 0
|
mStartPage = 0
|
||||||
mEndPage = 0
|
mEndPage = 0
|
||||||
@@ -803,3 +809,8 @@ class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// data "ArrayList<GalleryInfo>" is too large, we cannot put it as Parcelable otherwise TransactionTooLargeException would occur
|
||||||
|
// TODO: convert to paging3 source then keep it in a proper window
|
||||||
|
// @see PagingConfig.maxSize
|
||||||
|
private val contentMap = hashMapOf<Int, ArrayList<GalleryInfo>>()
|
||||||
@@ -28,7 +28,7 @@ import java.time.ZoneOffset
|
|||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo>() {
|
abstract class GalleryInfoContentHelper : ContentHelper() {
|
||||||
@JvmField
|
@JvmField
|
||||||
var jumpTo: String? = null
|
var jumpTo: String? = null
|
||||||
private val listener: FavouriteStatusRouter.Listener
|
private val listener: FavouriteStatusRouter.Listener
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class ImageSearchLayout @JvmOverloads constructor(
|
|||||||
Mdc3Theme {
|
Mdc3Theme {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
fun selectImage() = coroutineScope.launch {
|
fun selectImage() = coroutineScope.launch {
|
||||||
pickVisualMedia(ImageOnly)?.let {
|
context.pickVisualMedia(ImageOnly)?.let {
|
||||||
path = it.toString()
|
path = it.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import android.graphics.drawable.AnimatedImageDrawable
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Size
|
|
||||||
import com.hippo.ehviewer.coil.imageRequest
|
import com.hippo.ehviewer.coil.imageRequest
|
||||||
|
|
||||||
class UnikeryDrawable(private val mTextView: ObservedTextView, url: String) :
|
class UnikeryDrawable(private val mTextView: ObservedTextView, url: String) :
|
||||||
@@ -31,7 +30,6 @@ class UnikeryDrawable(private val mTextView: ObservedTextView, url: String) :
|
|||||||
mTextView.setOnWindowAttachListener(this)
|
mTextView.setOnWindowAttachListener(this)
|
||||||
mImageRequest = mTextView.context.imageRequest {
|
mImageRequest = mTextView.context.imageRequest {
|
||||||
target(onSuccess = { onGetValue(it) })
|
target(onSuccess = { onGetValue(it) })
|
||||||
size(Size.ORIGINAL)
|
|
||||||
data(url)
|
data(url)
|
||||||
}
|
}
|
||||||
load()
|
load()
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import com.hippo.ehviewer.R
|
|||||||
import com.hippo.ehviewer.Settings
|
import com.hippo.ehviewer.Settings
|
||||||
import com.hippo.ehviewer.client.EhUtils
|
import com.hippo.ehviewer.client.EhUtils
|
||||||
import com.hippo.ehviewer.coil.imageRequest
|
import com.hippo.ehviewer.coil.imageRequest
|
||||||
|
import com.hippo.ehviewer.coil.read
|
||||||
import com.hippo.ehviewer.dao.DownloadInfo
|
import com.hippo.ehviewer.dao.DownloadInfo
|
||||||
import com.hippo.ehviewer.databinding.DrawerListRvBinding
|
import com.hippo.ehviewer.databinding.DrawerListRvBinding
|
||||||
import com.hippo.ehviewer.databinding.ItemDownloadBinding
|
import com.hippo.ehviewer.databinding.ItemDownloadBinding
|
||||||
@@ -912,8 +913,8 @@ class DownloadsScene :
|
|||||||
if (!thumbLocation.exists()) {
|
if (!thumbLocation.exists()) {
|
||||||
thumbLocation.ensureFile()
|
thumbLocation.ensureFile()
|
||||||
val key = info.thumbKey!!
|
val key = info.thumbKey!!
|
||||||
imageCache[key]?.use {
|
imageCache.read(key) {
|
||||||
UniFile.fromFile(it.data.toFile())!!.openFileDescriptor("r").use { src ->
|
UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use { src ->
|
||||||
thumbLocation.openFileDescriptor("w").use { dst ->
|
thumbLocation.openFileDescriptor("w").use { dst ->
|
||||||
src sendTo dst
|
src sendTo dst
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ import coil.imageLoader
|
|||||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.hippo.ehviewer.EhApplication.Companion.galleryDetailCache
|
import com.hippo.ehviewer.EhApplication.Companion.galleryDetailCache
|
||||||
|
import com.hippo.ehviewer.EhApplication.Companion.imageCache
|
||||||
import com.hippo.ehviewer.EhDB
|
import com.hippo.ehviewer.EhDB
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
import com.hippo.ehviewer.Settings
|
import com.hippo.ehviewer.Settings
|
||||||
@@ -139,6 +140,7 @@ import com.hippo.ehviewer.client.parser.ParserUtils
|
|||||||
import com.hippo.ehviewer.client.parser.TorrentParser
|
import com.hippo.ehviewer.client.parser.TorrentParser
|
||||||
import com.hippo.ehviewer.coil.imageRequest
|
import com.hippo.ehviewer.coil.imageRequest
|
||||||
import com.hippo.ehviewer.coil.justDownload
|
import com.hippo.ehviewer.coil.justDownload
|
||||||
|
import com.hippo.ehviewer.coil.read
|
||||||
import com.hippo.ehviewer.dao.DownloadInfo
|
import com.hippo.ehviewer.dao.DownloadInfo
|
||||||
import com.hippo.ehviewer.dao.Filter
|
import com.hippo.ehviewer.dao.Filter
|
||||||
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
||||||
@@ -1217,7 +1219,7 @@ class GalleryDetailScene : BaseScene(), DownloadInfoListener {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val key = composeBindingGI!!.thumbKey!!
|
val key = composeBindingGI!!.thumbKey!!
|
||||||
val path = imageLoader(context).diskCache!![key]!!.use { it.data }
|
val path = imageCache.read(key) { data }
|
||||||
val lub = ListUrlBuilder()
|
val lub = ListUrlBuilder()
|
||||||
lub.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
|
lub.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
|
||||||
lub.imagePath = path.toString()
|
lub.imagePath = path.toString()
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import com.hippo.ehviewer.coil.imageRequest
|
|||||||
import com.hippo.ehviewer.coil.justDownload
|
import com.hippo.ehviewer.coil.justDownload
|
||||||
import com.hippo.ehviewer.ui.compose.data.EhPreviewItem
|
import com.hippo.ehviewer.ui.compose.data.EhPreviewItem
|
||||||
import com.hippo.ehviewer.ui.compose.rememberDialogState
|
import com.hippo.ehviewer.ui.compose.rememberDialogState
|
||||||
|
import com.hippo.ehviewer.ui.compose.rememberMemorized
|
||||||
import com.hippo.ehviewer.ui.compose.setMD3Content
|
import com.hippo.ehviewer.ui.compose.setMD3Content
|
||||||
import com.hippo.ehviewer.ui.legacy.recyclerview.calculateSuitableSpanCount
|
import com.hippo.ehviewer.ui.legacy.recyclerview.calculateSuitableSpanCount
|
||||||
import com.hippo.ehviewer.ui.navToReader
|
import com.hippo.ehviewer.ui.navToReader
|
||||||
@@ -72,7 +73,7 @@ class GalleryPreviewScreen : Fragment() {
|
|||||||
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 {
|
||||||
val galleryDetail = rememberSaveable { requireArguments().getParcelableCompat<GalleryDetail>(KEY_GALLERY_DETAIL)!! }
|
val galleryDetail = remember { requireArguments().getParcelableCompat<GalleryDetail>(KEY_GALLERY_DETAIL)!! }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
fun onPreviewCLick(index: Int) = context.navToReader(galleryDetail, index)
|
fun onPreviewCLick(index: Int) = context.navToReader(galleryDetail, index)
|
||||||
val toNextPage = rememberSaveable { requireArguments().getBoolean(KEY_NEXT_PAGE) }
|
val toNextPage = rememberSaveable { requireArguments().getBoolean(KEY_NEXT_PAGE) }
|
||||||
@@ -118,14 +119,14 @@ class GalleryPreviewScreen : Fragment() {
|
|||||||
initialKey = goto
|
initialKey = goto
|
||||||
}
|
}
|
||||||
|
|
||||||
val previewPagesMap = rememberSaveable { mutableMapOf<Int, PreviewPage>().apply { put(1, galleryDetail.previewList) } }
|
val previewPagesMap = rememberMemorized { mutableMapOf<Int, PreviewPage>().apply { put(1, galleryDetail.previewList) } }
|
||||||
val data = remember(initialKey) {
|
val data = remember(initialKey) {
|
||||||
Pager(PagingConfig(1, prefetchDistance = 4, enablePlaceholders = false, initialLoadSize = 1), initialKey) {
|
Pager(PagingConfig(1, prefetchDistance = 4, enablePlaceholders = false, initialLoadSize = 1), initialKey) {
|
||||||
object : PagingSource<Int, PreviewPage>() {
|
object : PagingSource<Int, PreviewPage>() {
|
||||||
override fun getRefreshKey(state: PagingState<Int, PreviewPage>) = null
|
override fun getRefreshKey(state: PagingState<Int, PreviewPage>) = null
|
||||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PreviewPage> {
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PreviewPage> {
|
||||||
val up = params.key ?: 1
|
val up = params.key ?: 1
|
||||||
val end = up + params.loadSize - 1
|
val end = (up + params.loadSize - 1).coerceAtMost(galleryDetail.previewPages)
|
||||||
runSuspendCatching {
|
runSuspendCatching {
|
||||||
(up..end).mapNotNull { it.takeUnless { previewPagesMap.contains(it) } }
|
(up..end).mapNotNull { it.takeUnless { previewPagesMap.contains(it) } }
|
||||||
.parMap(Dispatchers.IO) { getPreviewListByPage(it - 1).apply { previewPagesMap[it] = this } }
|
.parMap(Dispatchers.IO) { getPreviewListByPage(it - 1).apply { previewPagesMap[it] = this } }
|
||||||
|
|||||||
@@ -1,41 +1,69 @@
|
|||||||
package com.hippo.ehviewer.util
|
package com.hippo.ehviewer.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType
|
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import arrow.atomic.Atomic
|
import androidx.lifecycle.Lifecycle
|
||||||
import arrow.atomic.update
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import arrow.atomic.AtomicInt
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import rikka.core.util.ContextUtils.requireActivity
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
// Fuck off the silly Android launcher and callback :)
|
// Fuck off the silly Android launcher and callback :)
|
||||||
|
|
||||||
private typealias LauncherAndCallback<K, V> = Atomic<Pair<ActivityResultLauncher<K>, (V) -> Unit>>
|
private val atomicInteger = AtomicInt()
|
||||||
private fun <K, V> LauncherAndCallback<K, V>.cleanup() = update { it.copy {} }
|
|
||||||
private lateinit var requestPermission: LauncherAndCallback<String, Boolean>
|
|
||||||
private lateinit var pickVisualMedia: LauncherAndCallback<PickVisualMediaRequest, Uri?>
|
|
||||||
private suspend fun <K, V> LauncherAndCallback<K, V>.await(key: K) = withUIContext {
|
|
||||||
suspendCancellableCoroutine { cont ->
|
|
||||||
updateAndGet { prev -> prev.copy { cont.resume(it) } }.first.launch(key)
|
|
||||||
cont.invokeOnCancellation { cleanup() } // Drop continuation when cancelled
|
|
||||||
}.apply { cleanup() } // Drop continuation when completed
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ComponentActivity.initLauncher() {
|
private val Context.lifecycle: Lifecycle
|
||||||
requestPermission = Atomic(registerForActivityResult(ActivityResultContracts.RequestPermission()) { requestPermission.get().second(it) } to {})
|
get() {
|
||||||
pickVisualMedia = Atomic(registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { pickVisualMedia.get().second(it) } to {})
|
var context: Context? = this
|
||||||
|
while (true) {
|
||||||
|
when (context) {
|
||||||
|
is LifecycleOwner -> return context.lifecycle
|
||||||
|
!is ContextWrapper -> error("This should never happen!")
|
||||||
|
else -> context = context.baseContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <I, O> Context.awaitActivityResult(contract: ActivityResultContract<I, O>, input: I): O {
|
||||||
|
val key = "activity_rq#${atomicInteger.getAndIncrement()}"
|
||||||
|
var launcher: ActivityResultLauncher<I>? = null
|
||||||
|
var observer: LifecycleEventObserver? = null
|
||||||
|
observer = LifecycleEventObserver { _, event ->
|
||||||
|
if (Lifecycle.Event.ON_DESTROY == event) {
|
||||||
|
launcher?.unregister()
|
||||||
|
if (observer != null) {
|
||||||
|
lifecycle.removeObserver(observer!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycle.addObserver(observer)
|
||||||
|
return withUIContext {
|
||||||
|
suspendCoroutine { cont -> // No cancellation support here since we cannot cancel a launched Intent
|
||||||
|
val activity = requireActivity<ComponentActivity>(this@awaitActivityResult)
|
||||||
|
launcher = activity.activityResultRegistry.register(key, contract) {
|
||||||
|
launcher?.unregister()
|
||||||
|
lifecycle.removeObserver(observer)
|
||||||
|
cont.resume(it)
|
||||||
|
}.apply { launch(input) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Context.requestPermission(key: String): Boolean {
|
suspend fun Context.requestPermission(key: String): Boolean {
|
||||||
if (ContextCompat.checkSelfPermission(this, key) == PackageManager.PERMISSION_GRANTED) return true
|
if (ContextCompat.checkSelfPermission(this, key) == PackageManager.PERMISSION_GRANTED) return true
|
||||||
return requestPermission.await(key)
|
return awaitActivityResult(ActivityResultContracts.RequestPermission(), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun pickVisualMedia(type: VisualMediaType): Uri? = pickVisualMedia.await(PickVisualMediaRequest.Builder().setMediaType(type).build())
|
suspend fun Context.pickVisualMedia(type: VisualMediaType): Uri? = awaitActivityResult(ActivityResultContracts.PickVisualMedia(), PickVisualMediaRequest.Builder().setMediaType(type).build())
|
||||||
+1
-1
@@ -15,6 +15,6 @@ tasks.register("Delete", Delete::class) {
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools:r8:8.2.6-dev")
|
classpath("com.android.tools:r8:8.2.7-dev")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user