This commit is contained in:
Koitharu
2025-03-01 17:07:02 +02:00
parent 7a01fdd04c
commit 7fc2d2f36f
4 changed files with 70 additions and 42 deletions

View File

@@ -1,13 +1,12 @@
package org.koitharu.kotatsu.core.ui.image package org.koitharu.kotatsu.core.ui.image
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.OnPreDrawListener
import android.widget.ImageView import android.widget.ImageView
import coil3.size.Dimension import coil3.size.Dimension
import coil3.size.Size import coil3.size.Size
import coil3.size.ViewSizeResolver import coil3.size.ViewSizeResolver
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -20,31 +19,67 @@ class CoverSizeResolver(
) : ViewSizeResolver<ImageView> { ) : ViewSizeResolver<ImageView> {
override suspend fun size(): Size { override suspend fun size(): Size {
// Fast path: the view is already measured.
getSize()?.let { return it } getSize()?.let { return it }
return suspendCancellableCoroutine { cont ->
val layoutListener = LayoutListener(cont) // Slow path: wait for the view to be measured.
view.addOnLayoutChangeListener(layoutListener) return suspendCancellableCoroutine { continuation ->
cont.invokeOnCancellation { val viewTreeObserver = view.viewTreeObserver
view.removeOnLayoutChangeListener(layoutListener)
val preDrawListener = object : OnPreDrawListener {
private var isResumed = false
override fun onPreDraw(): Boolean {
val size = getSize()
if (size != null) {
viewTreeObserver.removePreDrawListenerSafe(this)
if (!isResumed) {
isResumed = true
continuation.resume(size)
}
}
return true
}
}
viewTreeObserver.addOnPreDrawListener(preDrawListener)
continuation.invokeOnCancellation {
viewTreeObserver.removePreDrawListenerSafe(preDrawListener)
} }
} }
} }
private fun getSize(): Size? { private fun getSize(): Size? {
val lp = view.layoutParams var width = getWidth()
var width = getDimension(lp.width, view.width, view.paddingLeft + view.paddingRight) var height = getHeight()
var height = getDimension(lp.height, view.height, view.paddingTop + view.paddingBottom) when {
if (width == null && height == null) { width == null && height == null -> {
return null return null
} }
if (height == null && width != null) { height == null && width != null -> {
height = Dimension((width.px * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).roundToInt()) height = Dimension((width.px * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).roundToInt())
} else if (width == null && height != null) { }
width = Dimension((height.px * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).roundToInt()) width == null && height != null -> {
width = Dimension((height.px * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).roundToInt())
}
} }
return Size(checkNotNull(width), checkNotNull(height)) return Size(checkNotNull(width), checkNotNull(height))
} }
private fun getWidth() = getDimension(
paramSize = view.layoutParams?.width ?: -1,
viewSize = view.width,
paddingSize = if (subtractPadding) view.paddingLeft + view.paddingRight else 0
)
private fun getHeight() = getDimension(
paramSize = view.layoutParams?.height ?: -1,
viewSize = view.height,
paddingSize = if (subtractPadding) view.paddingTop + view.paddingBottom else 0
)
private fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension.Pixels? { private fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension.Pixels? {
if (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) { if (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) {
return null return null
@@ -60,24 +95,11 @@ class CoverSizeResolver(
return null return null
} }
private inner class LayoutListener( private fun ViewTreeObserver.removePreDrawListenerSafe(victim: OnPreDrawListener) {
private val continuation: CancellableContinuation<Size>, if (isAlive) {
) : OnLayoutChangeListener { removeOnPreDrawListener(victim)
} else {
override fun onLayoutChange( view.viewTreeObserver.removeOnPreDrawListener(victim)
v: View,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
) {
val size = getSize() ?: return
v.removeOnLayoutChangeListener(this)
continuation.resume(size)
} }
} }
} }

View File

@@ -446,7 +446,7 @@ class DetailsActivity :
val manga = details.toManga() val manga = details.toManga()
with(viewBinding) { with(viewBinding) {
textViewTitle.text = manga.title textViewTitle.text = manga.title
textViewSubtitle.textAndVisible = manga.altTitle textViewSubtitle.textAndVisible = manga.altTitles.joinToString("\n")
textViewNsfw.isVisible = manga.isNsfw textViewNsfw.isVisible = manga.isNsfw
textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) } textViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }
} }
@@ -561,6 +561,7 @@ class DetailsActivity :
} }
private fun loadCover(imageUrl: String?) { private fun loadCover(imageUrl: String?) {
viewBinding.imageViewCover.isEnabled = !imageUrl.isNullOrEmpty()
val lastResult = CoilUtils.result(viewBinding.imageViewCover) val lastResult = CoilUtils.result(viewBinding.imageViewCover)
if (lastResult is SuccessResult && lastResult.request.data == imageUrl) { if (lastResult is SuccessResult && lastResult.request.data == imageUrl) {
return return

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.settings.userdata.storage package org.koitharu.kotatsu.settings.userdata.storage
import coil3.ImageLoader
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -35,6 +36,7 @@ class StorageManageSettingsViewModel @Inject constructor(
private val cookieJar: MutableCookieJar, private val cookieJar: MutableCookieJar,
private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase, private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,
private val mangaDataRepositoryProvider: Provider<MangaDataRepository>, private val mangaDataRepositoryProvider: Provider<MangaDataRepository>,
private val coil: ImageLoader,
) : BaseViewModel() { ) : BaseViewModel() {
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
@@ -78,6 +80,9 @@ class StorageManageSettingsViewModel @Inject constructor(
storageManager.clearCache(cache) storageManager.clearCache(cache)
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache) checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
loadStorageUsage() loadStorageUsage()
if (cache == CacheDir.THUMBS || cache == CacheDir.FAVICONS) {
coil.memoryCache?.clear()
}
} finally { } finally {
loadingKeys.update { it - key } loadingKeys.update { it - key }
} }

View File

@@ -1,6 +1,6 @@
[versions] [versions]
acra = "5.12.0" acra = "5.12.0"
activity = "1.10.0" activity = "1.10.1"
adapterdelegates = "4.3.2" adapterdelegates = "4.3.2"
appcompat = "1.7.0" appcompat = "1.7.0"
avifDecoder = "1.1.1.14d8e3c4" avifDecoder = "1.1.1.14d8e3c4"
@@ -9,10 +9,10 @@ coil = "3.1.0"
collections = "1.4.5" collections = "1.4.5"
#noinspection GradleDependency - 2.5.3 cause crashes #noinspection GradleDependency - 2.5.3 cause crashes
conscrypt = "2.5.2" conscrypt = "2.5.2"
constraintlayout = "2.2.0" constraintlayout = "2.2.1"
coreKtx = "1.15.0" coreKtx = "1.15.0"
coroutines = "1.10.1" coroutines = "1.10.1"
desugar = "2.1.4" desugar = "2.1.5"
diskLruCache = "1.4" diskLruCache = "1.4"
fragment = "1.8.6" fragment = "1.8.6"
gradle = "8.8.1" gradle = "8.8.1"
@@ -27,11 +27,11 @@ ksp = "2.1.10-1.0.29"
leakcanary = "3.0-alpha-8" leakcanary = "3.0-alpha-8"
lifecycle = "2.8.7" lifecycle = "2.8.7"
markwon = "4.6.2" markwon = "4.6.2"
material = "1.13.0-alpha10" material = "1.13.0-alpha11"
moshi = "1.15.2" moshi = "1.15.2"
okhttp = "4.12.0" okhttp = "4.12.0"
okio = "3.10.2" okio = "3.10.2"
parsers = "ddb9b13df7" parsers = "843d1f1bea"
preference = "1.2.1" preference = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
room = "2.6.1" room = "2.6.1"