From f55d6ce5e28171766444eea8d7301b86decf27ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Mets=C3=A4nheimo?= Date: Wed, 9 Oct 2024 12:14:17 +0300 Subject: [PATCH 1/3] feat: ignoreQueryParamsForCacheKey image option (iOS & Android) Ignores URL query parameters for cache keys when the new option is set to true, defaults to false. This is needed for example when downloading images using S3 presigned URL's that include a signature that changes between requests preventing the images from being found in the cache. Also fixes the merge error in FasterImageViewManager.kt which came from commit 86a23fd2c9da28147fc3bcb3cf8be78f57a5d327 --- .../fasterimage/FasterImageViewManager.kt | 41 +++++++++++-------- ios/FasterImageViewManager.swift | 12 +++++- src/index.tsx | 2 + 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt b/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt index e28c44f..baf6707 100644 --- a/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt +++ b/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt @@ -10,6 +10,7 @@ import android.graphics.RectF import android.graphics.Path import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.net.Uri import android.os.Build import android.util.Base64 import android.view.View @@ -18,6 +19,7 @@ import android.widget.ImageView.ScaleType import androidx.appcompat.widget.AppCompatImageView import coil.annotation.ExperimentalCoilApi import coil.imageLoader +import coil.memory.MemoryCache import coil.request.CachePolicy import coil.request.ImageRequest import coil.size.Scale @@ -93,9 +95,10 @@ import com.facebook.react.uimanager.events.RCTEventEmitter val failureImage = options.getString("failureImage") val grayscale = if (options.hasKey("grayscale")) options.getDouble("grayscale") else 0.0 val allowHardware = if (options.hasKey("allowHardware")) options.getBoolean("allowHardware") else true - val headers = options.getMap("headers") + val headers = options.getMap("headers") + val ignoreQueryParamsForCacheKey = if (options.hasKey("ignoreQueryParamsForCacheKey")) options.getBoolean("ignoreQueryParamsForCacheKey") else false - val borderRadii = BorderRadii( + val borderRadii = BorderRadii( uniform = if (options.hasKey("borderRadius")) options.getDouble("borderRadius") else 0.0, topLeft = if (options.hasKey("borderTopLeftRadius")) options.getDouble("borderTopLeftRadius") else 0.0, topRight = if (options.hasKey("borderTopRightRadius")) options.getDouble("borderTopRightRadius") else 0.0, @@ -127,6 +130,20 @@ import com.facebook.react.uimanager.events.RCTEventEmitter ) } else { requestBuilder = requestBuilder.data(it) + if (ignoreQueryParamsForCacheKey) { + val uri = Uri.parse(it) + val keyUri = uri.buildUpon().clearQuery().build() + val cacheKey = keyUri.toString() + val memoryCacheKey = MemoryCache.Key(cacheKey) + if (cachePolicy.equals("memory")) { + requestBuilder = requestBuilder + .memoryCacheKey(memoryCacheKey) + } else { + requestBuilder = requestBuilder + .memoryCacheKey(memoryCacheKey) + .diskCacheKey(cacheKey) + } + } headers?.let { for (entry in it.entryIterator) { requestBuilder.setHeader(entry.key, entry.value as String) @@ -232,20 +249,6 @@ import com.facebook.react.uimanager.events.RCTEventEmitter private fun makeThumbHash(view: AppCompatImageView, hash: String): Drawable { val thumbHash = ThumbHash.thumbHashToRGBA(Base64.decode(hash, Base64.DEFAULT)) - val bitmap = Bitmap.createBitmap(thumbHash.width, thumbHash.height, Bitmap.Config.ARGB_8888) - bitmap.setPixels(toIntArray(thumbHash.rgba), 0, thumbHash.width, 0, 0, thumbHash.width, thumbHash.height) - return BitmapDrawable(view.context.resources, bitmap) - } - - private fun makeBlurHash(view: AppCompatImageView, hash: String): Drawable { - val bitmap = BlurHashDecoder.decode(hash, 8, 8) - return BitmapDrawable(view.context.resources, bitmap) - } - - private fun toIntArray(byteArray: ByteArray): IntArray { - val intArray = IntArray(byteArray.size) - for (i in byteArray.indices) { - intArray[i] = byteArray[i].toInt() and 0xFF val intArray = IntArray(thumbHash.width * thumbHash.height) for (i in intArray.indices) { val r = thumbHash.rgba[i * 4].toInt() and 0xFF @@ -259,6 +262,11 @@ import com.facebook.react.uimanager.events.RCTEventEmitter return BitmapDrawable(view.context.resources, bitmap) } + private fun makeBlurHash(view: AppCompatImageView, hash: String): Drawable { + val bitmap = BlurHashDecoder.decode(hash, 8, 8) + return BitmapDrawable(view.context.resources, bitmap) + } + companion object { private val RESIZE_MODE = mapOf( "contain" to ScaleType.FIT_CENTER, @@ -276,6 +284,7 @@ import com.facebook.react.uimanager.events.RCTEventEmitter } } + object ThumbHash { /** * Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. diff --git a/ios/FasterImageViewManager.swift b/ios/FasterImageViewManager.swift index a8dee30..b2d4956 100644 --- a/ios/FasterImageViewManager.swift +++ b/ios/FasterImageViewManager.swift @@ -42,6 +42,7 @@ struct ImageOptions: Decodable { let url: String let headers: [String: String]? let grayscale: Double? + let ignoreQueryParamsForCacheKey: Bool? } struct BorderRadii { @@ -134,12 +135,19 @@ final class FasterImageView: UIView { } progressiveLoadingEnabled = options.progressiveLoadingEnabled ?? false grayscale = options.grayscale ?? 0.0 + ignoreQueryParamsForCacheKey = options.ignoreQueryParamsForCacheKey ?? false if let url = URL(string: options.url) { var urlRequestFromOptions = URLRequest(url: url) urlRequestFromOptions.allHTTPHeaderFields = options.headers - urlRequest = urlRequestFromOptions + + if ignoreQueryParamsForCacheKey { + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) + components?.query = nil + var url = components?.url?.absoluteString ?? url.absoluteString + lazyImageView.request?.userInfo[.imageIdKey] = url + } } else { onError?([ "error": "Expected a valid url but got: \(options.url)", @@ -270,6 +278,8 @@ final class FasterImageView: UIView { } } + var ignoreQueryParamsForCacheKey = false + // MARK: - Optional Properties var base64Placeholder: String? { diff --git a/src/index.tsx b/src/index.tsx index bb203a8..6c986d9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -47,6 +47,7 @@ export type AndroidImageResizeMode = * @property {number} [borderBottomRightRadius] - Bottom right border radius of the image * @property {number} [grayscale] - Grayscale value of the image, 0-1 * @property {boolean} [allowHardware] - Allow hardware rendering, defaults to true (Android only) + * @property {boolean} [ignoreQueryParamsForCacheKey] - Ignore query params for cache key, defaults to false */ export type ImageOptions = { blurhash?: string; @@ -70,6 +71,7 @@ export type ImageOptions = { headers?: Record; grayscale?: number; allowHardware?: boolean; + ignoreQueryParamsForCacheKey?: boolean; }; /** From 242e3953f35be5b24adfe73e468c2b1c6bf866c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Mets=C3=A4nheimo?= Date: Wed, 9 Oct 2024 12:32:40 +0300 Subject: [PATCH 2/3] added the new option to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6dbaac0..38e8c5c 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ await clearCache(); | onError | function | | The function to call when an error occurs. The error is passed as the first argument of the function | | onSuccess | function | | The function to call when the image is successfully loaded | | grayscale | number | 0 | Filter or transformation that converts the image into shades of gray (0-1). | +| ignoreQueryParamsForCacheKey | boolean | false | Ignore URL query parameters in cache keys | | allowHardware | boolean | true | Allow hardware rendering (Android only) | | headers | Record | undefined | Pass in headers | | accessibilityLabel | string | undefined | accessibility label | From cf6ce0cff83e7d8edcc9c0a1e54f22a88e45ac24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Mets=C3=A4nheimo?= Date: Wed, 9 Oct 2024 12:54:15 +0300 Subject: [PATCH 3/3] remove redundant whitespace canges --- .../com/candlefinance/fasterimage/FasterImageViewManager.kt | 3 +-- ios/FasterImageViewManager.swift | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt b/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt index baf6707..fc32aab 100644 --- a/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt +++ b/android/src/main/java/com/candlefinance/fasterimage/FasterImageViewManager.kt @@ -98,7 +98,7 @@ import com.facebook.react.uimanager.events.RCTEventEmitter val headers = options.getMap("headers") val ignoreQueryParamsForCacheKey = if (options.hasKey("ignoreQueryParamsForCacheKey")) options.getBoolean("ignoreQueryParamsForCacheKey") else false - val borderRadii = BorderRadii( + val borderRadii = BorderRadii( uniform = if (options.hasKey("borderRadius")) options.getDouble("borderRadius") else 0.0, topLeft = if (options.hasKey("borderTopLeftRadius")) options.getDouble("borderTopLeftRadius") else 0.0, topRight = if (options.hasKey("borderTopRightRadius")) options.getDouble("borderTopRightRadius") else 0.0, @@ -284,7 +284,6 @@ import com.facebook.react.uimanager.events.RCTEventEmitter } } - object ThumbHash { /** * Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. diff --git a/ios/FasterImageViewManager.swift b/ios/FasterImageViewManager.swift index b2d4956..85f7d52 100644 --- a/ios/FasterImageViewManager.swift +++ b/ios/FasterImageViewManager.swift @@ -140,6 +140,7 @@ final class FasterImageView: UIView { if let url = URL(string: options.url) { var urlRequestFromOptions = URLRequest(url: url) urlRequestFromOptions.allHTTPHeaderFields = options.headers + urlRequest = urlRequestFromOptions if ignoreQueryParamsForCacheKey {