react-native-fast-image 8.8.0 → 8.9.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.
package/README.md CHANGED
@@ -173,6 +173,19 @@ For views that get reused for other content, such as rows in FlashList or recycl
173
173
 
174
174
  ---
175
175
 
176
+ ### `loop?: boolean | number`
177
+
178
+ How many times an animated image (GIF, animated WebP) plays:
179
+
180
+ - Not set **(Default)** - As many times as the file says (like a browser).
181
+ - `true` - Loop forever.
182
+ - `false` - Play once.
183
+ - A number - Play that many times.
184
+
185
+ Changing it restarts the animation.
186
+
187
+ ---
188
+
176
189
  ### `onLoadStart?: () => void`
177
190
 
178
191
  Called when the image starts to load.
@@ -243,11 +256,17 @@ FastImage.preload([
243
256
  ])
244
257
  ```
245
258
 
246
- It resolves when all the images have loaded or failed, with a result for each source, in order: `{ uri, ok, width, height, error }`. `width` and `height` are the image's size (as in `onLoad`), which is also a way to find an image's size without showing it. A few sources load at a time (3, or `SDWebImagePrefetcher`'s `maxConcurrentPrefetchCount` on iOS), so a long list doesn't hold up the images the app is showing, and at low priority unless the source sets `priority`. It never rejects:
259
+ It resolves when all the images have loaded or failed, with a result for each source, in order: `{ uri, ok: true, width, height }` if it loaded, or `{ uri, ok: false, error }` if it didn't (checking `ok` narrows the type in TypeScript). `width` and `height` are the image's size (as in `onLoad`), which is also a way to find an image's size without showing it. A few sources load at a time (3, or `SDWebImagePrefetcher`'s `maxConcurrentPrefetchCount` on iOS), so a long list doesn't hold up the images the app is showing, and at low priority unless the source sets `priority`. It never rejects:
247
260
 
248
261
  ```js
249
262
  const results = await FastImage.preload(sources)
