react-native-fast-image 9.3.7 → 10.0.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.
Files changed (76) hide show
  1. package/README.md +87 -61
  2. package/RNFastImage.podspec +14 -9
  3. package/android/build.gradle +21 -47
  4. package/android/consumer-rules.pro +7 -7
  5. package/android/src/main/AndroidManifest.xml +1 -2
  6. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimatable.java +1 -1
  7. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimated.java +2 -2
  8. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageApng.java +2 -2
  9. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageBlur.java +4 -6
  10. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheControl.java +1 -1
  11. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheLimits.java +4 -1
  12. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheWrite.java +1 -1
  13. package/android/src/main/java/com/fastimage/FastImageCrop.java +92 -0
  14. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageEvent.java +2 -3
  15. package/android/src/main/java/com/fastimage/FastImageEvents.java +74 -0
  16. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGif.java +1 -1
  17. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlide.java +3 -3
  18. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlideModule.java +1 -1
  19. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageKeyedGlideUrl.java +1 -1
  20. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageOkHttpProgressGlideModule.java +36 -9
  21. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageRequestListener.java +6 -1
  22. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSharedDownloads.java +1 -1
  23. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSource.java +1 -1
  24. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSourceSize.java +12 -2
  25. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvg.java +3 -3
  26. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvgRenderer.java +1 -1
  27. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageUrl.java +1 -1
  28. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewConverter.java +7 -8
  29. package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
  30. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +31 -28
  31. package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
  32. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +352 -126
  33. package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageWebGlideUrl.java +1 -1
  34. package/app.plugin.js +2 -7
  35. package/dist/index.cjs.js +276 -157
  36. package/dist/index.cjs.js.flow +33 -17
  37. package/dist/index.cjs.web.js +211 -61
  38. package/dist/index.cjs.web.js.flow +33 -17
  39. package/dist/index.d.ts +83 -58
  40. package/dist/index.js +278 -159
  41. package/dist/index.js.flow +33 -17
  42. package/dist/index.web.js +212 -62
  43. package/dist/index.web.js.flow +33 -17
  44. package/dist/objectFit.d.ts +2 -2
  45. package/dist/objectPosition.d.ts +42 -0
  46. package/dist/specs/FastImageViewNativeComponent.d.ts +48 -0
  47. package/dist/specs/NativeFastImageModule.d.ts +11 -0
  48. package/dist/warnings.d.ts +3 -0
  49. package/ios/FastImage/FFFDownsampledImage.h +4 -8
  50. package/ios/FastImage/FFFDownsampledImage.m +0 -34
  51. package/ios/FastImage/FFFastImageModule.h +8 -0
  52. package/ios/FastImage/{FFFastImageViewManager.m → FFFastImageModule.mm} +70 -59
  53. package/ios/FastImage/FFFastImageView.h +36 -7
  54. package/ios/FastImage/FFFastImageView.m +201 -164
  55. package/ios/FastImage/FFFastImageViewComponentView.h +11 -0
  56. package/ios/FastImage/FFFastImageViewComponentView.mm +325 -0
  57. package/ios/FastImage/RCTConvert+FFFastImage.h +1 -1
  58. package/ios/FastImage/RCTConvert+FFFastImage.m +7 -10
  59. package/package.json +28 -9
  60. package/src/constants.ts +28 -0
  61. package/src/index.js.flow +179 -0
  62. package/src/index.tsx +1103 -0
  63. package/src/index.web.tsx +548 -0
  64. package/src/objectFit.ts +48 -0
  65. package/src/objectPosition.ts +243 -0
  66. package/src/specs/FastImageViewNativeComponent.ts +83 -0
  67. package/src/specs/NativeFastImageModule.ts +35 -0
  68. package/src/warnings.ts +37 -0
  69. package/android/src/main/AndroidManifestNew.xml +0 -3
  70. package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +0 -63
  71. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +0 -49
  72. package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +0 -18
  73. package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +0 -21
  74. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +0 -379
  75. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java +0 -27
  76. package/ios/FastImage/FFFastImageViewManager.h +0 -5
@@ -9,7 +9,6 @@
9
9
 
10
10
  @interface FFFastImageView ()
11
11
 
