react-native-fast-image 9.3.6 → 9.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,6 @@ import com.bumptech.glide.load.model.LazyHeaders;
16
16
  import com.bumptech.glide.request.RequestOptions;
17
17
  import com.bumptech.glide.signature.ApplicationVersionSignature;
18
18
  import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
19
- import com.facebook.react.bridge.NoSuchKeyException;
20
19
  import com.facebook.react.bridge.ReadableMap;
21
20
  import com.facebook.react.bridge.ReadableMapKeySetIterator;
22
21
  import com.facebook.react.bridge.ReadableType;
@@ -34,6 +33,10 @@ class FastImageViewConverter {
34
33
  put("immutable", FastImageCacheControl.IMMUTABLE);
35
34
  put("web", FastImageCacheControl.WEB);
36
35
  put("cacheOnly", FastImageCacheControl.CACHE_ONLY);
36
+ // React Native's ImageURISource value, which views take. Its
37
+ // others (e.g. 'reload') are immutable, as any value this map
38
+ // doesn't have (see getValueFromSource).
39
+ put("only-if-cached", FastImageCacheControl.CACHE_ONLY);
37
40
  }};
38
41
 
39
42
  private static final Map<String, Priority> FAST_IMAGE_PRIORITY_MAP =
@@ -174,13 +177,12 @@ class FastImageViewConverter {
174
177
  return value;
175
178
  }
176
179
 
180
+ // A value the map doesn't have (e.g. React Native's `cache: 'reload'`, or
181
+ // one that isn't a string) gets the default, as on iOS.
177
182
  private static <T> T getValueFromSource(String propName, String defaultProp, Map<String, T> map, ReadableMap source) {
178
- String propValue;
179
- try {
180
- propValue = source != null ? source.getString(propName) : null;
181
- } catch (NoSuchKeyException e) {
182
- propValue = null;
183
- }
184
- return getValue(propName, defaultProp, map, propValue);
183
+ T value = source != null && source.hasKey(propName) && source.getType(propName) == ReadableType.String
184
+ ? map.get(source.getString(propName))
185
+ : null;
186
+ return value != null ? value : map.get(defaultProp);
185
187
  }
186
188
  }
@@ -780,13 +780,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
780
780
  private ScaleType mLayoutScaleType = ScaleType.FIT_CENTER;
781
781
 
782
782
  // Glide crops or fits the bitmap for the scale type when it loads, so a
