From 2ae2447408b79757bea42080d316757a9d95b0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Tue, 22 Oct 2024 22:59:23 +0200 Subject: [PATCH 1/6] feat(image): initializing cache property in image source class android --- .../Libraries/Image/Image.android.js | 2 + .../react-native/Libraries/Image/Image.ios.js | 2 + .../react/views/image/ImageCacheControl.kt | 13 + .../react/views/image/ReactImageManager.kt | 3 + .../react/views/image/ReactImageView.kt | 14 +- .../react/views/imagehelper/ImageSource.kt | 26 +- .../js/examples/Image/ImageExample.js | 1574 ++++++++--------- 7 files changed, 844 insertions(+), 790 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageCacheControl.kt diff --git a/packages/react-native/Libraries/Image/Image.android.js b/packages/react-native/Libraries/Image/Image.android.js index 998270b1cfc3be..70e4783dca44e8 100644 --- a/packages/react-native/Libraries/Image/Image.android.js +++ b/packages/react-native/Libraries/Image/Image.android.js @@ -150,6 +150,8 @@ let BaseImage: AbstractImageAndroid = React.forwardRef( ); } + console.log({source: props.source}); + let style: ImageStyleProp; let sources; if (Array.isArray(source)) { diff --git a/packages/react-native/Libraries/Image/Image.ios.js b/packages/react-native/Libraries/Image/Image.ios.js index b3b9e61e50b9ba..8c73136c1414ca 100644 --- a/packages/react-native/Libraries/Image/Image.ios.js +++ b/packages/react-native/Libraries/Image/Image.ios.js @@ -149,6 +149,8 @@ let BaseImage: AbstractImageIOS = React.forwardRef((props, forwardedRef) => { ...restProps } = props; + console.log({ cache: props.source }); + const _accessibilityState = { busy: ariaBusy ?? props.accessibilityState?.busy, checked: ariaChecked ?? props.accessibilityState?.checked, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageCacheControl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageCacheControl.kt new file mode 100644 index 00000000000000..8d720466333594 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageCacheControl.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.image + +public enum class ImageCacheControl { + DEFAULT, + RELOAD, +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt index 77e1d986655597..94261eb3bb0816 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt @@ -101,11 +101,14 @@ public constructor( // In JS this is Image.props.source @ReactProp(name = "src") public fun setSrc(view: ReactImageView, sources: ReadableArray?) { + FLog.w(ReactConstants.TAG, "setSrc called with $sources") setSource(view, sources) } @ReactProp(name = "source") public fun setSource(view: ReactImageView, sources: ReadableArray?) { + // log the params + FLog.w(ReactConstants.TAG, "setSource called with $sources") view.setSource(sources) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index 6a2a6c922ed0aa..cd7b5e5ed02283 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -11,6 +11,8 @@ package com.facebook.react.views.image import android.content.Context import android.graphics.Bitmap +import com.facebook.common.logging.FLog +import com.facebook.react.common.ReactConstants import android.graphics.BitmapShader import android.graphics.Canvas import android.graphics.Color @@ -103,6 +105,7 @@ public class ReactImageView( private var headers: ReadableMap? = null private var resizeMultiplier = 1.0f private var resizeMethod = ImageResizeMethod.AUTO + private var cacheControl = ImageCacheControl.DEFAULT init { // Workaround Android bug where ImageView visibility is not propagated to the Drawable, so you @@ -268,7 +271,13 @@ public class ReactImageView( } else if (sources.size() == 1) { // Optimize for the case where we have just one uri, case in which we don't need the sizes val source = sources.getMap(0) - var imageSource = ImageSource(context, source.getString("uri")) + var imageSource = + ImageSource( + context, + source.getString("uri"), + 0.0, + 0.0, + source.getString("cache")) if (Uri.EMPTY == imageSource.uri) { warnImageSource(source.getString("uri")) imageSource = getTransparentBitmapImageSource(context) @@ -282,7 +291,8 @@ public class ReactImageView( context, source.getString("uri"), source.getDouble("width"), - source.getDouble("height")) + source.getDouble("height"), + source.getString("cache")) if (Uri.EMPTY == imageSource.uri) { warnImageSource(source.getString("uri")) imageSource = getTransparentBitmapImageSource(context) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt index 947044cf43e0ac..4d772ce9aaebe0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt @@ -9,7 +9,11 @@ package com.facebook.react.views.imagehelper import android.content.Context import android.net.Uri +import com.facebook.common.logging.FLog +import com.facebook.react.common.ReactConstants +import com.facebook.react.views.image.ImageCacheControl import java.util.Objects +import android.util.Log /** Class describing an image source (network URI or resource) and size. */ public open class ImageSource @@ -19,9 +23,15 @@ constructor( /** Get the source of this image, as it was passed to the constructor. */ public val source: String?, width: Double = 0.0, - height: Double = 0.0 + height: Double = 0.0, + cacheControl: String? = null ) { + init { + // Log cache control to ensure it's set correctly + Log.d("ImageSource", "CacheControl set to: $cacheControl") + } + /** Get the URI for this image - can be either a parsed network URI or a resource URI. */ public open val uri: Uri = computeUri(context) /** Get the area of this image. */ @@ -30,6 +40,8 @@ constructor( public open val isResource: Boolean get() = _isResource + public val cacheControl: ImageCacheControl = computeCacheControl(cacheControl) + private var _isResource: Boolean = false override fun equals(other: Any?): Boolean { @@ -64,6 +76,18 @@ constructor( return ResourceDrawableIdHelper.instance.getResourceDrawableUri(context, source) } + private fun computeCacheControl(cacheControl: String?): ImageCacheControl { + return when (cacheControl) { + null, + "default" -> ImageCacheControl.DEFAULT + "reload" -> ImageCacheControl.RELOAD + else -> { + FLog.w(ReactConstants.TAG, "Invalid resize method $cacheControl") + return ImageCacheControl.DEFAULT + } + } + } + public companion object { private const val TRANSPARENT_BITMAP_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 1a4077b8b73fe0..4d515e945f7f0b 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -873,141 +873,141 @@ exports.description = 'Base component for displaying different types of images.'; exports.examples = [ - { - title: 'Plain Network Image with `source` prop.', - description: ('If the `source` prop `uri` property is prefixed with ' + - '"http", then it will be downloaded from the network.': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'Plain Network Image with `src` prop.', - description: ('If the `src` prop is defined with ' + - '"http", then it will be downloaded from the network.': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'Multiple Image Source using the `srcSet` prop.', - description: - ('A list of comma seperated uris along with scale are provided in `srcSet`.' + - 'An appropriate value will be used based on the scale of the device.': string), - render: function (): React.Node { - return ( - - ); - }, - }, - { - title: 'Plain Blob Image', - description: ('If the `source` prop `uri` property is an object URL, ' + - 'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'Plain Static Image', - description: - ('Static assets should be placed in the source code tree, and ' + - 'required in the same way as JavaScript modules.': string), - render: function (): React.Node { - return ( - - - - - - - ); - }, - }, - { - title: 'Image Loading Events', - render: function (): React.Node { - return ( - - ); - }, - }, - { - title: 'Error Handler', - render: function (): React.Node { - return ( - - ); - }, - }, - { - title: 'Error Handler for Large Images', - render: function (): React.Node { - return ( - - ); - }, - }, - { - title: 'Image Download Progress', - render: function (): React.Node { - return ( - - ); - }, - }, - { - title: 'defaultSource', - description: 'Show a placeholder image when a network image is loading', - render: function (): React.Node { - return ( - - ); - }, - platform: 'ios', - }, + // { + // title: 'Plain Network Image with `source` prop.', + // description: ('If the `source` prop `uri` property is prefixed with ' + + // '"http", then it will be downloaded from the network.': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Plain Network Image with `src` prop.', + // description: ('If the `src` prop is defined with ' + + // '"http", then it will be downloaded from the network.': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Multiple Image Source using the `srcSet` prop.', + // description: + // ('A list of comma seperated uris along with scale are provided in `srcSet`.' + + // 'An appropriate value will be used based on the scale of the device.': string), + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // title: 'Plain Blob Image', + // description: ('If the `source` prop `uri` property is an object URL, ' + + // 'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Plain Static Image', + // description: + // ('Static assets should be placed in the source code tree, and ' + + // 'required in the same way as JavaScript modules.': string), + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Image Loading Events', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // title: 'Error Handler', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // title: 'Error Handler for Large Images', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // title: 'Image Download Progress', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // title: 'defaultSource', + // description: 'Show a placeholder image when a network image is loading', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // platform: 'ios', + // }, { title: 'Cache Policy', description: @@ -1036,656 +1036,656 @@ exports.examples = [ ); }, - platform: 'ios', - }, - { - title: 'Borders', - name: 'borders', - render: function (): React.Node { - return ( - - - - ); - }, - }, - { - title: 'Border Radius', - name: 'border-radius', - render: function (): React.Node { - return ( - - - - - - - - - - ); - }, - }, - { - title: 'Background Color', - name: 'background-color', - render: function (): React.Node { - return ( - - - - - - - ); - }, - }, - { - title: 'Box Shadow', - name: 'box-shadow', - render: function (): React.Node { - return ( - - - - - - ); - }, - }, - { - title: 'Opacity', - render: function (): React.Node { - return ( - - - - - - - - - ); - }, - }, - { - title: 'Nesting content inside component', - render: function (): React.Node { - return ( - - - React - - ); - }, - }, - { - title: 'Nesting content inside component', - render: function (): React.Node { - return ( - - React - - ); - }, - }, - { - title: 'Tint Color', - description: ('The `tintColor` prop changes all the non-alpha ' + - 'pixels to the tint color.': string), - render: function (): React.Node { - return ( - - - - - - - - - It also works using the `tintColor` style prop - - - - - - - - - The `tintColor` prop has precedence over the `tintColor` style prop - - - - - - - - - It also works with downloaded images: - - - - - - - - - ); - }, - }, - { - title: 'Object Fit', - description: ('The `objectFit` style prop controls how the image is ' + - 'rendered within the frame.': string), - render: function (): React.Node { - return ( - - {[smallImage, fullImage].map((image, index) => { - return ( - - - - - Contain - - - - - - Cover - - - - - - - - Fill - - - - - - Scale Down - - - - - - ); - })} - - ); - }, - }, - { - title: 'Resize Mode', - description: ('The `resizeMode` style prop controls how the image is ' + - 'rendered within the frame.': string), - render: function (): React.Node { - return ( - - {[smallImage, fullImage].map((image, index) => { - return ( - - - - - Contain - - - - - - Cover - - - - - - - - Stretch - - - - - - Repeat - - - - - - Center - - - - - - ); - })} - - ); - }, - }, - { - title: 'Animated GIF', - render: function (): React.Node { - return ( - - ); - }, - platform: 'ios', - }, - { - title: 'Base64 image', - render: function (): React.Node { - return ( - - ); - }, - platform: 'ios', - }, - { - title: 'Cap Insets', - description: - ('When the image is resized, the corners of the size specified ' + - 'by capInsets will stay a fixed size, but the center content and ' + - 'borders of the image will be stretched. This is useful for creating ' + - 'resizable rounded buttons, shadows, and other resizable assets.': string), - render: function (): React.Node { - return ; - }, - platform: 'ios', - }, - { - title: 'Image Size', - render: function (): React.Node { - return ; - }, - }, - { - title: 'MultipleSourcesExample', - description: - ('The `source` prop allows passing in an array of uris, so that native to choose which image ' + - 'to diplay based on the size of the of the target image': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'Legacy local image', - description: ('Images shipped with the native bundle, but not managed ' + - 'by the JS packager': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'Bundled images', - description: 'Images shipped in a separate native bundle', - render: function (): React.Node { - return ( - - - - - ); - }, - platform: 'ios', - }, - { - title: 'Blur Radius', - render: function (): React.Node { - return ( - - - - - - - - - ); - }, - }, - { - title: 'Accessibility', - description: - ('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'Accessibility Label', - description: - ('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string), - render: function (): React.Node { - return ( - - ); - }, - }, - { - title: 'Accessibility Label via alt prop', - description: - 'Using the alt prop markes an element as being accessibile, and passes the alt text to accessibilityLabel', - render: function (): React.Node { - return ( - Picture of people standing around a table - ); - }, - }, - { - title: 'Fade Duration', - description: - ('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string), - render: function (): React.Node { - return ; - }, - platform: 'android', - }, - { - title: 'Loading Indicator Source', - description: - ('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'On Layout', - description: - ('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string), - render: function (): React.Node { - return ; - }, - }, - { - title: 'On Partial Load', - description: - ('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string), - render: function (): React.Node { - return ; - }, - platform: 'ios', - }, - { - title: 'Vector Drawable', - description: - 'Demonstrating an example of loading a vector drawable asset by name', - render: function (): React.Node { - return ; - }, - platform: 'android', - }, - { - title: 'Large image with different resize methods', - name: 'resize-method', - description: - 'Demonstrating the effects of loading a large image with different resize methods', - scrollable: true, - render: function (): React.Node { - const methods: Array = [ - 'auto', - 'resize', - 'scale', - 'none', - ]; - // Four copies of the same image so we don't serve cached copies of the same image - const images = [ - require('../../assets/large-image-1.png'), - require('../../assets/large-image-2.png'), - require('../../assets/large-image-3.png'), - require('../../assets/large-image-4.png'), - ]; - return ( - - {methods.map((method, index) => ( - - {method} - - - ))} - - ); - }, - platform: 'android', - }, + // platform: 'ios', + }, + // { + // title: 'Borders', + // name: 'borders', + // render: function (): React.Node { + // return ( + // + // + // + // ); + // }, + // }, + // { + // title: 'Border Radius', + // name: 'border-radius', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Background Color', + // name: 'background-color', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Box Shadow', + // name: 'box-shadow', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Opacity', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Nesting content inside component', + // render: function (): React.Node { + // return ( + // + // + // React + // + // ); + // }, + // }, + // { + // title: 'Nesting content inside component', + // render: function (): React.Node { + // return ( + // + // React + // + // ); + // }, + // }, + // { + // title: 'Tint Color', + // description: ('The `tintColor` prop changes all the non-alpha ' + + // 'pixels to the tint color.': string), + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // It also works using the `tintColor` style prop + // + // + // + // + // + // + // + // + // The `tintColor` prop has precedence over the `tintColor` style prop + // + // + // + // + // + // + // + // + // It also works with downloaded images: + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Object Fit', + // description: ('The `objectFit` style prop controls how the image is ' + + // 'rendered within the frame.': string), + // render: function (): React.Node { + // return ( + // + // {[smallImage, fullImage].map((image, index) => { + // return ( + // + // + // + // + // Contain + // + // + // + // + // + // Cover + // + // + // + // + // + // + // + // Fill + // + // + // + // + // + // Scale Down + // + // + // + // + // + // ); + // })} + // + // ); + // }, + // }, + // { + // title: 'Resize Mode', + // description: ('The `resizeMode` style prop controls how the image is ' + + // 'rendered within the frame.': string), + // render: function (): React.Node { + // return ( + // + // {[smallImage, fullImage].map((image, index) => { + // return ( + // + // + // + // + // Contain + // + // + // + // + // + // Cover + // + // + // + // + // + // + // + // Stretch + // + // + // + // + // + // Repeat + // + // + // + // + // + // Center + // + // + // + // + // + // ); + // })} + // + // ); + // }, + // }, + // { + // title: 'Animated GIF', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // platform: 'ios', + // }, + // { + // title: 'Base64 image', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // platform: 'ios', + // }, + // { + // title: 'Cap Insets', + // description: + // ('When the image is resized, the corners of the size specified ' + + // 'by capInsets will stay a fixed size, but the center content and ' + + // 'borders of the image will be stretched. This is useful for creating ' + + // 'resizable rounded buttons, shadows, and other resizable assets.': string), + // render: function (): React.Node { + // return ; + // }, + // platform: 'ios', + // }, + // { + // title: 'Image Size', + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'MultipleSourcesExample', + // description: + // ('The `source` prop allows passing in an array of uris, so that native to choose which image ' + + // 'to diplay based on the size of the of the target image': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Legacy local image', + // description: ('Images shipped with the native bundle, but not managed ' + + // 'by the JS packager': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Bundled images', + // description: 'Images shipped in a separate native bundle', + // render: function (): React.Node { + // return ( + // + // + // + // + // ); + // }, + // platform: 'ios', + // }, + // { + // title: 'Blur Radius', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Accessibility', + // description: + // ('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Accessibility Label', + // description: + // ('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string), + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // title: 'Accessibility Label via alt prop', + // description: + // 'Using the alt prop markes an element as being accessibile, and passes the alt text to accessibilityLabel', + // render: function (): React.Node { + // return ( + // Picture of people standing around a table + // ); + // }, + // }, + // { + // title: 'Fade Duration', + // description: + // ('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string), + // render: function (): React.Node { + // return ; + // }, + // platform: 'android', + // }, + // { + // title: 'Loading Indicator Source', + // description: + // ('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'On Layout', + // description: + // ('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string), + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'On Partial Load', + // description: + // ('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string), + // render: function (): React.Node { + // return ; + // }, + // platform: 'ios', + // }, + // { + // title: 'Vector Drawable', + // description: + // 'Demonstrating an example of loading a vector drawable asset by name', + // render: function (): React.Node { + // return ; + // }, + // platform: 'android', + // }, + // { + // title: 'Large image with different resize methods', + // name: 'resize-method', + // description: + // 'Demonstrating the effects of loading a large image with different resize methods', + // scrollable: true, + // render: function (): React.Node { + // const methods: Array = [ + // 'auto', + // 'resize', + // 'scale', + // 'none', + // ]; + // // Four copies of the same image so we don't serve cached copies of the same image + // const images = [ + // require('../../assets/large-image-1.png'), + // require('../../assets/large-image-2.png'), + // require('../../assets/large-image-3.png'), + // require('../../assets/large-image-4.png'), + // ]; + // return ( + // + // {methods.map((method, index) => ( + // + // {method} + // + // + // ))} + // + // ); + // }, + // platform: 'android', + // }, ]; From 23a57e2b5f1fe987a15dfd7c6c01ef6eed18f411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Wed, 23 Oct 2024 18:21:22 +0200 Subject: [PATCH 2/6] feat(image): skipping cache if cache control is reload --- .../react/views/image/ReactImageView.kt | 32 ++++++++++++------- .../js/examples/Image/ImageExample.js | 8 +++++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index cd7b5e5ed02283..c89781e06e8e2e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -105,7 +105,6 @@ public class ReactImageView( private var headers: ReadableMap? = null private var resizeMultiplier = 1.0f private var resizeMethod = ImageResizeMethod.AUTO - private var cacheControl = ImageCacheControl.DEFAULT init { // Workaround Android bug where ImageView visibility is not propagated to the Drawable, so you @@ -458,17 +457,25 @@ public class ReactImageView( callerContext?.let { builder.setCallerContext(it) } - cachedImageSource?.let { cachedSource -> - val cachedImageRequestBuilder = - ImageRequestBuilder.newBuilderWithSource(cachedSource.uri) - .setPostprocessor(postprocessor) - .setResizeOptions(resizeOptions) - .setAutoRotateEnabled(true) - .setProgressiveRenderingEnabled(progressiveRenderingEnabled) - if (resizeMethod == ImageResizeMethod.NONE) { - cachedImageRequestBuilder.setDownsampleOverride(DownsampleMode.NEVER) + val cacheControl = imageSource?.cacheControl + + FLog.w(ReactConstants.TAG, "landing here with $cacheControl") + + if (cacheControl != ImageCacheControl.RELOAD) { + cachedImageSource?.let { cachedSource -> + val cachedImageRequestBuilder = + ImageRequestBuilder.newBuilderWithSource(cachedSource.uri) + .setPostprocessor(postprocessor) + .setResizeOptions(resizeOptions) + .setAutoRotateEnabled(true) + .setProgressiveRenderingEnabled(progressiveRenderingEnabled) + if (resizeMethod == ImageResizeMethod.NONE) { + cachedImageRequestBuilder.setDownsampleOverride(DownsampleMode.NEVER) + } + builder.setLowResImageRequest(cachedImageRequestBuilder.build()) } - builder.setLowResImageRequest(cachedImageRequestBuilder.build()) + } else { + FLog.w(ReactConstants.TAG, "skipping cache for $uri with $cacheControl") } if (downloadListener != null && controllerForTesting != null) { @@ -522,8 +529,11 @@ public class ReactImageView( val multiSource = getBestSourceForSize(width, height, sources) imageSource = multiSource.bestResult cachedImageSource = multiSource.bestResultInCache + + FLog.w(ReactConstants.TAG, "bestResult: ${imageSource?.uri}, bestResultInCache: ${cachedImageSource?.uri}") return } + FLog.w(ReactConstants.TAG, "bestResult sources: ${sources[0].uri}") imageSource = sources[0] } diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 4d515e945f7f0b..13fda8d3b757b3 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -1033,6 +1033,14 @@ exports.examples = [ }} style={styles.base} /> + ); }, From 63b2efeac54a584a2c713580dd45eb0c7a64d7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Wed, 23 Oct 2024 22:32:30 +0200 Subject: [PATCH 3/6] feat(image): evicting from cache if cache control is set to reload --- .../react/views/image/ReactImageView.kt | 15 +++- .../js/examples/Image/ImageExample.js | 81 ++++++++++++++++--- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index c89781e06e8e2e..dd16409a1e2d56 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -52,6 +52,7 @@ import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.build.ReactBuildConfig import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.modules.fresco.ReactNetworkImageRequest +import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.react.uimanager.BackgroundStyleApplicator import com.facebook.react.uimanager.LengthPercentage import com.facebook.react.uimanager.LengthPercentageType @@ -419,6 +420,7 @@ public class ReactImageView( private fun maybeUpdateViewFromRequest(doResize: Boolean) { val uri = this.imageSource?.uri ?: return + val cacheControl = this.imageSource?.cacheControl val postprocessorList = mutableListOf() iterativeBoxBlurPostProcessor?.let { postprocessorList.add(it) } @@ -427,6 +429,17 @@ public class ReactImageView( val resizeOptions = if (doResize) resizeOptions else null + if (cacheControl == ImageCacheControl.RELOAD) { + FLog.w(ReactConstants.TAG, "disabling disk cache for $uri with $cacheControl") + // imageRequestBuilder.disableDiskCache() + // imageRequestBuilder.disableMemoryCache() + // imageRequestBuilder.setCacheChoice(ImageRequest.CacheChoice.SMALL) + + // clears the cache before the request is executed + val imagePipeline = Fresco.getImagePipeline() + imagePipeline.evictFromCache(uri) + } + val imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(uri) .setPostprocessor(postprocessor) @@ -457,8 +470,6 @@ public class ReactImageView( callerContext?.let { builder.setCallerContext(it) } - val cacheControl = imageSource?.cacheControl - FLog.w(ReactConstants.TAG, "landing here with $cacheControl") if (cacheControl != ImageCacheControl.RELOAD) { diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 13fda8d3b757b3..3a5d7620746caa 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -13,10 +13,18 @@ import type {ImageProps} from 'react-native/Libraries/Image/ImageProps'; import type {LayoutEvent} from 'react-native/Libraries/Types/CoreEventTypes'; +import RNTesterButton from '../../components/RNTesterButton'; import RNTesterText from '../../components/RNTesterText'; import ImageCapInsetsExample from './ImageCapInsetsExample'; import React from 'react'; -import {Image, ImageBackground, StyleSheet, Text, View} from 'react-native'; +import { + Button, + Image, + ImageBackground, + StyleSheet, + Text, + View, +} from 'react-native'; import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; const IMAGE1 = @@ -626,6 +634,51 @@ class VectorDrawableExample extends React.Component< } } +function CacheControlAndroidExample(): React.Node { + const [reload, setReload] = React.useState(0); + + const onReload = () => { + setReload(reload + 1); + }; + + return ( + <> + + + Default + + + + Reload + + + + + + + + Re-render image components + + + + + ); +} + const fullImage: ImageSource = { uri: IMAGE2, }; @@ -863,6 +916,11 @@ const styles = StyleSheet.create({ height: 100, width: '500%', }, + cachePolicyAndroidButtonContainer: { + flex: 1, + alignItems: 'center', + marginTop: 10, + }, }); exports.displayName = (undefined: ?string); @@ -1009,7 +1067,7 @@ exports.examples = [ // platform: 'ios', // }, { - title: 'Cache Policy', + title: 'Cache Policy iOS', description: ('First image has never been loaded before and is instructed not to load unless in cache.' + 'Placeholder image from above will stay. Second image is the same but forced to load regardless of' + @@ -1033,18 +1091,19 @@ exports.examples = [ }} style={styles.base} /> - ); }, - // platform: 'ios', + platform: 'ios', + }, + { + title: 'Cache Policy Android', + description: + ('First image will be loaded and will be cached. Second image is the same but will be reloaded as the cache policy is set to reload.': string), + render: function (): React.Node { + return ; + }, + platform: 'android', }, // { // title: 'Borders', From 5921703156d4938a2a7bc8a37119c3e65f6cb33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Wed, 23 Oct 2024 22:40:43 +0200 Subject: [PATCH 4/6] refactor(image): cleaning up non-needed logs --- .../react/views/image/ReactImageManager.kt | 3 - .../react/views/image/ReactImageView.kt | 41 +- .../react/views/imagehelper/ImageSource.kt | 8 - .../js/examples/Image/ImageExample.js | 1588 ++++++++--------- 4 files changed, 804 insertions(+), 836 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt index 94261eb3bb0816..77e1d986655597 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt @@ -101,14 +101,11 @@ public constructor( // In JS this is Image.props.source @ReactProp(name = "src") public fun setSrc(view: ReactImageView, sources: ReadableArray?) { - FLog.w(ReactConstants.TAG, "setSrc called with $sources") setSource(view, sources) } @ReactProp(name = "source") public fun setSource(view: ReactImageView, sources: ReadableArray?) { - // log the params - FLog.w(ReactConstants.TAG, "setSource called with $sources") view.setSource(sources) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index dd16409a1e2d56..246c36ced26210 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -11,8 +11,6 @@ package com.facebook.react.views.image import android.content.Context import android.graphics.Bitmap -import com.facebook.common.logging.FLog -import com.facebook.react.common.ReactConstants import android.graphics.BitmapShader import android.graphics.Canvas import android.graphics.Color @@ -26,6 +24,7 @@ import android.graphics.drawable.Drawable import android.net.Uri import com.facebook.common.references.CloseableReference import com.facebook.common.util.UriUtil +import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.controller.AbstractDraweeControllerBuilder import com.facebook.drawee.controller.ControllerListener import com.facebook.drawee.controller.ForwardingControllerListener @@ -52,7 +51,6 @@ import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.build.ReactBuildConfig import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.modules.fresco.ReactNetworkImageRequest -import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.react.uimanager.BackgroundStyleApplicator import com.facebook.react.uimanager.LengthPercentage import com.facebook.react.uimanager.LengthPercentageType @@ -430,13 +428,8 @@ public class ReactImageView( val resizeOptions = if (doResize) resizeOptions else null if (cacheControl == ImageCacheControl.RELOAD) { - FLog.w(ReactConstants.TAG, "disabling disk cache for $uri with $cacheControl") - // imageRequestBuilder.disableDiskCache() - // imageRequestBuilder.disableMemoryCache() - // imageRequestBuilder.setCacheChoice(ImageRequest.CacheChoice.SMALL) - - // clears the cache before the request is executed val imagePipeline = Fresco.getImagePipeline() + imagePipeline.evictFromCache(uri) } @@ -470,25 +463,19 @@ public class ReactImageView( callerContext?.let { builder.setCallerContext(it) } - FLog.w(ReactConstants.TAG, "landing here with $cacheControl") - - if (cacheControl != ImageCacheControl.RELOAD) { - cachedImageSource?.let { cachedSource -> - val cachedImageRequestBuilder = - ImageRequestBuilder.newBuilderWithSource(cachedSource.uri) - .setPostprocessor(postprocessor) - .setResizeOptions(resizeOptions) - .setAutoRotateEnabled(true) - .setProgressiveRenderingEnabled(progressiveRenderingEnabled) - if (resizeMethod == ImageResizeMethod.NONE) { - cachedImageRequestBuilder.setDownsampleOverride(DownsampleMode.NEVER) - } - builder.setLowResImageRequest(cachedImageRequestBuilder.build()) + cachedImageSource?.let { cachedSource -> + val cachedImageRequestBuilder = + ImageRequestBuilder.newBuilderWithSource(cachedSource.uri) + .setPostprocessor(postprocessor) + .setResizeOptions(resizeOptions) + .setAutoRotateEnabled(true) + .setProgressiveRenderingEnabled(progressiveRenderingEnabled) + if (resizeMethod == ImageResizeMethod.NONE) { + cachedImageRequestBuilder.setDownsampleOverride(DownsampleMode.NEVER) } - } else { - FLog.w(ReactConstants.TAG, "skipping cache for $uri with $cacheControl") + builder.setLowResImageRequest(cachedImageRequestBuilder.build()) } - + if (downloadListener != null && controllerForTesting != null) { val combinedListener: ForwardingControllerListener = ForwardingControllerListener() @@ -541,10 +528,8 @@ public class ReactImageView( imageSource = multiSource.bestResult cachedImageSource = multiSource.bestResultInCache - FLog.w(ReactConstants.TAG, "bestResult: ${imageSource?.uri}, bestResultInCache: ${cachedImageSource?.uri}") return } - FLog.w(ReactConstants.TAG, "bestResult sources: ${sources[0].uri}") imageSource = sources[0] } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt index 4d772ce9aaebe0..f3cbff57a6e8af 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt @@ -9,8 +9,6 @@ package com.facebook.react.views.imagehelper import android.content.Context import android.net.Uri -import com.facebook.common.logging.FLog -import com.facebook.react.common.ReactConstants import com.facebook.react.views.image.ImageCacheControl import java.util.Objects import android.util.Log @@ -27,11 +25,6 @@ constructor( cacheControl: String? = null ) { - init { - // Log cache control to ensure it's set correctly - Log.d("ImageSource", "CacheControl set to: $cacheControl") - } - /** Get the URI for this image - can be either a parsed network URI or a resource URI. */ public open val uri: Uri = computeUri(context) /** Get the area of this image. */ @@ -82,7 +75,6 @@ constructor( "default" -> ImageCacheControl.DEFAULT "reload" -> ImageCacheControl.RELOAD else -> { - FLog.w(ReactConstants.TAG, "Invalid resize method $cacheControl") return ImageCacheControl.DEFAULT } } diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 3a5d7620746caa..76ec29d62e3889 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -17,14 +17,7 @@ import RNTesterButton from '../../components/RNTesterButton'; import RNTesterText from '../../components/RNTesterText'; import ImageCapInsetsExample from './ImageCapInsetsExample'; import React from 'react'; -import { - Button, - Image, - ImageBackground, - StyleSheet, - Text, - View, -} from 'react-native'; +import {Image, ImageBackground, StyleSheet, Text, View} from 'react-native'; import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; const IMAGE1 = @@ -931,143 +924,143 @@ exports.description = 'Base component for displaying different types of images.'; exports.examples = [ - // { - // title: 'Plain Network Image with `source` prop.', - // description: ('If the `source` prop `uri` property is prefixed with ' + - // '"http", then it will be downloaded from the network.': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Plain Network Image with `src` prop.', - // description: ('If the `src` prop is defined with ' + - // '"http", then it will be downloaded from the network.': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Multiple Image Source using the `srcSet` prop.', - // description: - // ('A list of comma seperated uris along with scale are provided in `srcSet`.' + - // 'An appropriate value will be used based on the scale of the device.': string), - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // title: 'Plain Blob Image', - // description: ('If the `source` prop `uri` property is an object URL, ' + - // 'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Plain Static Image', - // description: - // ('Static assets should be placed in the source code tree, and ' + - // 'required in the same way as JavaScript modules.': string), - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Image Loading Events', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // title: 'Error Handler', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // title: 'Error Handler for Large Images', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // title: 'Image Download Progress', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // title: 'defaultSource', - // description: 'Show a placeholder image when a network image is loading', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // platform: 'ios', - // }, { - title: 'Cache Policy iOS', + title: 'Plain Network Image with `source` prop.', + description: ('If the `source` prop `uri` property is prefixed with ' + + '"http", then it will be downloaded from the network.': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'Plain Network Image with `src` prop.', + description: ('If the `src` prop is defined with ' + + '"http", then it will be downloaded from the network.': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'Multiple Image Source using the `srcSet` prop.', + description: + ('A list of comma seperated uris along with scale are provided in `srcSet`.' + + 'An appropriate value will be used based on the scale of the device.': string), + render: function (): React.Node { + return ( + + ); + }, + }, + { + title: 'Plain Blob Image', + description: ('If the `source` prop `uri` property is an object URL, ' + + 'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'Plain Static Image', + description: + ('Static assets should be placed in the source code tree, and ' + + 'required in the same way as JavaScript modules.': string), + render: function (): React.Node { + return ( + + + + + + + ); + }, + }, + { + title: 'Image Loading Events', + render: function (): React.Node { + return ( + + ); + }, + }, + { + title: 'Error Handler', + render: function (): React.Node { + return ( + + ); + }, + }, + { + title: 'Error Handler for Large Images', + render: function (): React.Node { + return ( + + ); + }, + }, + { + title: 'Image Download Progress', + render: function (): React.Node { + return ( + + ); + }, + }, + { + title: 'defaultSource', + description: 'Show a placeholder image when a network image is loading', + render: function (): React.Node { + return ( + + ); + }, + platform: 'ios', + }, + { + title: 'Cache Policy', description: ('First image has never been loaded before and is instructed not to load unless in cache.' + 'Placeholder image from above will stay. Second image is the same but forced to load regardless of' + @@ -1097,662 +1090,663 @@ exports.examples = [ platform: 'ios', }, { - title: 'Cache Policy Android', - description: - ('First image will be loaded and will be cached. Second image is the same but will be reloaded as the cache policy is set to reload.': string), + title: 'Cache Policy', + description: ('First image will be loaded and will be cached. ' + + 'Second image is the same but will be reloaded if re-rendered' + + 'as the cache policy is set to reload.': string), render: function (): React.Node { return ; }, platform: 'android', }, - // { - // title: 'Borders', - // name: 'borders', - // render: function (): React.Node { - // return ( - // - // - // - // ); - // }, - // }, - // { - // title: 'Border Radius', - // name: 'border-radius', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Background Color', - // name: 'background-color', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Box Shadow', - // name: 'box-shadow', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Opacity', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Nesting content inside component', - // render: function (): React.Node { - // return ( - // - // - // React - // - // ); - // }, - // }, - // { - // title: 'Nesting content inside component', - // render: function (): React.Node { - // return ( - // - // React - // - // ); - // }, - // }, - // { - // title: 'Tint Color', - // description: ('The `tintColor` prop changes all the non-alpha ' + - // 'pixels to the tint color.': string), - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // It also works using the `tintColor` style prop - // - // - // - // - // - // - // - // - // The `tintColor` prop has precedence over the `tintColor` style prop - // - // - // - // - // - // - // - // - // It also works with downloaded images: - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Object Fit', - // description: ('The `objectFit` style prop controls how the image is ' + - // 'rendered within the frame.': string), - // render: function (): React.Node { - // return ( - // - // {[smallImage, fullImage].map((image, index) => { - // return ( - // - // - // - // - // Contain - // - // - // - // - // - // Cover - // - // - // - // - // - // - // - // Fill - // - // - // - // - // - // Scale Down - // - // - // - // - // - // ); - // })} - // - // ); - // }, - // }, - // { - // title: 'Resize Mode', - // description: ('The `resizeMode` style prop controls how the image is ' + - // 'rendered within the frame.': string), - // render: function (): React.Node { - // return ( - // - // {[smallImage, fullImage].map((image, index) => { - // return ( - // - // - // - // - // Contain - // - // - // - // - // - // Cover - // - // - // - // - // - // - // - // Stretch - // - // - // - // - // - // Repeat - // - // - // - // - // - // Center - // - // - // - // - // - // ); - // })} - // - // ); - // }, - // }, - // { - // title: 'Animated GIF', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // platform: 'ios', - // }, - // { - // title: 'Base64 image', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // platform: 'ios', - // }, - // { - // title: 'Cap Insets', - // description: - // ('When the image is resized, the corners of the size specified ' + - // 'by capInsets will stay a fixed size, but the center content and ' + - // 'borders of the image will be stretched. This is useful for creating ' + - // 'resizable rounded buttons, shadows, and other resizable assets.': string), - // render: function (): React.Node { - // return ; - // }, - // platform: 'ios', - // }, - // { - // title: 'Image Size', - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'MultipleSourcesExample', - // description: - // ('The `source` prop allows passing in an array of uris, so that native to choose which image ' + - // 'to diplay based on the size of the of the target image': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Legacy local image', - // description: ('Images shipped with the native bundle, but not managed ' + - // 'by the JS packager': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Bundled images', - // description: 'Images shipped in a separate native bundle', - // render: function (): React.Node { - // return ( - // - // - // - // - // ); - // }, - // platform: 'ios', - // }, - // { - // title: 'Blur Radius', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Accessibility', - // description: - // ('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Accessibility Label', - // description: - // ('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string), - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // title: 'Accessibility Label via alt prop', - // description: - // 'Using the alt prop markes an element as being accessibile, and passes the alt text to accessibilityLabel', - // render: function (): React.Node { - // return ( - // Picture of people standing around a table - // ); - // }, - // }, - // { - // title: 'Fade Duration', - // description: - // ('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string), - // render: function (): React.Node { - // return ; - // }, - // platform: 'android', - // }, - // { - // title: 'Loading Indicator Source', - // description: - // ('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'On Layout', - // description: - // ('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string), - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'On Partial Load', - // description: - // ('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string), - // render: function (): React.Node { - // return ; - // }, - // platform: 'ios', - // }, - // { - // title: 'Vector Drawable', - // description: - // 'Demonstrating an example of loading a vector drawable asset by name', - // render: function (): React.Node { - // return ; - // }, - // platform: 'android', - // }, - // { - // title: 'Large image with different resize methods', - // name: 'resize-method', - // description: - // 'Demonstrating the effects of loading a large image with different resize methods', - // scrollable: true, - // render: function (): React.Node { - // const methods: Array = [ - // 'auto', - // 'resize', - // 'scale', - // 'none', - // ]; - // // Four copies of the same image so we don't serve cached copies of the same image - // const images = [ - // require('../../assets/large-image-1.png'), - // require('../../assets/large-image-2.png'), - // require('../../assets/large-image-3.png'), - // require('../../assets/large-image-4.png'), - // ]; - // return ( - // - // {methods.map((method, index) => ( - // - // {method} - // - // - // ))} - // - // ); - // }, - // platform: 'android', - // }, + { + title: 'Borders', + name: 'borders', + render: function (): React.Node { + return ( + + + + ); + }, + }, + { + title: 'Border Radius', + name: 'border-radius', + render: function (): React.Node { + return ( + + + + + + + + + + ); + }, + }, + { + title: 'Background Color', + name: 'background-color', + render: function (): React.Node { + return ( + + + + + + + ); + }, + }, + { + title: 'Box Shadow', + name: 'box-shadow', + render: function (): React.Node { + return ( + + + + + + ); + }, + }, + { + title: 'Opacity', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, + { + title: 'Nesting content inside component', + render: function (): React.Node { + return ( + + + React + + ); + }, + }, + { + title: 'Nesting content inside component', + render: function (): React.Node { + return ( + + React + + ); + }, + }, + { + title: 'Tint Color', + description: ('The `tintColor` prop changes all the non-alpha ' + + 'pixels to the tint color.': string), + render: function (): React.Node { + return ( + + + + + + + + + It also works using the `tintColor` style prop + + + + + + + + + The `tintColor` prop has precedence over the `tintColor` style prop + + + + + + + + + It also works with downloaded images: + + + + + + + + + ); + }, + }, + { + title: 'Object Fit', + description: ('The `objectFit` style prop controls how the image is ' + + 'rendered within the frame.': string), + render: function (): React.Node { + return ( + + {[smallImage, fullImage].map((image, index) => { + return ( + + + + + Contain + + + + + + Cover + + + + + + + + Fill + + + + + + Scale Down + + + + + + ); + })} + + ); + }, + }, + { + title: 'Resize Mode', + description: ('The `resizeMode` style prop controls how the image is ' + + 'rendered within the frame.': string), + render: function (): React.Node { + return ( + + {[smallImage, fullImage].map((image, index) => { + return ( + + + + + Contain + + + + + + Cover + + + + + + + + Stretch + + + + + + Repeat + + + + + + Center + + + + + + ); + })} + + ); + }, + }, + { + title: 'Animated GIF', + render: function (): React.Node { + return ( + + ); + }, + platform: 'ios', + }, + { + title: 'Base64 image', + render: function (): React.Node { + return ( + + ); + }, + platform: 'ios', + }, + { + title: 'Cap Insets', + description: + ('When the image is resized, the corners of the size specified ' + + 'by capInsets will stay a fixed size, but the center content and ' + + 'borders of the image will be stretched. This is useful for creating ' + + 'resizable rounded buttons, shadows, and other resizable assets.': string), + render: function (): React.Node { + return ; + }, + platform: 'ios', + }, + { + title: 'Image Size', + render: function (): React.Node { + return ; + }, + }, + { + title: 'MultipleSourcesExample', + description: + ('The `source` prop allows passing in an array of uris, so that native to choose which image ' + + 'to diplay based on the size of the of the target image': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'Legacy local image', + description: ('Images shipped with the native bundle, but not managed ' + + 'by the JS packager': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'Bundled images', + description: 'Images shipped in a separate native bundle', + render: function (): React.Node { + return ( + + + + + ); + }, + platform: 'ios', + }, + { + title: 'Blur Radius', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, + { + title: 'Accessibility', + description: + ('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'Accessibility Label', + description: + ('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string), + render: function (): React.Node { + return ( + + ); + }, + }, + { + title: 'Accessibility Label via alt prop', + description: + 'Using the alt prop markes an element as being accessibile, and passes the alt text to accessibilityLabel', + render: function (): React.Node { + return ( + Picture of people standing around a table + ); + }, + }, + { + title: 'Fade Duration', + description: + ('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string), + render: function (): React.Node { + return ; + }, + platform: 'android', + }, + { + title: 'Loading Indicator Source', + description: + ('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'On Layout', + description: + ('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string), + render: function (): React.Node { + return ; + }, + }, + { + title: 'On Partial Load', + description: + ('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string), + render: function (): React.Node { + return ; + }, + platform: 'ios', + }, + { + title: 'Vector Drawable', + description: + 'Demonstrating an example of loading a vector drawable asset by name', + render: function (): React.Node { + return ; + }, + platform: 'android', + }, + { + title: 'Large image with different resize methods', + name: 'resize-method', + description: + 'Demonstrating the effects of loading a large image with different resize methods', + scrollable: true, + render: function (): React.Node { + const methods: Array = [ + 'auto', + 'resize', + 'scale', + 'none', + ]; + // Four copies of the same image so we don't serve cached copies of the same image + const images = [ + require('../../assets/large-image-1.png'), + require('../../assets/large-image-2.png'), + require('../../assets/large-image-3.png'), + require('../../assets/large-image-4.png'), + ]; + return ( + + {methods.map((method, index) => ( + + {method} + + + ))} + + ); + }, + platform: 'android', + }, ]; From 28fdd4b94bb3b67aadaa481d247f9e4ea85432f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Wed, 23 Oct 2024 22:52:41 +0200 Subject: [PATCH 5/6] refactor(image): cleaning unused imports --- packages/react-native/Libraries/Image/Image.android.js | 2 -- packages/react-native/Libraries/Image/Image.ios.js | 2 -- .../main/java/com/facebook/react/views/image/ReactImageView.kt | 3 +-- .../java/com/facebook/react/views/imagehelper/ImageSource.kt | 1 - packages/rn-tester/js/examples/Image/ImageExample.js | 2 +- 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/react-native/Libraries/Image/Image.android.js b/packages/react-native/Libraries/Image/Image.android.js index 70e4783dca44e8..998270b1cfc3be 100644 --- a/packages/react-native/Libraries/Image/Image.android.js +++ b/packages/react-native/Libraries/Image/Image.android.js @@ -150,8 +150,6 @@ let BaseImage: AbstractImageAndroid = React.forwardRef( ); } - console.log({source: props.source}); - let style: ImageStyleProp; let sources; if (Array.isArray(source)) { diff --git a/packages/react-native/Libraries/Image/Image.ios.js b/packages/react-native/Libraries/Image/Image.ios.js index 8c73136c1414ca..b3b9e61e50b9ba 100644 --- a/packages/react-native/Libraries/Image/Image.ios.js +++ b/packages/react-native/Libraries/Image/Image.ios.js @@ -149,8 +149,6 @@ let BaseImage: AbstractImageIOS = React.forwardRef((props, forwardedRef) => { ...restProps } = props; - console.log({ cache: props.source }); - const _accessibilityState = { busy: ariaBusy ?? props.accessibilityState?.busy, checked: ariaChecked ?? props.accessibilityState?.checked, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index 246c36ced26210..2a5770a7957081 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -475,7 +475,7 @@ public class ReactImageView( } builder.setLowResImageRequest(cachedImageRequestBuilder.build()) } - + if (downloadListener != null && controllerForTesting != null) { val combinedListener: ForwardingControllerListener = ForwardingControllerListener() @@ -527,7 +527,6 @@ public class ReactImageView( val multiSource = getBestSourceForSize(width, height, sources) imageSource = multiSource.bestResult cachedImageSource = multiSource.bestResultInCache - return } imageSource = sources[0] diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt index f3cbff57a6e8af..2a07424435ab6b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt @@ -11,7 +11,6 @@ import android.content.Context import android.net.Uri import com.facebook.react.views.image.ImageCacheControl import java.util.Objects -import android.util.Log /** Class describing an image source (network URI or resource) and size. */ public open class ImageSource diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 76ec29d62e3889..7be0b75b69672c 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -631,7 +631,7 @@ function CacheControlAndroidExample(): React.Node { const [reload, setReload] = React.useState(0); const onReload = () => { - setReload(reload + 1); + setReload(prevReload => prevReload + 1); }; return ( From c2b0e6c8e310f7e8123b22f5961278e58433569e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Wed, 23 Oct 2024 23:30:44 +0200 Subject: [PATCH 6/6] docs(image): adding correct type spec for only ios props --- packages/react-native/Libraries/Image/ImageSource.d.ts | 2 +- packages/react-native/Libraries/Image/ImageSource.js | 2 +- packages/rn-tester/js/examples/Image/ImageExample.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native/Libraries/Image/ImageSource.d.ts b/packages/react-native/Libraries/Image/ImageSource.d.ts index 0e163309a87a76..1b7d51729b5d42 100644 --- a/packages/react-native/Libraries/Image/ImageSource.d.ts +++ b/packages/react-native/Libraries/Image/ImageSource.d.ts @@ -51,7 +51,7 @@ export interface ImageURISource { * to a URL load request, no attempt is made to load the data from the originating source, * and the load is considered to have failed. * - * @platform ios + * @platform ios (for `force-cache` and `only-if-cached`) */ cache?: 'default' | 'reload' | 'force-cache' | 'only-if-cached' | undefined; /** diff --git a/packages/react-native/Libraries/Image/ImageSource.js b/packages/react-native/Libraries/Image/ImageSource.js index bbb32572125c11..a489de2ff07409 100644 --- a/packages/react-native/Libraries/Image/ImageSource.js +++ b/packages/react-native/Libraries/Image/ImageSource.js @@ -66,7 +66,7 @@ export interface ImageURISource { * to a URL load request, no attempt is made to load the data from the originating source, * and the load is considered to have failed. * - * @platform ios + * @platform ios (for `force-cache` and `only-if-cached`) */ +cache?: ?('default' | 'reload' | 'force-cache' | 'only-if-cached'); diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 7be0b75b69672c..814c7312ea775d 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -1092,7 +1092,7 @@ exports.examples = [ { title: 'Cache Policy', description: ('First image will be loaded and will be cached. ' + - 'Second image is the same but will be reloaded if re-rendered' + + 'Second image is the same but will be reloaded if re-rendered ' + 'as the cache policy is set to reload.': string), render: function (): React.Node { return ;