12
- @property(nonatomic, assign) BOOL hasSentOnLoadStart;
13
12
  @property(nonatomic, assign) BOOL hasCompleted;
14
13
  @property(nonatomic, assign) BOOL hasErrored;
15
14
  // Whether the latest change of props requires the image to be reloaded
@@ -24,8 +23,6 @@
24
23
  @property(nonatomic, assign) NSUInteger imageCount;
25
24
  // The view's size when the image showing was blurred.
26
25
  @property(nonatomic, assign) CGSize blurredForSize;
27
- // The view's size when the image showing was tiled (resizeMode repeat).
28
- @property(nonatomic, assign) CGSize tiledForSize;
29
26
  // The blurred image showing (untinted), so a tint change can re-show it.
30
27
  @property(nonatomic, strong) UIImage* blurredImage;
31
28
  // The image being blurred fades in once it's blurred (transition).
@@ -58,6 +55,13 @@
58
55
  @property(nonatomic, assign) BOOL decodedCover;
59
56
  // Counts loads, so a quiet reload's completion can tell if it's still current.
60
57
  @property(nonatomic, assign) NSUInteger loadCount;
58
+ // The frame the view was laid out at (the box the image fits in), and how far
59
+ // objectPosition moves the view from it (see updatePosition).
60
+ @property(nonatomic, assign) CGRect box;
61
+ @property(nonatomic, assign) CGPoint positionOffset;
62
+ // The view's own mask, which clips it to the box while it's moved, when React
63
+ // Native gives it none (the view doesn't clip: overflow visible).
64
+ @property(nonatomic, strong) CALayer* ownMask;
61
65
 
62
66
  @end
63
67
 
@@ -72,6 +76,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
72
76
  if (self != [FFFastImageView class]) {
73
77
  return;
74
78
  }
79
+ FFFApplyCacheLimits();
75
80
  [[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidEnterBackgroundNotification
76
81
  object: nil
77
82
  queue: [NSOperationQueue mainQueue]
@@ -82,8 +87,11 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
82
87
 
83
88
  - (id) init {
84
89
  self = [super init];
85
- self.resizeMode = RCTResizeModeCover;
90
+ // objectFit cover (FFFObjectFitCover, 0).
91
+ self.contentMode = UIViewContentModeScaleAspectFill;
86
92
  self.clipsToBounds = YES;
93
+ _objectPositionX = 0.5;
94
+ _objectPositionY = 0.5;
87
95
  // The tint (tintColor) stays as it is while an alert or sheet is shown,
88
96
  // rather than dimming like the system's controls.
89
97
  self.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
@@ -201,10 +209,18 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
201
209
  [self.layer addAnimation: fadeIn forKey: @"FFFFadeIn"];
202
210
  return;
203
211
  }
204
- [UIView transitionWithView: self
212
+ // On the view it's in, which doesn't move, with the change itself not
213
+ // animated: objectPosition moves this view for the new image
214
+ // (updatePosition), which in a transition of this view was animated while
215
+ // its mask moved at once. The old image stays where it was while the new
216
+ // one fades in where it goes.
217
+ UIView* container = self.superview ?: self;
218
+ [UIView transitionWithView: container
205
219
  duration: self.transitionDuration / 1000
206
220
  options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction
207
- animations: change
221
+ animations: ^{
222
+ [UIView performWithoutAnimation: change];
223
+ }
208
224
  completion: nil];
209
225
  }
210
226
 
@@ -224,50 +240,157 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
224
240
  }
225
241
  }
226
242
 