783
- // new resizeMode needs a reload to take effect (#762).
783
+ // new resizeMode needs a reload to take effect (#762): the image that's
784
+ // showing, loaded again quietly (see reblur), as for a new blurRadius,
785
+ // without the load events, as on iOS.
784
786
  public void setResizeMode(ScaleType scaleType, boolean repeat, boolean inPixels) {
785
787
  if (scaleType == mResizeScaleType && repeat == mRepeat && inPixels == mInPixels) return;
786
788
  mResizeScaleType = scaleType;
787
789
  mInPixels = inPixels;
788
790
  mRepeat = repeat;
789
- mNeedsReload = true;
791
+ mFitChanged = true;
790
792
  // A loaded image on screen was cropped or fitted for the old scale
791
793
  // type: it keeps its layout until the image for the new one is ready
792
794
  // (onImageLoaded), or the load fails. Laid out for the new one, a
@@ -946,6 +948,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
946
948
  // reblur for a change).
947
949
  private float mBlurRadius = 0;
948
950
  private boolean mBlurChanged = false;
951
+ // resizeMode or objectFit changed (setResizeMode).
952
+ private boolean mFitChanged = false;
949
953
 
950
954
  public void setBlurRadius(float blurRadius) {
951
955
  if (blurRadius == mBlurRadius) return;
@@ -1123,10 +1127,11 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
1123
1127
  into(shown, builder, meanwhile, true);
1124
1128
  }
1125
1129
 
1126
- // blurRadius changed, and nothing else that needs a reload: the image
1127
- // that's showing, blurred again, with no events, as reloadForSize does: it's
1128
- // decoded again from the cache and blurred on Glide's threads, and the view
1129
- // keeps the current image until then. If that fails, it shows the image
1130
+ // blurRadius or resizeMode changed, and nothing else that needs a reload:
1131
+ // the image that's showing, blurred or fitted again, with no events, as
1132
+ // reloadForSize does: it's decoded again from the cache and blurred or
1133
+ // fitted on Glide's threads, and the view keeps the current image (and its
1134
+ // layout, see onImageLoaded) until then. If that fails, it shows the image
1130
1135
  // from Glide's caches; cache: 'web' skips them, so the view shows
1131
1136
  // defaultSource (or nothing) then. Also while it's loading again that way
1132
1137
  // (e.g. the blur changes every frame): that load is replaced. A load that
@@ -1240,8 +1245,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
1240
1245
  @Nullable RequestManager requestManager) {
1241
1246
  if (mDropped) return;
1242
1247
  mManager = manager;
1243
- if (mBlurChanged && !mNeedsReload) {
1248
+ if ((mBlurChanged || mFitChanged) && !mNeedsReload) {
1244
1249
  mBlurChanged = false;
1250
+ mFitChanged = false;
1245
1251
  reblur();
1246
1252
  }
1247
1253
  if (!mNeedsReload)
@@ -1272,6 +1278,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
1272
1278
  // defaultSource, resizeMode), not for every prop update.
1273
1279
  mNeedsReload = false;
1274
1280
  mBlurChanged = false;
1281
+ mFitChanged = false;
1275
1282
  boolean switching = mSwitchesSource;
1276
1283
  mSwitchesSource = false;
1277
1284
  boolean restarting = mRestarting;
package/dist/index.cjs.js CHANGED
@@ -82,6 +82,24 @@ function resolveObjectFit(objectFit, style, resizeMode) {
82
82
  return (_RESIZE_MODE_FIT$resi = RESIZE_MODE_FIT[resizeMode]) !== null && _RESIZE_MODE_FIT$resi !== void 0 ? _RESIZE_MODE_FIT$resi : "cover";
83
83
  }
84
84
  //#endregion
85
+ //#region src/reactNativeCache.ts
86
+ const REACT_NATIVE_CACHE = [
87
+ "default",
88
+ "reload",
89
+ "force-cache",
90
+ "only-if-cached"
91
+ ];
92
+ const isReactNativeCache = (cache) => REACT_NATIVE_CACHE.includes(cache);
93
+ let warnedReload = false;
94
+ function warnIfReload(source) {
95
+ if (warnedReload || typeof __DEV__ === "undefined" || !__DEV__) return;
96
+ for (const entry of Array.isArray(source) ? source : [source]) if ((entry === null || entry === void 0 ? void 0 : entry.cache) === "reload") {
97
+ warnedReload = true;
98
+ console.warn("react-native-fast-image: `cache: 'reload'` loads the image as the default, `'immutable'`: it isn't loaded again each time. For an image that changes at the same url, use `cache: 'web'` (it follows the server's cache headers), or change its `cacheKey`.");
99
+ return;
100
+ }
101
+ }
102
+ //#endregion
85
103
  //#region src/index.tsx
86
104
  const resolveDefaultSource = (defaultSource) => {
87
105
  if (!defaultSource) return null;
@@ -139,8 +157,11 @@ function transitionProps(transition) {
139
157
  };
140
158
  }
141
159
  function withoutCache(source) {
142
- const { cache: _cache, ...rest } = source || {};
143
- return rest;
160
+ const { cache, ...rest } = source || {};
161
+ return isReactNativeCache(cache) ? {
162
+ ...rest,
163
+ cache
164
+ } : rest;
144
165
  }
145
166
  function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, objectFit, resizeMode = "cover", loop, transition, forwardedRef, onLayout, pointerEvents, hitSlop, onStartShouldSetResponder, onStartShouldSetResponderCapture, onMoveShouldSetResponder, onMoveShouldSetResponderCapture, onResponderGrant, onResponderReject, onResponderStart, onResponderMove, onResponderEnd, onResponderRelease, onResponderTerminationRequest, onResponderTerminate, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, onTouchEndCapture, ...viewProps }) {
146
167
  const { onClick, ...props } = viewProps;
@@ -214,6 +235,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
214
235
  resizeMode: mode === "none" || mode === "scale-down" ? "center" : mode
215
236
  }), children);
216
237
  }
238
+ warnIfReload(source);
217
239
  const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
218
240
  const single = Array.isArray(source) ? source[0] : source;
219
241
  const resolvedSource = sources ? void 0 : react_native.Image.resolveAssetSource(single);
@@ -112,6 +112,15 @@ export type FastImageSource = {
112
112
  scale?: number,
113
113
  }
114
114
 
