react-native-gleam 1.0.5 → 1.0.6

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
@@ -3,7 +3,7 @@
3
3
  Native-powered shimmer loading effect for React Native. Built with pure native animations — no reanimated, no SVG, zero dependencies.
4
4
 
5
5
  - **iOS**: `CAGradientLayer` + `CADisplayLink`
6
- - **Android**: `Choreographer` + `LinearGradient`
6
+ - **Android**: `Choreographer` + `LinearGradient` + `ValueAnimator`
7
7
  - **Fabric only** (New Architecture)
8
8
 
9
9
  https://github.com/user-attachments/assets/70eb886c-f3e2-4611-8ecc-0b03227267d0
@@ -142,16 +142,14 @@ class GleamView(context: Context) : ReactViewGroup(context) {
142
142
  if (loading) {
143
143
  registerClock()
144
144
  } else {
145
- contentOpacity = 1f
146
- shimmerOpacity = 0f
145
+ forceLoadedState()
147
146
  }
148
147
  } else {
149
148
  // Re-attachment: restore correct state
150
149
  if (loading) {
151
150
  registerClock()
152
151
  } else if (!isTransitioning) {
153
- contentOpacity = 1f
154
- shimmerOpacity = 0f
152
+ forceLoadedState()
155
153
  }
156
154
  }
157
155
  }
@@ -181,9 +179,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
181
179
  registerClock()
182
180
  invalidate()
183
181
  } else if (!isTransitioning) {
184
- contentOpacity = 1f
185
- shimmerOpacity = 0f
186
- invalidate()
182
+ forceLoadedState()
187
183
  }
188
184
  }
189
185
  }
@@ -386,22 +382,28 @@ class GleamView(context: Context) : ReactViewGroup(context) {
386
382
  start()
387
383
  }
388
384
  } else {
389
- unregisterClock()
390
- contentOpacity = 1f
391
- shimmerOpacity = 0f
392
- invalidate()
385
+ forceLoadedState()
393
386
  emitTransitionEnd(true)
394
387
  }
395
388
  }
396
389
  }
397
390
 
398
- private fun finishTransition() {
399
- if (!isTransitioning) return
391
+ private fun forceLoadedState() {
400
392
  isTransitioning = false
393
+ transitionGeneration++
394
+ transitionAnimator?.removeAllListeners()
395
+ transitionAnimator?.cancel()
396
+ transitionAnimator = null
401
397
  unregisterClock()
398
+ transitionProgress = 1f
402
399
  contentOpacity = 1f
403
400
  shimmerOpacity = 0f
404
401
  invalidate()
402
+ }
403
+
404
+ private fun finishTransition() {
405
+ if (!isTransitioning) return
406
+ forceLoadedState()
405
407
  emitTransitionEnd(true)
406
408
  }
407
409
 
package/ios/GleamView.mm CHANGED
@@ -172,6 +172,8 @@ static void _unregisterView(GleamView *view) {
172
172
  [super mountChildComponentView:childComponentView index:index];
173
173
  if (_loading || _isTransitioning) {
174
174
  childComponentView.alpha = _contentAlpha;
175
+ } else {
176
+ childComponentView.alpha = 1.0;
175
177
  }
176
178
  }
177
179
 
@@ -188,10 +190,7 @@ static void _unregisterView(GleamView *view) {
188
190
  }
189
191
  [self _registerClock];
190
192
  } else if (!_isTransitioning) {
191
- [self _setChildrenAlphaIfNeeded:1.0];
192
- _shimmerLayer.opacity = 0.0;
193
- [_shimmerLayer removeFromSuperlayer];
194
- [self _unregisterClock];
193
+ [self _forceLoadedState];
195
194
  }
196
195
  }
197
196
 
@@ -210,10 +209,7 @@ static void _unregisterView(GleamView *view) {
210
209
  } else if (!_isTransitioning) {
211
210
  // _isTransitioning=YES means ticks are actively driving it to
212
211
  // completion (we no longer bail on !self.window) — let it finish.
213
- [self _setChildrenAlphaIfNeeded:1.0];
214
- _shimmerLayer.opacity = 0.0;
215
- [_shimmerLayer removeFromSuperlayer];
216
- [self _unregisterClock];
212
+ [self _forceLoadedState];
217
213
  }
218
214
  }
219
215
 
