react-native-fast-image 8.29.0 → 8.30.1

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.
@@ -13,11 +13,14 @@ import android.graphics.Paint;
13
13
  import android.graphics.PorterDuff;
14
14
  import android.graphics.PorterDuffXfermode;
15
15
  import android.graphics.Shader;
16
+ import android.graphics.drawable.AnimatedImageDrawable;
16
17
  import android.graphics.drawable.BitmapDrawable;
17
18
  import android.graphics.drawable.Drawable;
19
+ import android.os.Build;
18
20
 
19
21
  import androidx.annotation.NonNull;
20
22
  import androidx.annotation.Nullable;
23
+ import androidx.annotation.RequiresApi;
21
24
  import androidx.appcompat.widget.AppCompatImageView;
22
25
  import androidx.core.view.ViewCompat;
23
26
 
@@ -29,6 +32,7 @@ import com.bumptech.glide.load.model.GlideUrl;
29
32
  import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
30
33
  import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
31
34
  import com.bumptech.glide.load.resource.gif.GifDrawable;
35
+ import com.bumptech.glide.request.Request;
32
36
  import com.bumptech.glide.request.RequestOptions;
33
37
  import com.bumptech.glide.request.target.DrawableImageViewTarget;
34
38
  import com.bumptech.glide.request.target.SizeReadyCallback;
@@ -179,6 +183,17 @@ class FastImageViewWithUrl extends AppCompatImageView {
179
183
  mOwnGif.startFromFirstFrame();
180
184
  }
181
185
  }
186
+ if (getDrawable() instanceof FastImageAnimatable) {
187
+ ((FastImageAnimatable) getDrawable()).setLoopCount(mLoopCount, !mPaused);
188
+ }
189
+ if (mAnimated != null && getDrawable() == mAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
190
+ AnimatedImageDrawable animated = (AnimatedImageDrawable) mAnimated;
191
+ applyRepeatCount(animated);
192
+ if (!mPaused) {
193
+ animated.stop();
194
+ animated.start();
195
+ }
196
+ }
182
197
  }
183
198
 
184
199
  // Pauses GIFs on the frame they're showing (the view's own animation).
@@ -359,6 +374,18 @@ class FastImageViewWithUrl extends AppCompatImageView {
359
374
  FastImageGif.resume(mOwnGif);
360
375
  }
361
376
  }
377
+ if (getDrawable() instanceof FastImageAnimatable) {
378
+ ((FastImageAnimatable) getDrawable()).setPaused(paused);
379
+ }
380
+ if (mAnimated != null && getDrawable() == mAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
381
+ if (paused) {
382
+ ((AnimatedImageDrawable) mAnimated).stop();
383
+ } else {
384
+ // Plays from its first frame: an AnimatedImageDrawable has no
385
+ // way to resume.
386
+ ((AnimatedImageDrawable) mAnimated).start();
387
+ }
388
+ }
362
389
  }
363
390
 
364
391
  // The GIF this view shows as its own animation (FastImageGif), recycled