227
- - (void) setResizeMode: (FFFResizeMode)resizeMode {
228
- if (_resizeMode != resizeMode) {
229
- BOOL repeated = _resizeMode == RCTResizeModeRepeat;
230
- _resizeMode = resizeMode;
243
+ - (void) setObjectFit: (FFFObjectFit)objectFit {
244
+ if (_objectFit != objectFit) {
245
+ _objectFit = objectFit;
231
246
  [self updateContentMode];
232
- // The blur is made for the size the image is shown at, and repeat
233
- // shows the image tiled: show it again.
234
- UIImage* image = self.originalImage ?: super.image;
235
- if ([self blurs: self.originalImage] || ((repeated || resizeMode == RCTResizeModeRepeat) && image)) {
236
- [self setImage: image];
247
+ // The blur is made for the size the image is shown at: show it again.
248
+ if ([self blurs: self.originalImage]) {
249
+ [self setImage: self.originalImage];
237
250
  }
238
251
  }
239
252
  }
240
253
 
241
- // The content mode for resizeMode. `center` scales an image larger than the
242
- // view down to fit, as React Native's Image and Android do (#866); only a
243
- // smaller one is shown at its own size. UIViewContentModeCenter alone showed
244
- // large images at full size, cropped. So it depends on the image and the
245
- // view's size. `none` is the image at its own size, centered, whatever its
246
- // size (cropped by the view).
254
+ // The content mode for objectFit. `scale-down` scales an image larger than
255
+ // the view down to fit, and shows a smaller one at its own size
256
+ // (UIViewContentModeCenter alone would show large images at full size,
257
+ // cropped), so it depends on the image and the view's size. `none` is the
258
+ // image at its own size, centered, whatever its size (cropped by the view).
247
259
  - (void) updateContentMode {
248
- UIViewContentMode contentMode = (UIViewContentMode) _resizeMode;
249
- if (_resizeMode == FFFResizeModeNone) {
250
- contentMode = UIViewContentModeCenter;
251
- } else if (_resizeMode == RCTResizeModeRepeat) {
252
- // The tiled image (see tiledImage:) fills the view.
253
- contentMode = UIViewContentModeScaleToFill;
254
- } else if (_resizeMode == RCTResizeModeCenter) {
255
- CGSize imageSize = super.image.size;
256
- CGSize viewSize = self.bounds.size;
257
- if (imageSize.width > viewSize.width || imageSize.height > viewSize.height) {
260
+ UIViewContentMode contentMode;
261
+ switch (_objectFit) {
262
+ case FFFObjectFitContain:
258
263
  contentMode = UIViewContentModeScaleAspectFit;
264
+ break;
265
+ case FFFObjectFitFill:
266
+ contentMode = UIViewContentModeScaleToFill;
267
+ break;
268
+ case FFFObjectFitNone:
269
+ contentMode = UIViewContentModeCenter;
270
+ break;
271
+ case FFFObjectFitScaleDown: {
272
+ CGSize imageSize = super.image.size;
273
+ CGSize viewSize = self.bounds.size;
274
+ contentMode = imageSize.width > viewSize.width || imageSize.height > viewSize.height
275
+ ? UIViewContentModeScaleAspectFit
276
+ : UIViewContentModeCenter;
277
+ break;
259
278
  }
279
+ case FFFObjectFitCover:
280
+ default:
281
+ contentMode = UIViewContentModeScaleAspectFill;
282
+ break;
260
283
  }
261
284
  if (self.contentMode != contentMode) {
262
285
  self.contentMode = contentMode;
263
286
  }
287
+ [self updatePosition];
288
+ }
289
+
290
+ - (void) setObjectPositionX: (CGFloat)objectPositionX {
291
+ _objectPositionX = objectPositionX;
292
+ [self updatePosition];
293
+ }
294
+
295
+ - (void) setObjectPositionXOffset: (CGFloat)objectPositionXOffset {
296
+ _objectPositionXOffset = objectPositionXOffset;
297
+ [self updatePosition];
298
+ }
299
+
300
+ - (void) setObjectPositionY: (CGFloat)objectPositionY {
301
+ _objectPositionY = objectPositionY;
302
+ [self updatePosition];
303
+ }
304
+
305
+ - (void) setObjectPositionYOffset: (CGFloat)objectPositionYOffset {
306
+ _objectPositionYOffset = objectPositionYOffset;
307
+ [self updatePosition];
308
+ }
309
+
310
+ // The frame React Native lays the view out at: the box the image fits in. The
311
+ // view itself is moved from it by objectPosition (see updatePosition).
312
+ - (void) setFrame: (CGRect)frame {
313
+ self.box = frame;
314
+ [super setFrame: CGRectOffset(frame, self.positionOffset.x, self.positionOffset.y)];
315
+ }
316
+
317
+ // objectPosition: where the image is in the view, as CSS places it. The
318
+ // content mode centers the image, so the view moves by the difference,
319
+ // keeping its size: React Native's mask for its rounded corners (made for its
320
+ // bounds) only has to move back (see updateMask). Not moved, as most images
321
+ // are, the view clips to its bounds as before.
322
+ - (void) updatePosition {
323
+ CGPoint offset = [self imageOffset];
324
+ if (!CGPointEqualToPoint(offset, self.positionOffset)) {
325
+ self.positionOffset = offset;
326
+ [super setFrame: CGRectOffset(self.box, offset.x, offset.y)];
327
+ }
328
+ [self updateMask];
329
+ }
330
+
331
+ // How far objectPosition moves the image from the center: on each axis its
332
+ // left or top edge is at (view - image) * fraction + offset, where the
333
+ // content mode centers it at (view - image) / 2. Nothing for fill, which
334
+ // fills the view.
335
+ - (CGPoint) imageOffset {
336
+ CGSize view = self.box.size;
337
+ CGSize size = super.image.size;
338
+ if (size.width <= 0 || size.height <= 0 || view.width <= 0 || view.height <= 0) {
339
+ return CGPointZero;
340
+ }
341
+ CGFloat scale;
342
+ switch (self.contentMode) {
343
+ case UIViewContentModeScaleAspectFill:
344
+ scale = MAX(view.width / size.width, view.height / size.height);
345
+ break;
346
+ case UIViewContentModeScaleAspectFit:
347
+ scale = MIN(view.width / size.width, view.height / size.height);
348
+ break;
349
+ case UIViewContentModeCenter:
350
+ scale = 1;
351
+ break;
352
+ default:
353
+ return CGPointZero;
354
+ }
355
+ CGFloat x = (view.width - size.width * scale) * (_objectPositionX - 0.5) + _objectPositionXOffset;
356
+ CGFloat y = (view.height - size.height * scale) * (_objectPositionY - 0.5) + _objectPositionYOffset;
357
+ return CGPointMake(isfinite(x) ? x : 0, isfinite(y) ? y : 0);
358
+ }
359
+
360
+ - (void) updateMask {
361
+ CGPoint offset = self.positionOffset;
362
+ BOOL moved = offset.x != 0 || offset.y != 0;
363
+ // Moved, the view's bounds aren't the box: the mask clips it.
364
+ self.clipsToBounds = !moved;
365
+ CALayer* mask = self.layer.mask;
366
+ if (!moved && (!mask || CGPointEqualToPoint(mask.frame.origin, CGPointZero)) && mask != self.ownMask) {
367
+ return;
368
+ }
369
+ [CATransaction begin];
370
+ [CATransaction setDisableActions: YES];
371
+ if (mask && mask != self.ownMask) {
372
+ // React Native's (the box with its rounded corners, in the view's
373
+ // bounds): back on the box.
374
+ CGRect frame = mask.frame;
375
+ frame.origin = CGPointMake(-offset.x, -offset.y);
376
+ mask.frame = frame;
377
+ } else if (moved) {
378
+ if (!self.ownMask) {
379
+ self.ownMask = [CALayer layer];
380
+ self.ownMask.backgroundColor = [UIColor blackColor].CGColor;
381
+ }
382
+ self.ownMask.frame = CGRectMake(-offset.x, -offset.y, self.box.size.width, self.box.size.height);
383
+ self.layer.mask = self.ownMask;
384
+ } else if (mask) {
385
+ self.layer.mask = nil;
386
+ }
387
+ [CATransaction commit];
264
388
  }
265
389
 
266
390
  - (void) layoutSubviews {
267
391
  [super layoutSubviews];
268
392
  [self updateContentMode];
269
393
  [self blurAgainIfResized];
270
- [self tileAgainIfResized];
271
394
  if (self.waitsForSize) {
272
395
  // An image in the memory cache shows in this frame. Others start
273
396
  // loading from didSetProps's block, after layout, in the order the
@@ -282,40 +405,6 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
282
405
  }
283
406
  }
284
407
 
285
- - (void) setOnFastImageLoadEnd: (RCTDirectEventBlock)onFastImageLoadEnd {
286
- _onFastImageLoadEnd = onFastImageLoadEnd;
287
- if (self.hasCompleted && _onFastImageLoadEnd) {
288
- _onFastImageLoadEnd([self loadEndEvent: YES]);
289
- }
290
- }
291
-
292
- - (void) setOnFastImageLoad: (RCTDirectEventBlock)onFastImageLoad {
293
- _onFastImageLoad = onFastImageLoad;
294
- if (self.hasCompleted && _onFastImageLoad) {
295
- _onFastImageLoad(self.onLoadEvent);
296
- }
297
- }
298
-
299
- - (void) setOnFastImageError: (RCTDirectEventBlock)onFastImageError {
300
- _onFastImageError = onFastImageError;
301
- if (self.hasErrored && _onFastImageError) {
302
- _onFastImageError(self.onErrorEvent);
303
- }
304
- }
305
-
306
- - (void) setOnFastImageLoadStart: (RCTDirectEventBlock)onFastImageLoadStart {
307
- // Send it for a load that has already started. When a reload is pending
308
- // (e.g. source set in the same update), reloadImage sends it.
309
- if (_source && !_needsReload && !self.hasSentOnLoadStart && onFastImageLoadStart) {
310
- _onFastImageLoadStart = onFastImageLoadStart;
311
- onFastImageLoadStart(@{});
312
- self.hasSentOnLoadStart = YES;
313
- } else {
314
- _onFastImageLoadStart = onFastImageLoadStart;
315
- self.hasSentOnLoadStart = NO;
316
- }
317
- }
318
-
319
408
  - (void) setImageColor: (UIColor*)imageColor {
320
409
  _imageColor = imageColor;
321
410
  // A blurred image is a still image: show it with the new tint, without
@@ -336,12 +425,6 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
336
425
  }
337
426
  }
338
427
 
339
- // Whether SDAnimatedImageView can tint the frames of an animated image as
340
- // they're decoded (animationTransformer, SDWebImage 5.20+).
341
- - (BOOL) tintsFrames {
342
- return [self respondsToSelector: @selector(setAnimationTransformer:)];
343
- }
344
-
345
428
  - (void) setImage: (UIImage*)image {
346
429
  self.imageCount++;
347
430
  if ([self blurs: image]) {
@@ -350,8 +433,8 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
350
433
  [self blurImage: image];
351
434
  return;
352
435
  }
353
- // Kept to tint, or tile (resizeMode repeat), it again.
354
- self.originalImage = image && (self.imageColor || _resizeMode == RCTResizeModeRepeat) ? image : nil;
436
+ // Kept to tint it again.
437
+ self.originalImage = image && self.imageColor ? image : nil;
355
438
  self.blurredImage = nil;
356
439
  self.blursOverNothing = NO;
357
440
  // The blur was removed before it was ready: the fade it was waiting for
@@ -389,17 +472,13 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
389
472
  // template image in the view's tintColor, so no tinted copy of the image is
390
473
  // made. SDAnimatedImageView draws the frames of an animated image itself,
391
474
  // without the tint, so each frame is tinted as it's decoded (source-in, as the
392
- // template is), or, before SDWebImage 5.20, the first frame shows.
475
+ // template is).
393
476
  - (void) showImage: (UIImage*)image {
394
477
  UIColor* tint = image ? self.imageColor : nil;
395
- // resizeMode repeat tiles an animated image's first frame (tiledImage:).
396
- BOOL repeats = image && _resizeMode == RCTResizeModeRepeat;
397
- BOOL animated = !repeats && [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
478
+ BOOL animated = [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
398
479
  self.tintColor = tint;
399
- if ([self tintsFrames]) {
400
- [self setValue: tint && animated ? [SDImageTintTransformer transformerWithColor: tint] : nil forKey: @"animationTransformer"];
401
- }
402
- if (tint && animated && [self tintsFrames]) {
480
+ self.animationTransformer = tint && animated ? [SDImageTintTransformer transformerWithColor: tint] : nil;
481
+ if (tint && animated) {
403
482
  BOOL changed = super.image != image;
404
483
  super.image = image;
405
484
  // Shows the first frame tinted now, also while paused (the player
@@ -410,69 +489,18 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
410
489
  }
411
490
  } else {
412
491
  if (tint) {
413
- if (animated) {
414
- image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
415
- } else if (!image.CGImage && !repeats) {
492
+ if (!image.CGImage) {
416
493
  // A vector image (an SVG), which UIKit doesn't draw as a
417
- // template: drawn into a bitmap at the size it's shown at
418
- // (tiledImage: draws one for repeat).
494
+ // template: drawn into a bitmap at the size it's shown at.
419
495
  image = [self rasterizedImage: image];
420
496
  }
421
497
  image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
422
498
  }
423
- if (repeats) {
424
- image = [self tiledImage: image];
425
- self.tiledForSize = self.bounds.size;
426
- }
427
499
  super.image = image;
428
500
  }
429
501
  [self updateContentMode];
430
502
  }
431
503
 
432
- // resizeMode repeat: the image repeated from the top-left at its own size in
433
- // pixels (a scaled image, e.g. a bundled @3x one, at its size in points), an
434
- // animated image's first frame, scaled down to fit the view if it's larger,
435
- // as React Native's Image does (and Android here).
436
- - (UIImage*) tiledImage: (UIImage*)image {
437
- if (!image.CGImage && image.size.width > 0 && image.size.height > 0) {
438
- // A vector image (an SVG): drawn at its own size in pixels (its width
439
- // and height), as Android tiles it and as any other image's size is.
440
- UIImage* vector = image;
441
- UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat preferredFormat];
442
- format.scale = 1;
443
- format.opaque = NO;
444
- UIGraphicsImageRenderer* renderer = [[UIGraphicsImageRenderer alloc] initWithSize: vector.size format: format];
445
- image = [[renderer imageWithActions: ^(UIGraphicsImageRendererContext* context) {
446
- [vector drawInRect: CGRectMake(0, 0, vector.size.width, vector.size.height)];
447
- }] imageWithRenderingMode: vector.renderingMode];
448
- }
449
- CGImageRef cgImage = image.CGImage;
450
- if (!cgImage) {
451
- return image;
452
- }
453
- CGFloat scale = image.scale > 1 ? image.scale : (self.window.screen.scale ?: [UIScreen mainScreen].scale);
454
- CGSize size = CGSizeMake(image.size.width * image.scale / scale, image.size.height * image.scale / scale);
455
- CGSize view = self.bounds.size;
456
- if (view.width > 0 && view.height > 0 && (size.width > view.width || size.height > view.height)) {
457
- scale *= MAX(size.width / view.width, size.height / view.height);
458
- }
459
- UIImage* still = [[UIImage imageWithCGImage: cgImage scale: scale orientation: image.imageOrientation]
460
- imageWithRenderingMode: image.renderingMode];
461
- return [still resizableImageWithCapInsets: UIEdgeInsetsZero resizingMode: UIImageResizingModeTile];
462
- }
463
-
464
- // The view's size changed since the image was tiled, which scales an image
465
- // larger than the view down to fit it: tile it again.
466
- - (void) tileAgainIfResized {
467
- if (_resizeMode != RCTResizeModeRepeat || CGSizeEqualToSize(self.bounds.size, self.tiledForSize)) {
468
- return;
469
- }
470
- UIImage* image = [self blurs: self.originalImage] ? self.blurredImage : self.originalImage;
471
- if (image) {
472
- [self showImage: image];
473
- }
474
- }
475
-
476
504
  - (void) setBlurRadius: (CGFloat)blurRadius {
477
505
  if (_blurRadius == blurRadius) {
478
506
  return;
@@ -541,7 +569,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
541
569
  }
542
570
 
543
571
  // Points on screen per point of the image, for the view's size and
544
- // resizeMode, or 1 before the view has a size.
572
+ // objectFit, or 1 before the view has a size.
545
573
  - (CGFloat) shownScale: (UIImage*)image {
546
574
  CGSize size = image.size;
547
575
  CGSize view = self.bounds.size;
@@ -550,14 +578,14 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
550
578
  }
551
579
  CGFloat x = view.width / size.width;
552
580
  CGFloat y = view.height / size.height;
553
- switch (_resizeMode) {
554
- case RCTResizeModeCover:
581
+ switch (_objectFit) {
582
+ case FFFObjectFitCover:
555
583
  return MAX(x, y);
556
- case RCTResizeModeContain:
584
+ case FFFObjectFitContain:
557
585
  return MIN(x, y);
558
- case RCTResizeModeStretch:
586
+ case FFFObjectFitFill:
559
587
  return sqrt(x * y);
560
- case RCTResizeModeCenter:
588
+ case FFFObjectFitScaleDown:
561
589
  return MIN(MIN(x, y), 1);
562
590
  default:
563
591
  return 1;
@@ -607,7 +635,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
607
635
  uint32_t boxSize = factor == 1 ? (uint32_t) box | 1 : (uint32_t) round((box / factor - 1) / 2) * 2 + 1;
608
636
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
609
637
  CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace,
610
- kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
638
+ (CGBitmapInfo)kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
611
639
  CGColorSpaceRelease(colorSpace);
612
640
  if (!context) {
613
641
  return image;
@@ -765,6 +793,7 @@ NSString *FFFErrorMessage(NSError *error)
765
793
  // source is the same, nor a fade from it.
766
794
  self.showsLoadedImage = NO;
767
795
  [self.layer removeAllAnimations];
796
+ [self.superview.layer removeAnimationForKey: kCATransition];
768
797
  _needsReload = YES;
769
798
  }
770
799
  }
@@ -776,7 +805,7 @@ NSString *FFFErrorMessage(NSError *error)
776
805
  }
777
806
  }
778
807
 
779
- - (void) didSetProps: (NSArray<NSString*>*)changedProps {
808
+ - (void) didSetProps {
780
809
  if (_needsReload) {
781
810
  // With downsample on, the image is decoded for the view's size, and
782
811
  // with several sources one is picked for it, so a view that hasn't
@@ -803,16 +832,15 @@ NSString *FFFErrorMessage(NSError *error)
803
832
  }
804
833
  }
805
834
 
806
- // Whether images are decoded at about the view's size (downsample, from
807
- // SDWebImage 5.19, and always for photo library images, which are large and
808
- // usually shown small: Photos makes them at the size asked for, on any
809
- // version). Not for `repeat`, which tiles the image at its own size, or
810
- // `none`, which shows it at its own size.
835
+ // Whether images are decoded at about the view's size (downsample, and always
836
+ // for photo library images, which are large and usually shown small: Photos
837
+ // makes them at the size asked for). Not for `none`, which shows it at its own
838
+ // size.
811
839
  - (BOOL) downsamples {
812
- if (_resizeMode == RCTResizeModeRepeat || _resizeMode == FFFResizeModeNone) {
840
+ if (_objectFit == FFFObjectFitNone) {
813
841
  return NO;
814
842
  }
815
- return [_source isPhotoLibrary] || (_downsample && [FFFDownsampledImage isSupported]);
843
+ return [_source isPhotoLibrary] || _downsample;
816
844
  }
817
845
 
818
846
  // Whether the view has been laid out with an area. One that's 0 wide or tall
@@ -833,7 +861,7 @@ NSString *FFFErrorMessage(NSError *error)
833
861
 
834
862
  // Whether the image has to cover the box, rather than fit in it.
835
863
  - (BOOL) decodeCovers {
836
- return _resizeMode == RCTResizeModeCover || _resizeMode == RCTResizeModeStretch;
864
+ return _objectFit == FFFObjectFitCover || _objectFit == FFFObjectFitFill;
837
865
  }
838
866
 
839
867
  // The view grew (by more than a fifth, as React Native's Image reloads), or
@@ -884,7 +912,7 @@ NSString *FFFErrorMessage(NSError *error)
884
912
  // source doesn't start one), and ignore what it still sends (SDWebImage
885
913
  // completes a cancelled load with an error; see downloadImage:).
886
914
  self.loadCount++;
887
- [self sd_cancelCurrentImageLoad];
915
+ [self sd_cancelLatestImageLoad];
888
916
 
889
917
  if (_source) {
890
918
  // Load base64 images.
@@ -892,9 +920,6 @@ NSString *FFFErrorMessage(NSError *error)
892
920
  if (url && [url hasPrefix: @"data:image"]) {
893
921
  if (self.onFastImageLoadStart) {
894
922
  self.onFastImageLoadStart(@{});
895
- self.hasSentOnLoadStart = YES;
896
- } else {
897
- self.hasSentOnLoadStart = NO;
898
923
  }
899
924
  // Use SDWebImage API to support external format like WebP images
900
925
  UIImage* image = [UIImage sd_imageWithData: [NSData dataWithContentsOfURL: _source.url]];
@@ -912,7 +937,8 @@ NSString *FFFErrorMessage(NSError *error)
912
937
  if (self.onFastImageProgress) {
913
938
  self.onFastImageProgress(@{
914
939
  @"loaded": @(1),
915
- @"total": @(1)
940
+ @"total": @(1),
941
+ @"progress": @(1)
916
942
  });
917
943
  }
918
944
  self.hasCompleted = YES;
@@ -937,9 +963,6 @@ NSString *FFFErrorMessage(NSError *error)
937
963
 
938
964
  if (self.onFastImageLoadStart) {
939
965
  self.onFastImageLoadStart(@{});
940
- self.hasSentOnLoadStart = YES;
941
- } else {
942
- self.hasSentOnLoadStart = NO;
943
966
  }
944
967
  self.hasCompleted = NO;
945
968
  self.hasErrored = NO;
@@ -982,8 +1005,7 @@ NSString *FFFErrorMessage(NSError *error)
982
1005
  // Whether the view loads as it's laid out, so its image shows in this frame:
983
1006
  // one in the memory cache, where SDWebImage finds it as the load starts, or
984
1007
  // one that isn't downloaded (no source, which shows defaultSource, or a data
985
- // uri). Also when that can't be told (an app's own image cache, or an
986
- // SDWebImage without cacheKeyForURL:context:), as before.
1008
+ // uri). Also when that can't be told (an app's own image cache), as before.
987
1009
  - (BOOL) loadsDuringLayout {
988
1010
  FFFastImageSource* source = [self picksSource] ? [self sourceForSize] : _source;
989
1011
  if (!source.url || [source.url.scheme isEqualToString: @"data"]) {
@@ -993,7 +1015,7 @@ NSString *FFFErrorMessage(NSError *error)
993
1015
  return NO;
994
1016
  }
995
1017
  SDWebImageManager* manager = [SDWebImageManager sharedManager];
996
- if (![manager.imageCache isKindOfClass: [SDImageCache class]] || ![manager respondsToSelector: @selector(cacheKeyForURL:context:)]) {
1018
+ if (![manager.imageCache isKindOfClass: [SDImageCache class]]) {
997
1019
  return YES;
998
1020
  }
999
1021
  CGSize box = [self decodeBox];
@@ -1062,7 +1084,8 @@ NSString *FFFErrorMessage(NSError *error)
1062
1084
  if (onProgress) {
1063
1085
  onProgress(@{
1064
1086
  @"loaded": @(receivedSize),
1065
- @"total": @(expectedSize)
1087
+ @"total": @(expectedSize),
1088
+ @"progress": @(MAX(0.0, MIN(1.0, (double)receivedSize / expectedSize)))
1066
1089
  });
1067
1090
  }
1068
1091
  });
@@ -1190,9 +1213,23 @@ NSString *FFFErrorMessage(NSError *error)
1190
1213
  }
1191
1214
  }
1192
1215
 
1216
+ - (void) stop {
1217
+ self.onFastImageLoadStart = nil;
1218
+ self.onFastImageProgress = nil;
1219
+ self.onFastImageLoad = nil;
1220
+ self.onFastImageError = nil;
1221
+ self.onFastImageLoadEnd = nil;
1222
+ // As a new load does (see reloadImage): a completion still on its way is
1223
+ // ignored, and a load waiting for the view's size doesn't start.
1224
+ self.loadCount++;
1225
+ self.waitsForSize = NO;
1226
+ [self stopWaitingForActive];
1227
+ [self sd_cancelLatestImageLoad];
1228
+ }
1229
+
1193
1230
  - (void) dealloc {
1194
1231
  [self stopWaitingForActive];
1195
- [self sd_cancelCurrentImageLoad];
1232
+ [self sd_cancelLatestImageLoad];
1196
1233
  }
1197
1234
 
1198
1235
  @end
@@ -0,0 +1,11 @@
1
+ #import <React/RCTViewComponentView.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ // The FastImageView component (src/specs) on the New Architecture: shows an
6
+ // FFFastImageView, and passes it the component's props and its events back.
7
+ @interface FFFastImageViewComponentView : RCTViewComponentView
8
+
9
+ @end
10
+
11
+ NS_ASSUME_NONNULL_END