react-native-gleam 1.0.3 → 1.0.5
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.
|
@@ -11,6 +11,7 @@ import android.graphics.RectF
|
|
|
11
11
|
import android.graphics.Shader
|
|
12
12
|
import android.os.SystemClock
|
|
13
13
|
import android.view.Choreographer
|
|
14
|
+
import android.view.View
|
|
14
15
|
import android.view.animation.DecelerateInterpolator
|
|
15
16
|
import androidx.annotation.UiThread
|
|
16
17
|
import com.facebook.react.bridge.Arguments
|
|
@@ -171,6 +172,22 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
override fun onVisibilityChanged(changedView: View, visibility: Int) {
|
|
176
|
+
super.onVisibilityChanged(changedView, visibility)
|
|
177
|
+
if (!didAttach) return
|
|
178
|
+
|
|
179
|
+
if (visibility == VISIBLE) {
|
|
180
|
+
if (loading) {
|
|
181
|
+
registerClock()
|
|
182
|
+
invalidate()
|
|
183
|
+
} else if (!isTransitioning) {
|
|
184
|
+
contentOpacity = 1f
|
|
185
|
+
shimmerOpacity = 0f
|
|
186
|
+
invalidate()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
174
191
|
/** Called by ViewManager when the view is dropped */
|
|
175
192
|
fun cleanup() {
|
|
176
193
|
unregisterClock()
|
package/ios/GleamView.mm
CHANGED
|
@@ -26,6 +26,7 @@ static GleamView * __strong *_views = NULL;
|
|
|
26
26
|
static NSUInteger _viewCount = 0;
|
|
27
27
|
static NSUInteger _viewCapacity = 0;
|
|
28
28
|
static CADisplayLink *_displayLink;
|
|
29
|
+
static void *kHiddenKVOContext = &kHiddenKVOContext;
|
|
29
30
|
|
|
30
31
|
static void _startDisplayLinkIfNeeded(void) {
|
|
31
32
|
if (_displayLink) return;
|
|
@@ -103,6 +104,7 @@ static void _unregisterView(GleamView *view) {
|
|
|
103
104
|
CGFloat _contentAlpha;
|
|
104
105
|
CGFloat _lastSetChildrenAlpha;
|
|
105
106
|
BOOL _didInitialSetup;
|
|
107
|
+
BOOL _isObservingHidden;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
@@ -158,6 +160,9 @@ static void _unregisterView(GleamView *view) {
|
|
|
158
160
|
@"transform": [NSNull null],
|
|
159
161
|
};
|
|
160
162
|
_shimmerLayer.locations = @[@0.0, @0.5, @1.0];
|
|
163
|
+
|
|
164
|
+
[self addObserver:self forKeyPath:@"hidden" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:kHiddenKVOContext];
|
|
165
|
+
_isObservingHidden = YES;
|
|
161
166
|
}
|
|
162
167
|
return self;
|
|
163
168
|
}
|
|
@@ -190,6 +195,28 @@ static void _unregisterView(GleamView *view) {
|
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
- (void)didMoveToWindow
|
|
199
|
+
{
|
|
200
|
+
[super didMoveToWindow];
|
|
201
|
+
if (!self.window) return;
|
|
202
|
+
|
|
203
|
+
// View (re)joined a window — resync visual state.
|
|
204
|
+
// Mirrors Android's onAttachedToWindow re-attachment path.
|
|
205
|
+
if (_loading) {
|
|
206
|
+
if (_shimmerLayer.superlayer != self.layer) {
|
|
207
|
+
[self.layer addSublayer:_shimmerLayer];
|
|
208
|
+
}
|
|
209
|
+
[self _registerClock];
|
|
210
|
+
} else if (!_isTransitioning) {
|
|
211
|
+
// _isTransitioning=YES means ticks are actively driving it to
|
|
212
|
+
// 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];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
193
220
|
- (void)removeFromSuperview
|
|
194
221
|
{
|
|
195
222
|
[self _unregisterClock];
|
|
@@ -214,6 +241,10 @@ static void _unregisterView(GleamView *view) {
|
|
|
214
241
|
|
|
215
242
|
- (void)prepareForRecycle
|
|
216
243
|
{
|
|
244
|
+
if (_isObservingHidden) {
|
|
245
|
+
[self removeObserver:self forKeyPath:@"hidden" context:kHiddenKVOContext];
|
|
246
|
+
_isObservingHidden = NO;
|
|
247
|
+
}
|
|
217
248
|
[super prepareForRecycle];
|
|
218
249
|
[self _unregisterClock];
|
|
219
250
|
_isTransitioning = NO;
|
|
@@ -227,6 +258,9 @@ static void _unregisterView(GleamView *view) {
|
|
|
227
258
|
_loading = YES;
|
|
228
259
|
_wasLoading = YES;
|
|
229
260
|
_didInitialSetup = NO;
|
|
261
|
+
|
|
262
|
+
[self addObserver:self forKeyPath:@"hidden" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:kHiddenKVOContext];
|
|
263
|
+
_isObservingHidden = YES;
|
|
230
264
|
}
|
|
231
265
|
|
|
232
266
|
// Invariant: a registered view (_isRegistered=YES) is held by the static
|
|
@@ -237,6 +271,10 @@ static void _unregisterView(GleamView *view) {
|
|
|
237
271
|
// by the display link on the main thread. The view leaks in _views but no crash.
|
|
238
272
|
- (void)dealloc
|
|
239
273
|
{
|
|
274
|
+
if (_isObservingHidden) {
|
|
275
|
+
_isObservingHidden = NO;
|
|
276
|
+
[self removeObserver:self forKeyPath:@"hidden" context:kHiddenKVOContext];
|
|
277
|
+
}
|
|
240
278
|
if (_isRegistered) {
|
|
241
279
|
_isRegistered = NO;
|
|
242
280
|
if ([NSThread isMainThread]) {
|
|
@@ -353,6 +391,36 @@ static void _unregisterView(GleamView *view) {
|
|
|
353
391
|
|
|
354
392
|
#pragma mark - Private
|
|
355
393
|
|
|
394
|
+
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
395
|
+
ofObject:(id)object
|
|
396
|
+
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
|
|
397
|
+
context:(void *)context
|
|
398
|
+
{
|
|
399
|
+
if (context != kHiddenKVOContext) {
|
|
400
|
+
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
BOOL wasHidden = [change[NSKeyValueChangeOldKey] boolValue];
|
|
405
|
+
BOOL isHidden = [change[NSKeyValueChangeNewKey] boolValue];
|
|
406
|
+
|
|
407
|
+
if (wasHidden && !isHidden) {
|
|
408
|
+
// hidden YES→NO: ancestor removed display:'none' — resync visual state
|
|
409
|
+
if (_loading) {
|
|
410
|
+
if (_shimmerLayer.superlayer != self.layer) {
|
|
411
|
+
[self.layer addSublayer:_shimmerLayer];
|
|
412
|
+
}
|
|
413
|
+
[self _registerClock];
|
|
414
|
+
} else if (!_isTransitioning) {
|
|
415
|
+
[self _setChildrenAlphaIfNeeded:1.0];
|
|
416
|
+
_shimmerOpacity = 0.0;
|
|
417
|
+
_shimmerLayer.opacity = 0.0;
|
|
418
|
+
[_shimmerLayer removeFromSuperlayer];
|
|
419
|
+
[self _unregisterClock];
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
356
424
|
- (CGFloat)_computeProgressWithTime:(CFTimeInterval)now
|
|
357
425
|
{
|
|
358
426
|
CGFloat effectiveTime = fmax(now - _delay, 0.0);
|
|
@@ -362,8 +430,10 @@ static void _unregisterView(GleamView *view) {
|
|
|
362
430
|
|
|
363
431
|
- (void)_tickWithTime:(CFTimeInterval)now
|
|
364
432
|
{
|
|
365
|
-
|
|
366
|
-
|
|
433
|
+
// Transitions must always complete to avoid stuck visual state when the
|
|
434
|
+
// view is hidden (display:'none' → hidden=YES) or has no window.
|
|
435
|
+
// Cost is bounded (≤ transitionDuration) and the ops are cheap property sets.
|
|
436
|
+
// Shimmer animation is cosmetic and unbounded — skip when not visible.
|
|
367
437
|
if (_isTransitioning) {
|
|
368
438
|
_transitionElapsed += _displayLink.duration;
|
|
369
439
|
CGFloat t = _transitionDuration > 0 ? fmin(_transitionElapsed / _transitionDuration, 1.0) : 1.0;
|
|
@@ -436,6 +506,7 @@ static void _unregisterView(GleamView *view) {
|
|
|
436
506
|
[self _finishTransition];
|
|
437
507
|
}
|
|
438
508
|
} else if (_loading) {
|
|
509
|
+
if (!self.window) return;
|
|
439
510
|
[self _updateGradientPositionWithTime:now];
|
|
440
511
|
}
|
|
441
512
|
}
|