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 +1 -1
- package/android/src/main/java/com/gleam/GleamView.kt +15 -13
- package/ios/GleamView.mm +66 -73
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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;
|