diff --git a/Sources/SkipUI/SkipUI/Components/AsyncImage.swift b/Sources/SkipUI/SkipUI/Components/AsyncImage.swift index 1dcc7a0e..9aeb05c0 100644 --- a/Sources/SkipUI/SkipUI/Components/AsyncImage.swift +++ b/Sources/SkipUI/SkipUI/Components/AsyncImage.swift @@ -4,11 +4,11 @@ import Foundation #if SKIP import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import android.webkit.MimeTypeMap import coil3.compose.SubcomposeAsyncImage import coil3.request.ImageRequest -import coil3.size.Size import coil3.fetch.Fetcher import coil3.fetch.FetchResult import coil3.ImageLoader @@ -114,16 +114,27 @@ public struct AsyncImage : View, Renderable { // we add a custom fetchers that will handle loading the URL. // Otherwise use Coil's default URL string handling let requestSource: Any = AssetURLFetcher.handlesURL(url) ? url : urlString - let model = ImageRequest.Builder(LocalContext.current) - .fetcherFactory(AssetURLFetcher.Factory()) // handler for asset:/ and jar:file:/ URLs - .decoderFactory(coil3.svg.SvgDecoder.Factory()) - //.decoderFactory(coil3.gif.GifDecoder.Factory()) - .decoderFactory(PdfDecoder.Factory()) - .data(requestSource) - .size(Size.ORIGINAL) - .memoryCacheKey(urlString) - .diskCacheKey(urlString) - .build() + + let androidContext = LocalContext.current + let dm = androidContext.resources.displayMetrics + let maxPx = max(Int(dm.widthPixels), Int(dm.heightPixels)) + let cacheKey = "\(urlString)#\(maxPx)x\(maxPx)" + let model = remember(urlString, maxPx) { + // Coil refuses to use its memory cache for .size(Size.ORIGINAL) requests! + // We're using maxPx as an arbitrary bound to force it to cache properly + // Coil memory-cache size validation is in MemoryCacheService.isCacheValueValidForSize: + // See compose-source/io-coil-kt-coil3/coil-core-android/commonMain/coil3/memory/MemoryCacheService.kt:127. + return ImageRequest.Builder(androidContext) + .fetcherFactory(AssetURLFetcher.Factory()) // handler for asset:/ and jar:file:/ URLs + .decoderFactory(coil3.svg.SvgDecoder.Factory()) + //.decoderFactory(coil3.gif.GifDecoder.Factory()) + .decoderFactory(PdfDecoder.Factory()) + .data(requestSource) + .size(coil3.size.Size(width: maxPx, height: maxPx)) + .memoryCacheKey(cacheKey) + .diskCacheKey(cacheKey) + .build() + } SubcomposeAsyncImage(model: model, contentDescription: nil, loading: { _ in let placeholderView = content(AsyncImagePhase.empty) diff --git a/Sources/SkipUI/SkipUI/Components/Image.swift b/Sources/SkipUI/SkipUI/Components/Image.swift index b8c96230..d31c7960 100644 --- a/Sources/SkipUI/SkipUI/Components/Image.swift +++ b/Sources/SkipUI/SkipUI/Components/Image.swift @@ -17,6 +17,7 @@ import androidx.compose.material.icons.twotone.__ import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.paint import androidx.compose.ui.geometry.Rect @@ -142,16 +143,26 @@ public struct Image : View, Renderable, Equatable { @Composable private func RenderAssetImage(asset: AssetImageInfo, label: Text?, aspectRatio: Double?, contentMode: ContentMode?, context: ComposeContext) { let url = asset.url - let model = ImageRequest.Builder(LocalContext.current) - .fetcherFactory(AssetURLFetcher.Factory()) // handler for asset:/ and jar:file:/ URLs - .decoderFactory(coil3.svg.SvgDecoder.Factory()) - //.decoderFactory(coil3.gif.GifDecoder.Factory()) - .decoderFactory(PdfDecoder.Factory()) - .data(url) - .size(coil3.size.Size.ORIGINAL) - .memoryCacheKey(url.description) - .diskCacheKey(url.description) - .build() + let androidContext = LocalContext.current + let dm = androidContext.resources.displayMetrics + let maxPx = max(Int(dm.widthPixels), Int(dm.heightPixels)) + let cacheKey = "\(url.description)#\(maxPx)x\(maxPx)" + let model = remember(asset.url, maxPx) { + // Coil refuses to use its memory cache for .size(Size.ORIGINAL) requests! + // We're using maxPx as an arbitrary bound to force it to cache properly + // Coil memory-cache size validation is in MemoryCacheService.isCacheValueValidForSize: + // See compose-source/io-coil-kt-coil3/coil-core-android/commonMain/coil3/memory/MemoryCacheService.kt:127. + return ImageRequest.Builder(androidContext) + .fetcherFactory(AssetURLFetcher.Factory()) // handler for asset:/ and jar:file:/ URLs + .decoderFactory(coil3.svg.SvgDecoder.Factory()) + //.decoderFactory(coil3.gif.GifDecoder.Factory()) + .decoderFactory(PdfDecoder.Factory()) + .data(asset.url) + .size(coil3.size.Size(width: maxPx, height: maxPx)) + .memoryCacheKey(cacheKey) + .diskCacheKey(cacheKey) + .build() + } let shouldTint = (templateRenderingMode == .template) || (templateRenderingMode == nil && asset.isTemplateImage) let tintColor = shouldTint ? EnvironmentValues.shared._foregroundStyle?.asColor(opacity: 1.0, animationContext: context) ?? Color.primary.colorImpl() : nil @@ -161,6 +172,7 @@ public struct Image : View, Renderable, Equatable { }, success: { state in RenderPainter(painter: self.painter, tintColor: tintColor, scale: scale, aspectRatio: aspectRatio, contentMode: contentMode, context: context) }, error: { state in + }) }