@@ -366,12 +393,20 @@ class FastImageViewWithUrl extends AppCompatImageView {
366
393
  @Nullable
367
394
  private GifDrawable mOwnGif;
368
395
 
396
+ // An animated WebP or AVIF this view shows: an AnimatedImageDrawable, which
397
+ // Glide 4.15+ decodes with Android's ImageDecoder (API 28+; AVIF 31+).
398
+ @Nullable
399
+ private Drawable mAnimated;
400
+
369
401
  // Shows each GIF as this view's own animation. Glide's target also starts
370
402
  // and stops it with the Activity, as it does Glide's own GifDrawable.
371
403
  private final class OwnGifTarget extends DrawableImageViewTarget {
372
404
  // Shown instead of the placeholder while loading (see shownCopy).
373
405
  @Nullable
374
406
  private Drawable mMeanwhile;
407
+ // Counts images ready (a thumbnail, then the image), so a copy
408
+ // decoded for one isn't shown over a later one.
409
+ private int mReady = 0;
375
410
 
376
411
  OwnGifTarget(@Nullable Drawable meanwhile) {
377
412
  super(FastImageViewWithUrl.this);
@@ -387,6 +422,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
387
422
 
388
423
  @Override
389
424
  public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
425
+ int ready = ++mReady;
390
426
  if (resource instanceof BitmapDrawable) {
391
427
  if (mPixelated) {
392
428
  // Scaled by the view without filtering: sharp pixels.
@@ -406,9 +442,48 @@ class FastImageViewWithUrl extends AppCompatImageView {
406
442
  return;
407
443
  }
408
444
  }
445
+ if (resource instanceof FastImageAnimatable) {
446
+ // An APNG: each view's is its own (FastImageApngRenderer).
447
+ FastImageAnimatable animatable = (FastImageAnimatable) resource;
448
+ animatable.setLoopCount(mLoopCount, false);
449
+ // The target starts it; paused, it stays on its first frame.
450
+ super.onResourceReady(resource, transition);
451
+ if (mPaused) animatable.setPaused(true);
452
+ return;
453
+ }
454
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && resource instanceof AnimatedImageDrawable) {
455
+ if (!FastImageAnimated.shownElsewhere(resource, FastImageViewWithUrl.this)) {
456
+ showAnimated((AnimatedImageDrawable) resource, transition);
457
+ return;
458
+ }
459
+ // Another view shows it: this one shows its own, once it's
460
+ // decoded (meanwhile, what it showed), if it's still this
461
+ // view's latest image. Failing that, it shares that view's.
462
+ Drawable shared = resource;
463
+ FastImageAnimated.copy(resource, copy -> {
464
+ Request request = getRequest();
465
+ if (ready != mReady || request == null || !request.isComplete()) return;
466
+ showAnimated((AnimatedImageDrawable) (copy != null ? copy : shared), transition);
467
+ });
468
+ return;
469
+ }
409
470
  super.onResourceReady(resource, transition);
410
471
  }
411
472
 
473
+ @RequiresApi(Build.VERSION_CODES.P)
474
+ private void showAnimated(AnimatedImageDrawable animated, @Nullable Transition<? super Drawable> transition) {
475
+ applyRepeatCount(animated);
476
+ // From its first frame, with this view's repeat count: the target
477
+ // starts it, but a drawable another view showed can still be
478
+ // playing (start() does nothing then), and a repeat count set
479
+ // during a play doesn't always apply to that play. Paused, it
480
+ // waits on its first frame.
481
+ animated.stop();
482
+ super.onResourceReady(animated, transition);
483
+ mAnimated = animated;
484
+ if (mPaused) animated.stop();
485
+ }
486
+
412
487
  // The target starts animations again when the Activity does: not a
413
488
  // paused one, and the view's own GIF continues its loop count (start()
414
489
  // would count again).
@@ -431,6 +506,9 @@ class FastImageViewWithUrl extends AppCompatImageView {
431
506
  mOwnGif.recycle();
432
507
  mOwnGif = null;
433
508
  }
509
+ if (mAnimated != null && mAnimated != resource) {
510
+ mAnimated = null;
511
+ }
434
512
  }
435
513
  }
436
514
 
@@ -440,6 +518,21 @@ class FastImageViewWithUrl extends AppCompatImageView {
440
518
  : mLoopCount);
441
519
  }
442
520
 
521
+ // The loop prop for an animated WebP or AVIF: its repeat count is the
522
+ // plays after the first. Not set, it plays as many times as the file says:
523
+ // the repeat count it was decoded with, which an earlier loop (this
524
+ // view's, or another view's that showed the same drawable) may have
525
+ // changed.
526
+ @RequiresApi(Build.VERSION_CODES.P)
527
+ private void applyRepeatCount(AnimatedImageDrawable animated) {
528
+ if (mLoopCount == -1) {
529
+ Integer own = FastImageAnimated.repeatCount(animated);
530
+ if (own != null) animated.setRepeatCount(own);
531
+ return;
532
+ }
533
+ animated.setRepeatCount(mLoopCount == 0 ? AnimatedImageDrawable.REPEAT_INFINITE : mLoopCount - 1);
534
+ }
535
+
443
536
  // resizeMode repeat: the image (a GIF's first frame) repeated from the