115
+ // React Native's ImageURISource cache values, which the views' `source` takes
116
+ // so a React Native source works as it is: native reads 'only-if-cached' as
117
+ // 'cacheOnly', and the others as the default, 'immutable'.
118
+ type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
119
+ type ViewSource = {
120
+ ...FastImageSource,
121
+ cache?: CacheControls | ReactNativeCache,
122
+ }
123
+
115
124
  export type FastImageProps = $ReadOnly<{|
116
125
  ...ViewProps,
117
126
  onError?: ?(event: OnErrorEvent) => void,
@@ -120,7 +129,7 @@ export type FastImageProps = $ReadOnly<{|
120
129
  onLoadStart?: ?() => void,
121
130
  onProgress?: ?(event: OnProgressEvent) => void,
122
131
 
123
- source?: ?(FastImageSource | Array<FastImageSource> | number),
132
+ source?: ?(ViewSource | Array<ViewSource> | number),
124
133
  defaultSource?: ?number,
125
134
 
126
135
  tintColor?: number | string,
@@ -112,6 +112,15 @@ export type FastImageSource = {
112
112
  scale?: number,
113
113
  }
114
114
 
115
+ // React Native's ImageURISource cache values, which the views' `source` takes
116
+ // so a React Native source works as it is: native reads 'only-if-cached' as
117
+ // 'cacheOnly', and the others as the default, 'immutable'.
118
+ type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
119
+ type ViewSource = {
120
+ ...FastImageSource,
121
+ cache?: CacheControls | ReactNativeCache,
122
+ }
123
+
115
124
  export type FastImageProps = $ReadOnly<{|
116
125
  ...ViewProps,
117
126
  onError?: ?(event: OnErrorEvent) => void,
@@ -120,7 +129,7 @@ export type FastImageProps = $ReadOnly<{|
120
129
  onLoadStart?: ?() => void,
121
130
  onProgress?: ?(event: OnProgressEvent) => void,
122
131
 
123
- source?: ?(FastImageSource | Array<FastImageSource> | number),
132
+ source?: ?(ViewSource | Array<ViewSource> | number),
124
133
  defaultSource?: ?number,
125
134
 
126
135
  tintColor?: number | string,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ColorValue, View, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
3
3
  import { cacheControl, priority, resizeMode } from './constants';
4
+ import { type ReactNativeCache } from './reactNativeCache';
4
5
  /** How the image fits the view: see [`objectFit`](#objectfit). */
5
6
  export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
6
7
  /** How the image fits the view: see [`resizeMode`](#resizemode). */
@@ -99,6 +100,9 @@ export type Source = {
99
100
  /** With several sources, the scale `width` and `height` are multiplied by. */
100
101
  scale?: number;
101
102
  };
103
+ type ViewSource = Source | (Omit<Source, 'cache'> & {
104
+ cache?: ReactNativeCache;
105
+ });
102
106
  /** How the image fades in: see [`transition`](#transition). */
103
107
  export type Transition = {
104
108
  /**
@@ -277,7 +281,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
277
281
  * `{ ok: false, error }`.
278
282
  * - An array of one is the same as that source.
279
283
  */
280
- source?: Source | Source[] | ImageRequireSource;
284
+ source?: ViewSource | ViewSource[] | ImageRequireSource;
281
285
  /**
282
286
  * An asset loaded with `require(...)`, shown while the first image loads,
283
287
  * and if an image fails to load. When `source` changes, the previous image
package/dist/index.js CHANGED
@@ -55,6 +55,24 @@ function resolveObjectFit(objectFit, style, resizeMode) {
55
55
  return (_RESIZE_MODE_FIT$resi = RESIZE_MODE_FIT[resizeMode]) !== null && _RESIZE_MODE_FIT$resi !== void 0 ? _RESIZE_MODE_FIT$resi : "cover";
56
56
  }
57
57
  //#endregion
58
+ //#region src/reactNativeCache.ts
59
+ const REACT_NATIVE_CACHE = [
60
+ "default",
61
+ "reload",
62
+ "force-cache",
63
+ "only-if-cached"
64
+ ];
65
+ const isReactNativeCache = (cache) => REACT_NATIVE_CACHE.includes(cache);
66
+ let warnedReload = false;
67
+ function warnIfReload(source) {
68
+ if (warnedReload || typeof __DEV__ === "undefined" || !__DEV__) return;
69
+ for (const entry of Array.isArray(source) ? source : [source]) if ((entry === null || entry === void 0 ? void 0 : entry.cache) === "reload") {
70
+ warnedReload = true;
71
+ console.warn("react-native-fast-image: `cache: 'reload'` loads the image as the default, `'immutable'`: it isn't loaded again each time. For an image that changes at the same url, use `cache: 'web'` (it follows the server's cache headers), or change its `cacheKey`.");
72
+ return;
73
+ }
74
+ }
75
+ //#endregion
58
76
  //#region src/index.tsx
59
77
  const resolveDefaultSource = (defaultSource) => {
60
78
  if (!defaultSource) return null;
@@ -112,8 +130,11 @@ function transitionProps(transition) {
112
130
  };
113
131
  }
114
132
  function withoutCache(source) {
115
- const { cache: _cache, ...rest } = source || {};
116
- return rest;
133
+ const { cache, ...rest } = source || {};
134
+ return isReactNativeCache(cache) ? {
135
+ ...rest,
136
+ cache
137
+ } : rest;
117
138
  }
118
139
  function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, objectFit, resizeMode = "cover", loop, transition, forwardedRef, onLayout, pointerEvents, hitSlop, onStartShouldSetResponder, onStartShouldSetResponderCapture, onMoveShouldSetResponder, onMoveShouldSetResponderCapture, onResponderGrant, onResponderReject, onResponderStart, onResponderMove, onResponderEnd, onResponderRelease, onResponderTerminationRequest, onResponderTerminate, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, onTouchEndCapture, ...viewProps }) {
119
140
  const { onClick, ...props } = viewProps;
@@ -187,6 +208,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
187
208
  resizeMode: mode === "none" || mode === "scale-down" ? "center" : mode
188
209
  }), children);
189
210
  }
211
+ warnIfReload(source);
190
212
  const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
191
213
  const single = Array.isArray(source) ? source[0] : source;
192
214
  const resolvedSource = sources ? void 0 : Image.resolveAssetSource(single);
@@ -112,6 +112,15 @@ export type FastImageSource = {
112
112
  scale?: number,
113
113
  }
114
114
 
115
+ // React Native's ImageURISource cache values, which the views' `source` takes
116
+ // so a React Native source works as it is: native reads 'only-if-cached' as
117
+ // 'cacheOnly', and the others as the default, 'immutable'.
118
+ type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
119
+ type ViewSource = {
120
+ ...FastImageSource,
121
+ cache?: CacheControls | ReactNativeCache,
122
+ }
123
+
115
124
  export type FastImageProps = $ReadOnly<{|
116
125
  ...ViewProps,
117
126
  onError?: ?(event: OnErrorEvent) => void,
@@ -120,7 +129,7 @@ export type FastImageProps = $ReadOnly<{|
120
129
  onLoadStart?: ?() => void,
121
130
  onProgress?: ?(event: OnProgressEvent) => void,
122
131
 
123
- source?: ?(FastImageSource | Array<FastImageSource> | number),
132
+ source?: ?(ViewSource | Array<ViewSource> | number),
124
133
  defaultSource?: ?number,
125
134
 
126
135
  tintColor?: number | string,
@@ -112,6 +112,15 @@ export type FastImageSource = {
112
112
  scale?: number,
113
113
  }
114
114
 
115
+ // React Native's ImageURISource cache values, which the views' `source` takes
116
+ // so a React Native source works as it is: native reads 'only-if-cached' as
117
+ // 'cacheOnly', and the others as the default, 'immutable'.
118
+ type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
119
+ type ViewSource = {
120
+ ...FastImageSource,
121
+ cache?: CacheControls | ReactNativeCache,
122
+ }
123
+
115
124
  export type FastImageProps = $ReadOnly<{|
116
125
  ...ViewProps,
117
126
  onError?: ?(event: OnErrorEvent) => void,
@@ -120,7 +129,7 @@ export type FastImageProps = $ReadOnly<{|
120
129
  onLoadStart?: ?() => void,
121
130
  onProgress?: ?(event: OnProgressEvent) => void,
122
131
 
123
- source?: ?(FastImageSource | Array<FastImageSource> | number),
132
+ source?: ?(ViewSource | Array<ViewSource> | number),
124
133
  defaultSource?: ?number,
125
134
 
126
135
  tintColor?: number | string,
@@ -0,0 +1,4 @@
1
+ export type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached';
2
+ export declare const isReactNativeCache: (cache: unknown) => cache is ReactNativeCache;
3
+ export declare function resetReloadWarning(): void;
4
+ export declare function warnIfReload(source: unknown): void;
@@ -24,6 +24,9 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
24
24
  @"immutable": @(FFFCacheControlImmutable),
25
25
  @"web": @(FFFCacheControlWeb),
26
26
  @"cacheOnly": @(FFFCacheControlCacheOnly),
27
+ // React Native's ImageURISource value, which views
28
+ // take. Its others (e.g. 'reload') are the default.
29
+ @"only-if-cached": @(FFFCacheControlCacheOnly),
27
30
  }), FFFCacheControlImmutable, integerValue);
28
31
 
29
32
  + (FFFastImageSource *)FFFastImageSource:(id)json {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "9.3.6",
3
+ "version": "9.4.0",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",