250
- const failed = results.filter((result) => !result.ok)
263
+ for (const result of results) {
264
+ if (result.ok) {
265
+ console.log(result.uri, result.width, result.height)
266
+ } else {
267
+ console.warn(result.uri, result.error)
268
+ }
269
+ }
251
270
  ```
252
271
 
253
272
  ### `FastImage.clearMemoryCache: () => Promise<void>`
@@ -0,0 +1,73 @@
1
+ package com.dylanvann.fastimage;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.Bitmap;
5
+
6
+ import androidx.annotation.Nullable;
7
+
8
+ import com.bumptech.glide.Glide;
9
+ import com.bumptech.glide.gifdecoder.GifDecoder;
10
+ import com.bumptech.glide.gifdecoder.GifHeader;
11
+ import com.bumptech.glide.gifdecoder.GifHeaderParser;
12
+ import com.bumptech.glide.gifdecoder.StandardGifDecoder;
13
+ import com.bumptech.glide.load.Transformation;
14
+ import com.bumptech.glide.load.engine.Resource;
15
+ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
16
+ import com.bumptech.glide.load.resource.UnitTransformation;
17
+ import com.bumptech.glide.load.resource.bitmap.BitmapResource;
18
+ import com.bumptech.glide.load.resource.gif.GifBitmapProvider;
19
+ import com.bumptech.glide.load.resource.gif.GifDrawable;
20
+
21
+ import java.nio.ByteBuffer;
22
+
23
+ // Gives a view its own animation of a GIF. Glide gives every view that shows
24
+ // the same GIF (same url and size) a GifDrawable over one frame loader (it's
25
+ // in the cached drawable's constant state), so they animate as one: a view
26
+ // whose GIF stopped (the `loop` prop) keeps drawing another view's frames, and
27
+ // restarting one throws while another view plays it. A copy has its own
28
+ // decoder and frame loader over the same data, so each view animates on its
29
+ // own, as with React Native's Image and on iOS. Decoded like Glide's
30
+ // ByteBufferGifDecoder does, at the size the image was loaded at, and cropped
31
+ // or fitted with the same transformation.
32
+ final class FastImageGif {
33
+ private FastImageGif() {}
34
+
35
+ // Null if the GIF can't be decoded again (then the view shows Glide's).
36
+ @Nullable
37
+ static GifDrawable copy(Context context, GifDrawable gif, int width, int height) {
38
+ if (width <= 0 || height <= 0) return null;
39
+ ByteBuffer data = gif.getBuffer();
40
+ GifHeader header = new GifHeaderParser().setData(data).parseHeader();
41
+ if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) return null;
42
+ Glide glide = Glide.get(context);
43
+ BitmapPool bitmapPool = glide.getBitmapPool();
44
+ GifDecoder decoder = new StandardGifDecoder(
45
+ new GifBitmapProvider(bitmapPool, glide.getArrayPool()),
46
+ header, gif.getBuffer(), sampleSize(header, width, height));
47
+ decoder.setDefaultBitmapConfig(Bitmap.Config.ARGB_8888);
48
+ decoder.advance();
49
+ Bitmap firstFrame = decoder.getNextFrame();
50
+ if (firstFrame == null) {
51
+ decoder.clear();
52
+ return null;
53
+ }
54
+ GifDrawable copy = new GifDrawable(
55
+ context, decoder, UnitTransformation.<Bitmap>get(), width, height, firstFrame);
56
+ Transformation<Bitmap> transformation = gif.getFrameTransformation();
57
+ if (transformation != null && !(transformation instanceof UnitTransformation)) {
58
+ // As GifDrawableTransformation does.
59
+ Resource<Bitmap> frame = new BitmapResource(firstFrame, bitmapPool);
60
+ Resource<Bitmap> transformed = transformation.transform(context, frame, width, height);
61
+ if (!frame.equals(transformed)) frame.recycle();
62
+ copy.setFrameTransformation(transformation, transformed.get());
63
+ }
64
+ return copy;
65
+ }
66
+
67
+ // ByteBufferGifDecoder's.
68
+ private static int sampleSize(GifHeader header, int width, int height) {
69
+ int exact = Math.min(header.getHeight() / height, header.getWidth() / width);
70
+ int powerOfTwo = exact == 0 ? 0 : Integer.highestOneBit(exact);
71
+ return Math.max(1, powerOfTwo);
72
+ }
73
+ }
@@ -54,9 +54,9 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
54
54
  final FastImageViewWithUrl view = (FastImageViewWithUrl) ((ImageViewTarget) target).getView();
55
55
  view.onImageLoaded();
56
56
  if (resource instanceof GifDrawable) {
57
- // Play the GIF as many times as the file says, as iOS does. Glide
57
+ // The `loop` prop, or the file's own loop count as on iOS. Glide
58
58
  // loops every GIF forever by default (#651).
59
- ((GifDrawable) resource).setLoopCount(GifDrawable.LOOP_INTRINSIC);
59
+ view.applyLoopCount((GifDrawable) resource);
60
60
  }
61
61
  if (!events) return false;
62
62
  boolean local = !(model instanceof GlideUrl);
@@ -89,6 +89,11 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
89
89
  view.setRecyclingKey(recyclingKey);
90
90
  }
91
91
 
92
+ @ReactProp(name = "loopCount", defaultInt = -1)
93
+ public void setLoopCount(FastImageViewWithUrl view, int loopCount) {
94
+ view.setLoopCount(loopCount);
95
+ }
96
+
92
97
  @ReactProp(name = "resizeMode")
93
98
  public void setResizeMode(FastImageViewWithUrl view, String resizeMode) {
94
99
  final FastImageViewWithUrl.ScaleType scaleType = FastImageViewConverter.getScaleType(resizeMode);
@@ -7,6 +7,7 @@ import android.annotation.SuppressLint;
7
7
  import android.content.Context;
8
8
  import android.graphics.drawable.Drawable;
9
9
 
10
+ import androidx.annotation.NonNull;
10
11
  import androidx.annotation.Nullable;
11
12
  import androidx.appcompat.widget.AppCompatImageView;
12
13
  import androidx.core.view.ViewCompat;
@@ -14,9 +15,11 @@ import androidx.core.view.ViewCompat;
14
15
  import com.bumptech.glide.RequestBuilder;
15
16
  import com.bumptech.glide.RequestManager;
16
17
  import com.bumptech.glide.load.model.GlideUrl;
18
+ import com.bumptech.glide.load.resource.gif.GifDrawable;
17
19
  import com.bumptech.glide.request.Request;
18
20
  import com.bumptech.glide.request.target.DrawableImageViewTarget;
19
21
  import com.bumptech.glide.request.target.SizeReadyCallback;
22
+ import com.bumptech.glide.request.transition.Transition;
20
23
  import com.facebook.react.bridge.ReadableMap;
21
24
  import com.facebook.react.bridge.WritableMap;
22
25
  import com.facebook.react.bridge.WritableNativeMap;
@@ -61,6 +64,68 @@ class FastImageViewWithUrl extends AppCompatImageView {
61
64
  if (!ViewCompat.isLaidOut(this)) layout(0, 0, 0, 0);
62
65
  }
63
66
 
67
+ // How many times GIFs play: -1 for the file's own loop count (the `loop`
68
+ // prop not set), 0 for forever, or a number of times.
69
+ private int mLoopCount = -1;
70
+
71
+ public void setLoopCount(int loopCount) {
72
+ if (loopCount == mLoopCount) return;
73
+ mLoopCount = loopCount;
74
+ // Apply it to the GIF that's showing, and play it again. Only this
75
+ // view's own animation: restarting one Glide shares throws while
76
+ // another view plays it.
77
+ if (mOwnGif != null && getDrawable() == mOwnGif) {
78
+ applyLoopCount(mOwnGif);
79
+ mOwnGif.stop();
80
+ mOwnGif.startFromFirstFrame();
81
+ }
82
+ }
83
+
84
+ // The GIF this view shows as its own animation (FastImageGif), recycled
85
+ // when the view stops showing it.
86
+ @Nullable
87
+ private GifDrawable mOwnGif;
88
+
89
+ // Shows each GIF as this view's own animation. Glide's target also starts
90
+ // and stops it with the Activity, as it does Glide's own GifDrawable.
91
+ private final class OwnGifTarget extends DrawableImageViewTarget {
92
+ OwnGifTarget() {
93
+ super(FastImageViewWithUrl.this);
94
+ }
95
+
96
+ @Override
97
+ public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
98
+ if (resource instanceof GifDrawable) {
99
+ GifDrawable own = FastImageGif.copy(
100
+ getContext(), (GifDrawable) resource, mLoadingWidth, mLoadingHeight);
101
+ if (own != null) {
102
+ applyLoopCount(own);
103
+ super.onResourceReady(own, transition);
104
+ mOwnGif = own;
105
+ return;
106
+ }
107
+ }
108
+ super.onResourceReady(resource, transition);
109
+ }
110
+
111
+ @Override
112
+ protected void setResource(@Nullable Drawable resource) {
113
+ super.setResource(resource);
114
+ // Not shown anymore (replaced, cleared or failed): free its frames.
115
+ if (mOwnGif != null && mOwnGif != resource) {
116
+ mOwnGif.stop();
117
+ mOwnGif.recycle();
118
+ mOwnGif = null;
119
+ }
120
+ }
121
+ }
122
+
123
+ void applyLoopCount(GifDrawable gif) {
124
+ gif.setLoopCount(mLoopCount == -1 ? GifDrawable.LOOP_INTRINSIC
125
+ : mLoopCount == 0 ? GifDrawable.LOOP_FOREVER
126
+ : mLoopCount);
127
+ }
128
+
64
129
  // Glide crops or fits the bitmap for the scale type when it loads, so a
65
130
  // new resizeMode needs a reload to take effect (#762).
66
131
  public void setResizeMode(ScaleType scaleType) {
@@ -179,7 +244,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
179
244
  mLoadingRequest = request;
180
245
  mLoadingWidth = 0;
181
246
  mLoadingHeight = 0;
182
- DrawableImageViewTarget target = new DrawableImageViewTarget(this);
247
+ OwnGifTarget target = new OwnGifTarget();
183
248
  target.getSize(new SizeReadyCallback() {
184
249
  @Override
185
250
  public void onSizeReady(int width, int height) {
package/dist/index.cjs.js CHANGED
@@ -63,11 +63,17 @@ function tintColorFromStyle(style) {
63
63
  }
64
64
  return style && typeof style === "object" ? style.tintColor : void 0;
65
65
  }
66
+ function loopCount(loop) {
67
+ if (loop === void 0) return -1;
68
+ if (loop === true) return 0;
69
+ if (loop === false) return 1;
70
+ return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
71
+ }
66
72
  function withoutCache(source) {
67
73
  const { cache: _cache, ...rest } = source || {};
68
74
  return rest;
69
75
  }
70
- function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", forwardedRef, onLayout, pointerEvents, ...viewProps }) {
76
+ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
71
77
  const { onClick, ...props } = viewProps;
72
78
  const wrapperProps = {
73
79
  onLayout,
@@ -108,6 +114,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
108
114
  }, /* @__PURE__ */ react.default.createElement(FastImageView, {
109
115
  ...imageProps,
110
116
  tintColor: resolvedTintColor,
117
+ loopCount: loopCount(loop),
111
118
  style: react_native.StyleSheet.absoluteFill,
112
119
  source: resolvedSource,
113
120
  defaultSource: resolvedDefaultSource,
@@ -125,18 +132,29 @@ const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */
125
132
  ...props
126
133
  }));
127
134
  FastImageComponent.displayName = "FastImage";
135
+ const noResult = {
136
+ ok: false,
137
+ error: "No result"
138
+ };
128
139
  const FastImage = FastImageComponent;
129
140
  FastImage.resizeMode = resizeMode;
130
141
  FastImage.cacheControl = cacheControl;
131
142
  FastImage.priority = priority;
132
143
  FastImage.preload = (sources) => Promise.resolve(react_native.NativeModules.FastImageView.preload(sources.map((s) => s || {}))).then((results) => sources.map((source, i) => {
133
144
  var _results$i;
134
- return {
135
- uri: source ? source.uri : void 0,
136
- ...(_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : {
137
- ok: false,
138
- error: "No result"
139
- }
145
+ const uri = source ? source.uri : void 0;
146
+ const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
147
+ if (!result.ok) return {
148
+ ...result,
149
+ uri
150
+ };
151
+ return typeof uri === "string" ? {
152
+ ...result,
153
+ uri
154
+ } : {
155
+ ok: false,
156
+ error: "Invalid source: no uri",
157
+ uri
140
158
  };
141
159
  }));
142
160
  FastImage.clearMemoryCache = () => react_native.NativeModules.FastImageView.clearMemoryCache();
@@ -43,14 +43,21 @@ export type ResizeModes = $Values<ResizeMode>
43
43
  export type Priorities = $Values<Priority>
44
44
  export type CacheControls = $Values<CacheControl>
45
45
 
46
- export type PreloadResult = {|
46
+ export type PreloadSuccess = {|
47
+ uri: string,
48
+ ok: true,
49
+ width: number,
50
+ height: number,
51
+ |}
52
+
53
+ export type PreloadFailure = {|
47
54
  uri?: string,
48
- ok: boolean,
49
- width?: number,
50
- height?: number,
51
- error?: string,
55
+ ok: false,
56
+ error: string,
52
57
  |}
53
58
 
59
+ export type PreloadResult = PreloadSuccess | PreloadFailure
60
+
54
61
  export type PreloadFn = (
55
62
  sources: Array<FastImageSource>,
56
63
  ) => Promise<Array<PreloadResult>>
@@ -76,6 +83,7 @@ export type FastImageProps = $ReadOnly<{|
76
83
  resizeMode?: ?ResizeModes,
77
84
  fallback?: ?boolean,
78
85
  recyclingKey?: ?string,
86
+ loop?: ?(boolean | number),
79
87
  testID?: ?string,
80
88
  |}>
81
89
 
package/dist/index.d.ts CHANGED
@@ -69,6 +69,12 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
69
69
  * image. Unlike changing `key`, the view is kept.
70
70
  */
71
71
  recyclingKey?: string | null;
72
+ /**
73
+ * How many times an animated image (GIF, animated WebP) plays: the file's
74
+ * own loop count by default, `true` to loop forever, `false` to play once,
75
+ * or a number of times. Changing it restarts the animation.
76
+ */
77
+ loop?: boolean | number;
72
78
  onLoadStart?(): void;
73
79
  onProgress?(event: OnProgressEvent): void;
74
80
  onLoad?(event: OnLoadEvent): void;
@@ -102,13 +108,18 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
102
108
  */
103
109
  children?: React.ReactNode;
104
110
  }
105
- export interface PreloadResult {
111
+ export interface PreloadSuccess {
112
+ uri: string;
113
+ ok: true;
114
+ width: number;
115
+ height: number;
116
+ }
117
+ export interface PreloadFailure {
106
118
  uri?: string;
107
- ok: boolean;
108
- width?: number;
109
- height?: number;
110
- error?: string;
119
+ ok: false;
120
+ error: string;
111
121
  }
122
+ export type PreloadResult = PreloadSuccess | PreloadFailure;
112
123
  export interface FastImageStaticProperties {
113
124
  resizeMode: typeof resizeMode;
114
125
  priority: typeof priority;
package/dist/index.js CHANGED
@@ -40,11 +40,17 @@ function tintColorFromStyle(style) {
40
40
  }
41
41
  return style && typeof style === "object" ? style.tintColor : void 0;
42
42
  }
43
+ function loopCount(loop) {
44
+ if (loop === void 0) return -1;
45
+ if (loop === true) return 0;
46
+ if (loop === false) return 1;
47
+ return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
48
+ }
43
49
  function withoutCache(source) {
44
50
  const { cache: _cache, ...rest } = source || {};
45
51
  return rest;
46
52
  }
47
- function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", forwardedRef, onLayout, pointerEvents, ...viewProps }) {
53
+ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
48
54
  const { onClick, ...props } = viewProps;
49
55
  const wrapperProps = {
50
56
  onLayout,
@@ -85,6 +91,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
85
91
  }, /* @__PURE__ */ React.createElement(FastImageView, {
86
92
  ...imageProps,
87
93
  tintColor: resolvedTintColor,
94
+ loopCount: loopCount(loop),
88
95
  style: StyleSheet.absoluteFill,
89
96
  source: resolvedSource,
90
97
  defaultSource: resolvedDefaultSource,
@@ -102,18 +109,29 @@ const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.crea
102
109
  ...props
103
110
  }));
104
111
  FastImageComponent.displayName = "FastImage";
112
+ const noResult = {
113
+ ok: false,
114
+ error: "No result"
115
+ };
105
116
  const FastImage = FastImageComponent;
106
117
  FastImage.resizeMode = resizeMode;
107
118
  FastImage.cacheControl = cacheControl;
108
119
  FastImage.priority = priority;
109
120
  FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.preload(sources.map((s) => s || {}))).then((results) => sources.map((source, i) => {
110
121
  var _results$i;
111
- return {
112
- uri: source ? source.uri : void 0,
113
- ...(_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : {
114
- ok: false,
115
- error: "No result"
116
- }
122
+ const uri = source ? source.uri : void 0;
123
+ const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
124
+ if (!result.ok) return {
125
+ ...result,
126
+ uri
127
+ };
128
+ return typeof uri === "string" ? {
129
+ ...result,
130
+ uri
131
+ } : {
132
+ ok: false,
133
+ error: "Invalid source: no uri",
134
+ uri
117
135
  };
118
136
  }));
119
137
  FastImage.clearMemoryCache = () => NativeModules.FastImageView.clearMemoryCache();
@@ -43,14 +43,21 @@ export type ResizeModes = $Values<ResizeMode>
43
43
  export type Priorities = $Values<Priority>
44
44
  export type CacheControls = $Values<CacheControl>
45
45
 
46
- export type PreloadResult = {|
46
+ export type PreloadSuccess = {|
47
+ uri: string,
48
+ ok: true,
49
+ width: number,
50
+ height: number,
51
+ |}
52
+
53
+ export type PreloadFailure = {|
47
54
  uri?: string,
48
- ok: boolean,
49
- width?: number,
50
- height?: number,
51
- error?: string,
55
+ ok: false,
56
+ error: string,
52
57
  |}
53
58
 
59
+ export type PreloadResult = PreloadSuccess | PreloadFailure
60
+
54
61
  export type PreloadFn = (
55
62
  sources: Array<FastImageSource>,
56
63
  ) => Promise<Array<PreloadResult>>
@@ -76,6 +83,7 @@ export type FastImageProps = $ReadOnly<{|
76
83
  resizeMode?: ?ResizeModes,
77
84
  fallback?: ?boolean,
78
85
  recyclingKey?: ?string,
86
+ loop?: ?(boolean | number),
79
87
  testID?: ?string,
80
88
  |}>
81
89
 
@@ -22,6 +22,9 @@
22
22
  // When it changes, the next image doesn't replace the current one: the view
23
23
  // clears first (for views reused for other content, like list rows).
24
24
  @property (nonatomic, copy) NSString *recyclingKey;
25
+ // How many times animated images play: -1 for the file's own loop count (the
26
+ // `loop` prop not set), 0 for forever, or a number of times.
27
+ @property (nonatomic, assign) NSInteger loopCount;
25
28
 
26
29
  @end
27
30
 
@@ -47,9 +47,31 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
47
47
  self = [super init];
48
48
  self.resizeMode = RCTResizeModeCover;
49
49
  self.clipsToBounds = YES;
50
+ _loopCount = -1;
50
51
  return self;
51
52
  }
52
53
 
54
+ - (void) setLoopCount: (NSInteger)loopCount {
55
+ if (_loopCount == loopCount) {
56
+ return;
57
+ }
58
+ _loopCount = loopCount;
59
+ // SDAnimatedImageView uses animationRepeatCount (0 is forever) instead of
60
+ // the file's loop count when shouldCustomLoopCount is set, for the next
61
+ // image it shows.
62
+ self.shouldCustomLoopCount = loopCount >= 0;
63
+ if (loopCount >= 0) {
64
+ self.animationRepeatCount = loopCount;
65
+ } else if (self.player && [self.image conformsToProtocol: @protocol(SDAnimatedImage)]) {
66
+ self.player.totalLoopCount = [(id<SDAnimatedImage>) self.image animatedImageLoopCount];
67
+ }
68
+ // Apply it to the image that's showing, and play it again.
69
+ if (self.player) {
70
+ [self.player seekToFrameAtIndex: 0 loopCount: 0];
71
+ [self startAnimating];
72
+ }
73
+ }
74
+
53
75
  - (void) setResizeMode: (RCTResizeMode)resizeMode {
54
76
  if (_resizeMode != resizeMode) {
55
77
  _resizeMode = resizeMode;
@@ -18,6 +18,7 @@ RCT_EXPORT_VIEW_PROPERTY(source, FFFastImageSource)
18
18
  RCT_EXPORT_VIEW_PROPERTY(defaultSource, UIImage)
19
19
  RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode)
20
20
  RCT_EXPORT_VIEW_PROPERTY(recyclingKey, NSString)
21
+ RCT_EXPORT_VIEW_PROPERTY(loopCount, NSInteger)
21
22
  RCT_EXPORT_VIEW_PROPERTY(onFastImageLoadStart, RCTDirectEventBlock)
22
23
  RCT_EXPORT_VIEW_PROPERTY(onFastImageProgress, RCTDirectEventBlock)
23
24
  RCT_EXPORT_VIEW_PROPERTY(onFastImageError, RCTDirectEventBlock)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.8.0",
3
+ "version": "8.9.0",
4
4
  "description": "🚩 FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",
@@ -52,6 +52,7 @@
52
52
  "@types/react-native": "^0.69.5",
53
53
  "@types/react-test-renderer": "^17.0.1",
54
54
  "maestro-runner": "1.1.27",
55
+ "odiff-bin": "^4.5.0",
55
56
  "oxfmt": "0.70.0",
56
57
  "oxlint": "1.85.0",
57
58
  "pretty-format": "^30.5.1",