444
537
  // top-left at its own size, scaled down to fit the view if it's larger,
445
538
  // as React Native's Image does. The view fills with it (FIT_XY).
package/dist/index.d.ts CHANGED
@@ -133,9 +133,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
133
133
  */
134
134
  recyclingKey?: string | null;
135
135
  /**
136
- * How many times an animated image (GIF, animated WebP) plays: the file's
137
- * own loop count by default, `true` to loop forever, `false` to play once,
138
- * or a number of times. Changing it restarts the animation.
136
+ * How many times an animated image (GIF, animated WebP, APNG, and animated
137
+ * AVIF on Android) plays: the file's own loop count by default, `true` to
138
+ * loop forever, `false` to play once, or a number of times. Changing it
139
+ * restarts the animation.
139
140
  */
140
141
  loop?: boolean | number;
141
142
  /**
@@ -147,9 +148,11 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
147
148
  */
148
149
  imageRendering?: 'auto' | 'smooth' | 'pixelated';
149
150
  /**
150
- * Pauses an animated image (GIF, and animated WebP on iOS) on the frame
151
- * it's showing; `false` plays it again from there. Each image animates on
152
- * its own, so pausing one doesn't pause others showing the same file.
151
+ * Pauses an animated image (GIF, animated WebP, APNG, and animated AVIF on
152
+ * Android) on the frame it's showing; `false` plays it again from there
153
+ * (on Android, an animated WebP or AVIF plays again from its first frame:
154
+ * Android can't resume one). Each image animates on its own, so pausing
155
+ * one doesn't pause others showing the same file.
153
156
  */
154
157
  paused?: boolean;
155
158
  /**
@@ -17,7 +17,7 @@ static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
17
17
 
18
18
  // The photo's own size in pixels, looked up by its identifier (the url after
19
19
  // ph://), or zero. Through the runtime, as the Photos framework is only there
20
- // when the app links it (with SDWebImagePhotosPlugin). Kept once looked up.
20
+ // with SDWebImagePhotosPlugin (on iOS, always). Kept once looked up.
21
21
  static CGSize FFFLookUpPhotoPixelSize(NSURL *url)
22
22
  {
23
23
  Class assetClass = NSClassFromString(@"PHAsset");
@@ -61,11 +61,11 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
61
61
  }
62
62
 
63
63
  // Loads photo library images (ph://<localIdentifier>) with
64
- // SDWebImagePhotosPlugin's loader when the app has it (the pod): FastImage
65
- // finds it at runtime, so it doesn't depend on it or on the Photos framework.
66
- // Its own instance, so the app's use of the plugin is left as it is. Without
67
- // the plugin, or for a source it can't load (assets-library://), the load
68
- // fails with `failure`.
64
+ // SDWebImagePhotosPlugin's loader: a dependency on iOS (the podspec), found
65
+ // at runtime so that tvOS builds without it (an app can add it there). Its
66
+ // own instance, so the app's use of the plugin is left as it is. Without the
67
+ // plugin, or for a source it can't load (assets-library://), the load fails
68
+ // with `failure`.
69
69
  @interface FFFPhotosLoader : NSObject <SDImageLoader>
70
70
  @property (nonatomic, strong) id<SDImageLoader> plugin;
71
71
  @property (nonatomic, copy) NSString *failure;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.29.0",
3
+ "version": "8.30.1",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",
@@ -28,6 +28,8 @@
28
28
  "android/build.gradle",
29
29
  "android/consumer-rules.pro",
30
30
  "android/src/main",
31
+ "android/src/glide-4.15",
32
+ "android/src/glide-older",
31
33
  "ios/FastImage",
32
34
  "ios/FastImage.xcodeproj/project.pbxproj",
33
35
  "ios/FastImage.xcodeproj/xcshareddata",