Fix images memory caching

master
Koitharu 2 years ago
parent 5ef1b4ac9c
commit 1a60df6d98
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 636
versionName = '7.0-b2'
versionCode = 637
versionName = '7.0-b3'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {

@ -43,6 +43,7 @@ import org.koitharu.kotatsu.core.util.AcraScreenLogger
import org.koitharu.kotatsu.core.util.ext.connectivityManager
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer
import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.CbzFetcher
import org.koitharu.kotatsu.local.data.LocalStorageChanges
@ -101,8 +102,8 @@ interface AppModule {
return ImageLoader.Builder(context)
.okHttpClient(okHttpClient.newBuilder().cache(null).build())
.interceptorDispatcher(Dispatchers.Default)
.fetcherDispatcher(Dispatchers.IO)
.decoderDispatcher(Dispatchers.Default)
.fetcherDispatcher(Dispatchers.Default)
.decoderDispatcher(Dispatchers.IO)
.transformationDispatcher(Dispatchers.Default)
.diskCache(diskCacheFactory)
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
@ -113,6 +114,7 @@ interface AppModule {
.add(SvgDecoder.Factory())
.add(CbzFetcher.Factory())
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
.add(MangaPageKeyer())
.add(pageFetcherFactory)
.add(imageProxyInterceptor)
.add(coverRestoreInterceptor)

@ -7,23 +7,23 @@ import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.graphics.ColorUtils
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.animation.ArgbEvaluatorCompat
import org.koitharu.kotatsu.core.util.ext.animatorDurationScale
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import kotlin.math.abs
import com.google.android.material.R as materialR
class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, TimeAnimator.TimeListener {
private val colorLow = context.getThemeColor(materialR.attr.colorBackgroundFloating)
private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainer)
private val colorLow = context.getThemeColor(materialR.attr.colorSurfaceContainerLowest)
private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainerHighest)
private var currentColor: Int = colorLow
private var alpha: Int = 255
private val interpolator = FastOutSlowInInterpolator()
private val period = 2000 * context.animatorDurationScale
private val period = context.getAnimationDuration(R.integer.config_longAnimTime) * 2
private val timeAnimator = TimeAnimator()
init {
@ -40,23 +40,21 @@ class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, Ti
}
override fun setAlpha(alpha: Int) {
this.alpha = alpha
// this.alpha = alpha FIXME coil's crossfade
}
override fun setColorFilter(colorFilter: ColorFilter?) {
throw UnsupportedOperationException("ColorFilter is not supported by PlaceholderDrawable")
}
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
@Deprecated("Deprecated in Java")
override fun getOpacity(): Int = PixelFormat.OPAQUE
override fun getOpacity(): Int = if (alpha == 255) PixelFormat.OPAQUE else PixelFormat.TRANSLUCENT
override fun getAlpha(): Int = alpha
override fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) {
if (callback != null) {
callback?.also {
updateColor()
invalidateSelf()
}
it.invalidateDrawable(this)
} ?: stop()
}
override fun start() {
@ -64,7 +62,7 @@ class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, Ti
}
override fun stop() {
timeAnimator.cancel()
timeAnimator.end()
}
override fun isRunning(): Boolean = timeAnimator.isStarted

@ -6,7 +6,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import coil.size.Dimension
import coil.size.Size
import coil.size.SizeResolver
import coil.size.ViewSizeResolver
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
@ -16,24 +16,24 @@ private const val ASPECT_RATIO_HEIGHT = 18f
private const val ASPECT_RATIO_WIDTH = 13f
class CoverSizeResolver(
private val imageView: ImageView,
) : SizeResolver {
override val view: ImageView,
) : ViewSizeResolver<ImageView> {
override suspend fun size(): Size {
getSize()?.let { return it }
return suspendCancellableCoroutine { cont ->
val layoutListener = LayoutListener(cont)
imageView.addOnLayoutChangeListener(layoutListener)
view.addOnLayoutChangeListener(layoutListener)
cont.invokeOnCancellation {
imageView.removeOnLayoutChangeListener(layoutListener)
view.removeOnLayoutChangeListener(layoutListener)
}
}
}
private fun getSize(): Size? {
val lp = imageView.layoutParams
var width = getDimension(lp.width, imageView.width, imageView.paddingLeft + imageView.paddingRight)
var height = getDimension(lp.height, imageView.height, imageView.paddingTop + imageView.paddingBottom)
val lp = view.layoutParams
var width = getDimension(lp.width, view.width, view.paddingLeft + view.paddingRight)
var height = getDimension(lp.height, view.height, view.paddingTop + view.paddingBottom)
if (width == null && height == null) {
return null
}

@ -0,0 +1,21 @@
package org.koitharu.kotatsu.core.ui.util
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
class BottomSheetNoHalfExpandedCallback() : BottomSheetBehavior.BottomSheetCallback() {
private var previousStableState = BottomSheetBehavior.STATE_COLLAPSED
override fun onStateChanged(sheet: View, state: Int) {
if (state == BottomSheetBehavior.STATE_HALF_EXPANDED) {
val behavior = (sheet.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior as? BottomSheetBehavior<*>
behavior?.state = previousStableState
} else if (state == BottomSheetBehavior.STATE_EXPANDED || state == BottomSheetBehavior.STATE_COLLAPSED) {
previousStableState = state
}
}
override fun onSlide(sheet: View, offset: Float) = Unit
}

@ -54,6 +54,7 @@ import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.BottomSheetClollapseCallback
import org.koitharu.kotatsu.core.ui.util.BottomSheetNoHalfExpandedCallback
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
@ -155,6 +156,7 @@ class DetailsActivity :
viewBinding.chipsTags.onChipClickListener = this
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
viewBinding.containerBottomSheet?.let { BottomSheetBehavior.from(it) }?.let { behavior ->
behavior.addBottomSheetCallback(BottomSheetNoHalfExpandedCallback())
onBackPressedDispatcher.addCallback(BottomSheetClollapseCallback(behavior))
}

@ -10,6 +10,7 @@ import coil.decode.ImageSource
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.network.HttpException
import coil.request.Options
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
@ -44,15 +45,17 @@ class MangaPageFetcher(
override suspend fun fetch(): FetchResult {
val repo = mangaRepositoryFactory.create(page.source)
val pageUrl = repo.getPageUrl(page)
pagesCache.get(pageUrl)?.let { file ->
return SourceResult(
source = ImageSource(
file = file.toOkioPath(),
metadata = MangaPageMetadata(page),
),
mimeType = null,
dataSource = DataSource.DISK,
)
if (options.diskCachePolicy.readEnabled) {
pagesCache.get(pageUrl)?.let { file ->
return SourceResult(
source = ImageSource(
file = file.toOkioPath(),
metadata = MangaPageMetadata(page),
),
mimeType = null,
dataSource = DataSource.DISK,
)
}
}
return loadPage(pageUrl)
}
@ -91,8 +94,8 @@ class MangaPageFetcher(
else -> {
val request = PageLoader.createPageRequest(page, pageUrl)
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
check(response.isSuccessful) {
"Invalid response: ${response.code} ${response.message} at $pageUrl"
if (!response.isSuccessful) {
throw HttpException(response)
}
val body = checkNotNull(response.body) {
"Null response"
@ -122,17 +125,15 @@ class MangaPageFetcher(
private val imageProxyInterceptor: ImageProxyInterceptor,
) : Fetcher.Factory<MangaPage> {
override fun create(data: MangaPage, options: Options, imageLoader: ImageLoader): Fetcher {
return MangaPageFetcher(
okHttpClient = okHttpClient,
pagesCache = pagesCache,
options = options,
page = data,
context = context,
mangaRepositoryFactory = mangaRepositoryFactory,
imageProxyInterceptor = imageProxyInterceptor,
)
}
override fun create(data: MangaPage, options: Options, imageLoader: ImageLoader) = MangaPageFetcher(
okHttpClient = okHttpClient,
pagesCache = pagesCache,
options = options,
page = data,
context = context,
mangaRepositoryFactory = mangaRepositoryFactory,
imageProxyInterceptor = imageProxyInterceptor,
)
}
class MangaPageMetadata(val page: MangaPage) : ImageSource.Metadata()

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.details.ui.pager.pages
import coil.key.Keyer
import coil.request.Options
import org.koitharu.kotatsu.parsers.model.MangaPage
class MangaPageKeyer : Keyer<MangaPage> {
override fun key(data: MangaPage, options: Options) = data.url
}

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="config_longAnimTime">1000</integer>
<integer name="config_defaultAnimTime">300</integer>
<integer name="config_shorterAnimTime">150</integer>
<integer name="config_tinyAnimTime">50</integer>

Loading…
Cancel
Save