@@ -303,63 +299,57 @@ static void _unregisterView(GleamView *view) {
303
299
 
304
300
  - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
305
301
  {
306
- const auto &oldViewProps = *std::static_pointer_cast<GleamViewProps const>(_props);
307
302
  const auto &newViewProps = *std::static_pointer_cast<GleamViewProps const>(props);
308
303
 
309
- if (oldViewProps.speed != newViewProps.speed) {
310
- double s = newViewProps.speed / 1000.0;
311
- _speed = (isnan(s) || isinf(s) || s <= 0) ? 1.0 : fmax(s, 0.001);
312
- }
313
-
314
- if (oldViewProps.delay != newViewProps.delay) {
315
- double d = newViewProps.delay / 1000.0;
316
- _delay = (isnan(d) || isinf(d)) ? 0.0 : d;
317
- }
318
-
319
- if (oldViewProps.transitionDuration != newViewProps.transitionDuration) {
320
- double td = newViewProps.transitionDuration / 1000.0;
321
- _transitionDuration = (isnan(td) || isinf(td) || td < 0) ? 0.3 : td;
322
- }
323
-
324
- if (oldViewProps.transitionType != newViewProps.transitionType) {
325
- auto tt = newViewProps.transitionType;
326
- if (tt == GleamViewTransitionType::Shrink) _transitionTypeValue = 1;
327
- else if (tt == GleamViewTransitionType::Collapse) _transitionTypeValue = 2;
328
- else _transitionTypeValue = 0;
329
- }
330
-
331
- if (oldViewProps.direction != newViewProps.direction) {
332
- auto dir = newViewProps.direction;
333
- if (dir == GleamViewDirection::Rtl) {
334
- _direction = GleamDirectionRTL;
335
- } else if (dir == GleamViewDirection::Ttb) {
336
- _direction = GleamDirectionTTB;
337
- } else {
338
- _direction = GleamDirectionLTR;
339
- }
304
+ // Fabric can recycle a native component without changing the JS props for
305
+ // the next cell. prepareForRecycle resets internal state, so every update
306
+ // must resync from the new props instead of relying only on old/new prop
307
+ // diffs. This keeps FlatList-recycled cells from staying in shimmer mode
308
+ // when the recycled JS prop is still loading=false.
309
+ double s = newViewProps.speed / 1000.0;
310
+ _speed = (isnan(s) || isinf(s) || s <= 0) ? 1.0 : fmax(s, 0.001);
311
+
312
+ double d = newViewProps.delay / 1000.0;
313
+ _delay = (isnan(d) || isinf(d)) ? 0.0 : d;
314
+
315
+ double td = newViewProps.transitionDuration / 1000.0;
316
+ _transitionDuration = (isnan(td) || isinf(td) || td < 0) ? 0.3 : td;
317
+
318
+ auto tt = newViewProps.transitionType;
319
+ if (tt == GleamViewTransitionType::Shrink) _transitionTypeValue = 1;
320
+ else if (tt == GleamViewTransitionType::Collapse) _transitionTypeValue = 2;
321
+ else _transitionTypeValue = 0;
322
+
323
+ auto dir = newViewProps.direction;
324
+ if (dir == GleamViewDirection::Rtl) {
325
+ _direction = GleamDirectionRTL;
326
+ } else if (dir == GleamViewDirection::Ttb) {
327
+ _direction = GleamDirectionTTB;
328
+ } else {
329
+ _direction = GleamDirectionLTR;
340
330
  }
341
331
 
342
332
  BOOL colorsChanged = NO;
343
- if (oldViewProps.intensity != newViewProps.intensity) {
344
- _intensity = fmin(fmax(newViewProps.intensity, 0.0), 1.0);
333
+ CGFloat nextIntensity = fmin(fmax(newViewProps.intensity, 0.0), 1.0);
334
+ if (_intensity != nextIntensity) {
345
335
  colorsChanged = YES;
346
336
  }
337
+ _intensity = nextIntensity;
347
338
 
348
- if (oldViewProps.baseColor != newViewProps.baseColor) {
349
- UIColor *color = RCTUIColorFromSharedColor(newViewProps.baseColor);
350
- _baseColor = color ?: [UIColor colorWithRed:0.878 green:0.878 blue:0.878 alpha:1.0];
339
+ UIColor *baseColor = RCTUIColorFromSharedColor(newViewProps.baseColor) ?: [UIColor colorWithRed:0.878 green:0.878 blue:0.878 alpha:1.0];
340
+ if (![_baseColor isEqual:baseColor]) {
351
341
  colorsChanged = YES;
352
342
  }
343
+ _baseColor = baseColor;
353
344
 
354
- if (oldViewProps.highlightColor != newViewProps.highlightColor) {
355
- UIColor *color = RCTUIColorFromSharedColor(newViewProps.highlightColor);
356
- _highlightColor = color ?: [UIColor colorWithRed:0.961 green:0.961 blue:0.961 alpha:1.0];
345
+ UIColor *highlightColor = RCTUIColorFromSharedColor(newViewProps.highlightColor) ?: [UIColor colorWithRed:0.961 green:0.961 blue:0.961 alpha:1.0];
346
+ if (![_highlightColor isEqual:highlightColor]) {
357
347
  colorsChanged = YES;
358
348
  }
349
+ _highlightColor = highlightColor;
359
350
 
360
- if (oldViewProps.loading != newViewProps.loading) {
361
- _loading = newViewProps.loading;
362
- }
351
+ BOOL loadingChanged = _loading != newViewProps.loading;
352
+ _loading = newViewProps.loading;
363
353
 
364
354
  if (!_didInitialSetup) {
365
355
  _didInitialSetup = YES;
@@ -369,20 +359,16 @@ static void _unregisterView(GleamView *view) {
369
359
  [self _applyLoadingState];
370
360
  } else {
371
361
  _wasLoading = NO;
372
- _contentAlpha = 1.0;
373
- _shimmerOpacity = 0.0;
374
- _lastSetChildrenAlpha = -1.0;
375
- for (UIView *subview in self.subviews) {
376
- subview.alpha = 1.0;
377
- }
378
- _lastSetChildrenAlpha = 1.0;
362
+ [self _forceLoadedState];
379
363
  }
380
364
  } else {
381
365
  if (colorsChanged) {
382
366
  [self _updateShimmerColors];
383
367
  }
384
- if (oldViewProps.loading != newViewProps.loading) {
368
+ if (loadingChanged) {
385
369
  [self _applyLoadingState];
370
+ } else if (!_loading && !_isTransitioning) {
371
+ [self _forceLoadedState];
386
372
  }
387
373
  }
388
374
 
@@ -412,11 +398,7 @@ static void _unregisterView(GleamView *view) {
412
398
  }
413
399
  [self _registerClock];
414
400
  } else if (!_isTransitioning) {
415
- [self _setChildrenAlphaIfNeeded:1.0];
416
- _shimmerOpacity = 0.0;
417
- _shimmerLayer.opacity = 0.0;
418
- [_shimmerLayer removeFromSuperlayer];
419
- [self _unregisterClock];
401
+ [self _forceLoadedState];
420
402
  }
421
403
  }
422
404
  }
@@ -608,20 +590,31 @@ static void _unregisterView(GleamView *view) {
608
590
  _transitionElapsed = 0.0;
609
591
  [self _registerClock];
610
592
  } else {
611
- [self _unregisterClock];
612
- _lastSetChildrenAlpha = -1.0;
613
- for (UIView *subview in self.subviews) {
614
- subview.alpha = 1.0;
615
- }
616
- _lastSetChildrenAlpha = 1.0;
617
- _contentAlpha = 1.0;
618
- _shimmerLayer.opacity = 0.0;
619
- [_shimmerLayer removeFromSuperlayer];
593
+ [self _forceLoadedState];
620
594
  [self _emitTransitionEnd:YES];
621
595
  }
622
596
  }
623
597
  }
624
598
 
599
+ - (void)_forceLoadedState
600
+ {
601
+ if (_isTransitioning) {
602
+ _isTransitioning = NO;
603
+ }
604
+ [self _unregisterClock];
605
+ _lastSetChildrenAlpha = -1.0;
606
+ for (UIView *subview in self.subviews) {
607
+ subview.alpha = 1.0;
608
+ }
609
+ _lastSetChildrenAlpha = 1.0;
610
+ _contentAlpha = 1.0;
611
+ _shimmerOpacity = 0.0;
612
+ _shimmerLayer.opacity = 0.0;
613
+ _shimmerLayer.transform = CATransform3DIdentity;
614
+ _shimmerLayer.mask = nil;
615
+ [_shimmerLayer removeFromSuperlayer];
616
+ }
617
+
625
618
  - (void)_finishTransition
626
619
  {
627
620
  _isTransitioning = NO;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-gleam",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Native-powered shimmer loading effect for React Native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",