react-native-gleam 1.0.0-beta.4.2 → 1.0.0-beta.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/Gleam.podspec
CHANGED
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
13
|
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
-
s.source = { :git => "https://github.com/
|
|
14
|
+
s.source = { :git => "https://github.com/RamboWasReal/react-native-gleam.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
17
|
s.private_header_files = "ios/**/*.h"
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ When `loading={true}`, children are hidden and a shimmer animation plays. When `
|
|
|
89
89
|
| `loading` | `boolean` | `true` | Toggle shimmer on/off |
|
|
90
90
|
| `speed` | `number` | `1000` | Duration of one shimmer cycle (ms) |
|
|
91
91
|
| `direction` | `GleamDirection` | `LeftToRight` | Animation direction |
|
|
92
|
-
| `delay` | `number` | `0` |
|
|
92
|
+
| `delay` | `number` | `0` | Phase offset (ms) — offsets the shimmer cycle for stagger effects |
|
|
93
93
|
| `transitionDuration` | `number` | `300` | Duration of the transition from shimmer to content (ms). `0` = instant |
|
|
94
94
|
| `transitionType` | `GleamTransition` | `Fade` | Transition style when loading ends |
|
|
95
95
|
| `intensity` | `number` | `1` | Highlight strength (0-1). Lower = more subtle shimmer |
|
|
@@ -97,7 +97,7 @@ When `loading={true}`, children are hidden and a shimmer animation plays. When `
|
|
|
97
97
|
| `highlightColor` | `string` | `#F5F5F5` | Color of the moving highlight |
|
|
98
98
|
| `onTransitionEnd` | `function` | — | Called when the fade transition completes. Receives `{ nativeEvent: { finished: boolean } }` |
|
|
99
99
|
|
|
100
|
-
All standard `View` props are also supported (`style`, `testID`, etc.).
|
|
100
|
+
All standard `View` props are also supported (`style`, `testID`, etc.). Note: the shimmer overlay supports uniform `borderRadius` only — per-corner radii are not applied to the shimmer.
|
|
101
101
|
|
|
102
102
|
### GleamDirection
|
|
103
103
|
|
|
@@ -143,7 +143,7 @@ When `loading` switches to `false`:
|
|
|
143
143
|
|
|
144
144
|
All shimmer instances sharing the same `speed` are automatically synchronized via a shared clock.
|
|
145
145
|
|
|
146
|
-
The shimmer respects `borderRadius` and
|
|
146
|
+
The shimmer respects uniform `borderRadius` and standard view styles.
|
|
147
147
|
|
|
148
148
|
## License
|
|
149
149
|
|
|
@@ -5,8 +5,8 @@ import android.content.Context
|
|
|
5
5
|
import android.graphics.Canvas
|
|
6
6
|
import android.graphics.Color
|
|
7
7
|
import android.graphics.LinearGradient
|
|
8
|
+
import android.graphics.Matrix
|
|
8
9
|
import android.graphics.Paint
|
|
9
|
-
import android.graphics.Path
|
|
10
10
|
import android.graphics.RectF
|
|
11
11
|
import android.graphics.Shader
|
|
12
12
|
import android.os.SystemClock
|
|
@@ -27,19 +27,24 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
27
27
|
enum class Direction { LTR, RTL, TTB }
|
|
28
28
|
enum class TransitionType { FADE, SHRINK, COLLAPSE }
|
|
29
29
|
|
|
30
|
+
private var didAttach = false
|
|
31
|
+
private var transitionGeneration = 0
|
|
32
|
+
|
|
30
33
|
var loading: Boolean = true
|
|
31
34
|
set(value) {
|
|
32
35
|
if (field != value) {
|
|
33
36
|
val wasLoading = field
|
|
34
37
|
field = value
|
|
35
|
-
|
|
38
|
+
if (didAttach) {
|
|
39
|
+
applyLoadingState(wasLoading)
|
|
40
|
+
}
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
var speed: Float = 1000f
|
|
40
45
|
set(value) {
|
|
41
46
|
if (field != value) {
|
|
42
|
-
field = value
|
|
47
|
+
field = value.coerceAtLeast(1f)
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -60,7 +65,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
60
65
|
var transitionDuration: Float = 300f
|
|
61
66
|
set(value) {
|
|
62
67
|
if (field != value) {
|
|
63
|
-
field = value
|
|
68
|
+
field = value.coerceAtLeast(0f)
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -75,6 +80,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
75
80
|
set(value) {
|
|
76
81
|
if (field != value) {
|
|
77
82
|
field = value.coerceIn(0f, 1f)
|
|
83
|
+
invalidateGradientCache()
|
|
78
84
|
invalidate()
|
|
79
85
|
}
|
|
80
86
|
}
|
|
@@ -83,6 +89,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
83
89
|
set(value) {
|
|
84
90
|
if (field != value) {
|
|
85
91
|
field = value
|
|
92
|
+
invalidateGradientCache()
|
|
86
93
|
invalidate()
|
|
87
94
|
}
|
|
88
95
|
}
|
|
@@ -91,17 +98,14 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
91
98
|
set(value) {
|
|
92
99
|
if (field != value) {
|
|
93
100
|
field = value
|
|
101
|
+
invalidateGradientCache()
|
|
94
102
|
invalidate()
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
// Drawing objects — pre-allocated, reused every frame
|
|
98
107
|
private val shimmerPaint = Paint()
|
|
99
|
-
private val
|
|
100
|
-
private val gradientPositions = floatArrayOf(0f, 0.5f, 1f)
|
|
101
|
-
private val gradientCoords = FloatArray(4)
|
|
102
|
-
private val clipPath = Path()
|
|
103
|
-
private val clipRect = RectF()
|
|
104
|
-
internal var cornerRadius: Float = 0f
|
|
108
|
+
private val drawRect = RectF()
|
|
105
109
|
private var transitionAnimator: ValueAnimator? = null
|
|
106
110
|
private var shimmerOpacity: Float = 1f
|
|
107
111
|
private var contentOpacity: Float = 0f
|
|
@@ -109,23 +113,53 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
109
113
|
private var transitionProgress: Float = 0f
|
|
110
114
|
private var isRegistered: Boolean = false
|
|
111
115
|
|
|
116
|
+
// Cached gradient — only recreated when colors change
|
|
117
|
+
private var cachedGradient: LinearGradient? = null
|
|
118
|
+
private val shaderMatrix = Matrix()
|
|
119
|
+
private var lastCachedBaseColor: Int = 0
|
|
120
|
+
private var lastCachedHighlight: Int = 0
|
|
121
|
+
|
|
122
|
+
// Cached corner radius in pixels — only recomputed when prop changes
|
|
123
|
+
internal var cornerRadius: Float = 0f
|
|
124
|
+
set(value) {
|
|
125
|
+
if (field != value) {
|
|
126
|
+
field = value
|
|
127
|
+
cornerRadiusPx = PixelUtil.toPixelFromDIP(value)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
private var cornerRadiusPx: Float = 0f
|
|
131
|
+
|
|
112
132
|
init {
|
|
113
133
|
setWillNotDraw(false)
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
override fun onAttachedToWindow() {
|
|
117
137
|
super.onAttachedToWindow()
|
|
118
|
-
if (
|
|
119
|
-
|
|
138
|
+
if (!didAttach) {
|
|
139
|
+
didAttach = true
|
|
140
|
+
if (loading) {
|
|
141
|
+
registerClock()
|
|
142
|
+
} else {
|
|
143
|
+
contentOpacity = 1f
|
|
144
|
+
shimmerOpacity = 0f
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
// Re-attachment: restore correct state
|
|
148
|
+
if (loading) {
|
|
149
|
+
registerClock()
|
|
150
|
+
} else if (!isTransitioning) {
|
|
151
|
+
contentOpacity = 1f
|
|
152
|
+
shimmerOpacity = 0f
|
|
153
|
+
}
|
|
120
154
|
}
|
|
121
155
|
}
|
|
122
156
|
|
|
123
157
|
override fun onDetachedFromWindow() {
|
|
124
158
|
super.onDetachedFromWindow()
|
|
125
159
|
unregisterClock()
|
|
160
|
+
isTransitioning = false
|
|
126
161
|
transitionAnimator?.cancel()
|
|
127
162
|
transitionAnimator = null
|
|
128
|
-
isTransitioning = false
|
|
129
163
|
}
|
|
130
164
|
|
|
131
165
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
@@ -135,6 +169,14 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
135
169
|
}
|
|
136
170
|
}
|
|
137
171
|
|
|
172
|
+
/** Called by ViewManager when the view is dropped */
|
|
173
|
+
fun cleanup() {
|
|
174
|
+
unregisterClock()
|
|
175
|
+
isTransitioning = false
|
|
176
|
+
transitionAnimator?.cancel()
|
|
177
|
+
transitionAnimator = null
|
|
178
|
+
}
|
|
179
|
+
|
|
138
180
|
private fun registerClock() {
|
|
139
181
|
if (!isRegistered) {
|
|
140
182
|
isRegistered = true
|
|
@@ -149,24 +191,39 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
149
191
|
}
|
|
150
192
|
}
|
|
151
193
|
|
|
152
|
-
/** Called by SharedClock every frame */
|
|
153
|
-
|
|
194
|
+
/** Called by SharedClock every frame with current timestamp */
|
|
195
|
+
internal var frameTimeMs: Float = 0f
|
|
196
|
+
|
|
197
|
+
internal fun onFrame(timeMs: Float) {
|
|
198
|
+
frameTimeMs = timeMs
|
|
154
199
|
invalidate()
|
|
155
200
|
}
|
|
156
201
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
*/
|
|
202
|
+
private fun invalidateGradientCache() {
|
|
203
|
+
cachedGradient = null
|
|
204
|
+
}
|
|
205
|
+
|
|
162
206
|
private fun computeProgress(): Float {
|
|
163
|
-
val timeMs = SystemClock.uptimeMillis().toFloat()
|
|
207
|
+
val timeMs = if (frameTimeMs > 0f) frameTimeMs else SystemClock.uptimeMillis().toFloat()
|
|
164
208
|
val effectiveTime = (timeMs - delay).coerceAtLeast(0f)
|
|
165
209
|
val rawProgress = (effectiveTime % speed) / speed
|
|
166
|
-
// AccelerateDecelerateInterpolator equivalent: (cos((x + 1) * PI) / 2) + 0.5
|
|
167
210
|
return ((cos((rawProgress + 1.0) * PI) / 2.0) + 0.5).toFloat()
|
|
168
211
|
}
|
|
169
212
|
|
|
213
|
+
private fun ensureGradient(effectiveHighlight: Int) {
|
|
214
|
+
if (cachedGradient != null && lastCachedBaseColor == baseColor && lastCachedHighlight == effectiveHighlight) {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
cachedGradient = LinearGradient(
|
|
218
|
+
0f, 0f, 1f, 0f,
|
|
219
|
+
intArrayOf(baseColor, effectiveHighlight, baseColor),
|
|
220
|
+
floatArrayOf(0f, 0.5f, 1f),
|
|
221
|
+
Shader.TileMode.CLAMP
|
|
222
|
+
)
|
|
223
|
+
lastCachedBaseColor = baseColor
|
|
224
|
+
lastCachedHighlight = effectiveHighlight
|
|
225
|
+
}
|
|
226
|
+
|
|
170
227
|
override fun dispatchDraw(canvas: Canvas) {
|
|
171
228
|
val w = width.toFloat()
|
|
172
229
|
val h = height.toFloat()
|
|
@@ -193,40 +250,37 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
193
250
|
highlightColor
|
|
194
251
|
}
|
|
195
252
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
gradientColors[2] = baseColor
|
|
253
|
+
ensureGradient(effectiveHighlight)
|
|
254
|
+
val gradient = cachedGradient ?: return
|
|
199
255
|
|
|
256
|
+
// Position the gradient using a matrix instead of recreating it
|
|
200
257
|
when (direction) {
|
|
201
258
|
Direction.LTR -> {
|
|
202
259
|
val size = w
|
|
203
260
|
val s = -size + (animationProgress * (w + 2 * size))
|
|
204
|
-
|
|
205
|
-
|
|
261
|
+
shaderMatrix.setScale(size, h)
|
|
262
|
+
shaderMatrix.postTranslate(s, 0f)
|
|
206
263
|
}
|
|
207
264
|
Direction.RTL -> {
|
|
208
265
|
val size = w
|
|
209
266
|
val s = w + size - (animationProgress * (w + 2 * size))
|
|
210
|
-
|
|
211
|
-
|
|
267
|
+
shaderMatrix.setScale(-size, h)
|
|
268
|
+
shaderMatrix.postTranslate(s, 0f)
|
|
212
269
|
}
|
|
213
270
|
Direction.TTB -> {
|
|
214
271
|
val size = h
|
|
215
272
|
val s = -size + (animationProgress * (h + 2 * size))
|
|
216
|
-
|
|
217
|
-
|
|
273
|
+
shaderMatrix.setRotate(90f)
|
|
274
|
+
shaderMatrix.postScale(w, size)
|
|
275
|
+
shaderMatrix.postTranslate(0f, s)
|
|
218
276
|
}
|
|
219
277
|
}
|
|
220
278
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
gradientCoords[2], gradientCoords[3],
|
|
224
|
-
gradientColors, gradientPositions,
|
|
225
|
-
Shader.TileMode.CLAMP
|
|
226
|
-
)
|
|
279
|
+
gradient.setLocalMatrix(shaderMatrix)
|
|
280
|
+
shimmerPaint.shader = gradient
|
|
227
281
|
shimmerPaint.alpha = (shimmerOpacity * 255).toInt()
|
|
228
282
|
|
|
229
|
-
// Shrink: scale down,
|
|
283
|
+
// Shrink: scale down, fast opacity fade
|
|
230
284
|
if (isTransitioning && transitionType == TransitionType.SHRINK) {
|
|
231
285
|
val scale = 1f - transitionProgress * 0.5f
|
|
232
286
|
val shrinkOpacity = (1f - transitionProgress * 2.5f).coerceAtLeast(0f)
|
|
@@ -235,7 +289,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
235
289
|
canvas.scale(scale, scale, w / 2f, h / 2f)
|
|
236
290
|
}
|
|
237
291
|
|
|
238
|
-
// Collapse: vertically then horizontally, with opacity fade
|
|
292
|
+
// Collapse: vertically then horizontally, with fast opacity fade
|
|
239
293
|
if (isTransitioning && transitionType == TransitionType.COLLAPSE) {
|
|
240
294
|
val p = transitionProgress
|
|
241
295
|
val scaleY = if (p < 0.6f) 1f - (p / 0.6f) * 0.98f else 0.02f
|
|
@@ -246,20 +300,15 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
246
300
|
canvas.scale(scaleX, scaleY, w / 2f, h / 2f)
|
|
247
301
|
}
|
|
248
302
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
clipRect.set(0f, 0f, w, h)
|
|
254
|
-
clipPath.addRoundRect(clipRect, r, r, Path.Direction.CW)
|
|
255
|
-
canvas.clipPath(clipPath)
|
|
256
|
-
canvas.drawRect(0f, 0f, w, h, shimmerPaint)
|
|
257
|
-
canvas.restoreToCount(count)
|
|
303
|
+
// Draw shimmer — use drawRoundRect instead of clipPath for hardware acceleration
|
|
304
|
+
drawRect.set(0f, 0f, w, h)
|
|
305
|
+
if (cornerRadiusPx > 0f) {
|
|
306
|
+
canvas.drawRoundRect(drawRect, cornerRadiusPx, cornerRadiusPx, shimmerPaint)
|
|
258
307
|
} else {
|
|
259
|
-
canvas.drawRect(
|
|
308
|
+
canvas.drawRect(drawRect, shimmerPaint)
|
|
260
309
|
}
|
|
261
310
|
|
|
262
|
-
// Restore scale
|
|
311
|
+
// Restore scale transform
|
|
263
312
|
if (isTransitioning && (transitionType == TransitionType.SHRINK || transitionType == TransitionType.COLLAPSE)) {
|
|
264
313
|
canvas.restore()
|
|
265
314
|
}
|
|
@@ -276,9 +325,12 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
276
325
|
|
|
277
326
|
private fun applyLoadingState(wasLoading: Boolean) {
|
|
278
327
|
if (loading) {
|
|
328
|
+
// Set isTransitioning=false BEFORE cancel to prevent stale onAnimationEnd
|
|
329
|
+
isTransitioning = false
|
|
330
|
+
transitionGeneration++
|
|
279
331
|
transitionAnimator?.cancel()
|
|
280
332
|
transitionAnimator = null
|
|
281
|
-
|
|
333
|
+
transitionProgress = 0f
|
|
282
334
|
contentOpacity = 0f
|
|
283
335
|
shimmerOpacity = 1f
|
|
284
336
|
registerClock()
|
|
@@ -286,12 +338,16 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
286
338
|
} else {
|
|
287
339
|
if (wasLoading && transitionDuration > 0f) {
|
|
288
340
|
isTransitioning = true
|
|
341
|
+
// Unregister from shared clock — the ValueAnimator drives redraws during transition
|
|
342
|
+
unregisterClock()
|
|
343
|
+
val currentGen = ++transitionGeneration
|
|
289
344
|
transitionAnimator?.cancel()
|
|
290
345
|
|
|
291
346
|
transitionAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
|
292
347
|
duration = transitionDuration.toLong()
|
|
293
|
-
interpolator = DecelerateInterpolator()
|
|
348
|
+
interpolator = DecelerateInterpolator(2f)
|
|
294
349
|
addUpdateListener { anim ->
|
|
350
|
+
if (transitionGeneration != currentGen) return@addUpdateListener
|
|
295
351
|
val p = anim.animatedValue as Float
|
|
296
352
|
transitionProgress = p
|
|
297
353
|
contentOpacity = p
|
|
@@ -300,6 +356,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
300
356
|
}
|
|
301
357
|
addListener(object : android.animation.AnimatorListenerAdapter() {
|
|
302
358
|
override fun onAnimationEnd(animation: android.animation.Animator) {
|
|
359
|
+
if (transitionGeneration != currentGen) return
|
|
303
360
|
finishTransition()
|
|
304
361
|
}
|
|
305
362
|
})
|
|
@@ -316,6 +373,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
316
373
|
}
|
|
317
374
|
|
|
318
375
|
private fun finishTransition() {
|
|
376
|
+
if (!isTransitioning) return
|
|
319
377
|
isTransitioning = false
|
|
320
378
|
unregisterClock()
|
|
321
379
|
contentOpacity = 1f
|
|
@@ -350,11 +408,14 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
350
408
|
* they compute progress from the same global timestamp.
|
|
351
409
|
*/
|
|
352
410
|
companion object SharedClock {
|
|
353
|
-
private val views =
|
|
411
|
+
private val views = mutableListOf<GleamView>()
|
|
412
|
+
private val iterationSnapshot = mutableListOf<GleamView>()
|
|
354
413
|
private var frameCallback: Choreographer.FrameCallback? = null
|
|
355
414
|
|
|
356
415
|
fun register(view: GleamView) {
|
|
357
|
-
views.
|
|
416
|
+
if (!views.contains(view)) {
|
|
417
|
+
views.add(view)
|
|
418
|
+
}
|
|
358
419
|
if (views.size == 1) start()
|
|
359
420
|
}
|
|
360
421
|
|
|
@@ -364,8 +425,11 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
364
425
|
}
|
|
365
426
|
|
|
366
427
|
private fun start() {
|
|
367
|
-
frameCallback = Choreographer.FrameCallback {
|
|
368
|
-
|
|
428
|
+
frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
|
|
429
|
+
val timeMs = frameTimeNanos / 1_000_000f
|
|
430
|
+
iterationSnapshot.clear()
|
|
431
|
+
iterationSnapshot.addAll(views)
|
|
432
|
+
iterationSnapshot.forEach { it.onFrame(timeMs) }
|
|
369
433
|
frameCallback?.let { Choreographer.getInstance().postFrameCallback(it) }
|
|
370
434
|
}
|
|
371
435
|
Choreographer.getInstance().postFrameCallback(frameCallback!!)
|
|
@@ -81,6 +81,11 @@ class GleamViewManager : ViewGroupManager<GleamView>(),
|
|
|
81
81
|
super.setBorderRadius(view, borderRadius)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
override fun onDropViewInstance(view: GleamView) {
|
|
85
|
+
view.cleanup()
|
|
86
|
+
super.onDropViewInstance(view)
|
|
87
|
+
}
|
|
88
|
+
|
|
84
89
|
override fun needsCustomLayoutForChildren(): Boolean = false
|
|
85
90
|
|
|
86
91
|
companion object {
|
package/ios/GleamView.mm
CHANGED
|
@@ -20,35 +20,62 @@ typedef NS_ENUM(NSInteger, GleamDirection) {
|
|
|
20
20
|
|
|
21
21
|
#pragma mark - Shared Display Link Clock
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Plain C array for zero-alloc per-frame iteration.
|
|
24
|
+
// Cleanup is guaranteed via removeFromSuperview, prepareForRecycle, and dealloc.
|
|
25
|
+
static GleamView * __strong *_views = NULL;
|
|
26
|
+
static NSUInteger _viewCount = 0;
|
|
27
|
+
static NSUInteger _viewCapacity = 0;
|
|
24
28
|
static CADisplayLink *_displayLink;
|
|
25
29
|
|
|
26
|
-
static void _ensureDisplayLink(void) {
|
|
27
|
-
if (!_registeredViews) {
|
|
28
|
-
_registeredViews = [NSMutableSet new];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
30
|
static void _startDisplayLinkIfNeeded(void) {
|
|
33
31
|
if (_displayLink) return;
|
|
34
32
|
_displayLink = [CADisplayLink displayLinkWithTarget:[GleamView class] selector:@selector(_onFrame:)];
|
|
33
|
+
if (@available(iOS 15.0, *)) {
|
|
34
|
+
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(30, 60, 60);
|
|
35
|
+
}
|
|
35
36
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
static void _stopDisplayLinkIfNeeded(void) {
|
|
39
|
-
if (
|
|
40
|
+
if (_viewCount > 0) return;
|
|
40
41
|
[_displayLink invalidate];
|
|
41
42
|
_displayLink = nil;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
static void _registerView(GleamView *view) {
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
// Check duplicate
|
|
47
|
+
for (NSUInteger i = 0; i < _viewCount; i++) {
|
|
48
|
+
if (_views[i] == view) return;
|
|
49
|
+
}
|
|
50
|
+
// Grow if needed
|
|
51
|
+
if (_viewCount >= _viewCapacity) {
|
|
52
|
+
NSUInteger newCap = _viewCapacity == 0 ? 16 : _viewCapacity * 2;
|
|
53
|
+
GleamView * __strong *newBuf = (GleamView * __strong *)calloc(newCap, sizeof(GleamView *));
|
|
54
|
+
if (_views) {
|
|
55
|
+
for (NSUInteger i = 0; i < _viewCount; i++) {
|
|
56
|
+
newBuf[i] = _views[i];
|
|
57
|
+
_views[i] = nil;
|
|
58
|
+
}
|
|
59
|
+
free(_views);
|
|
60
|
+
}
|
|
61
|
+
_views = newBuf;
|
|
62
|
+
_viewCapacity = newCap;
|
|
63
|
+
}
|
|
64
|
+
_views[_viewCount++] = view;
|
|
47
65
|
_startDisplayLinkIfNeeded();
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
static void _unregisterView(GleamView *view) {
|
|
51
|
-
|
|
69
|
+
for (NSUInteger i = 0; i < _viewCount; i++) {
|
|
70
|
+
if (_views[i] == view) {
|
|
71
|
+
_views[i] = nil;
|
|
72
|
+
// Swap with last
|
|
73
|
+
_views[i] = _views[_viewCount - 1];
|
|
74
|
+
_views[_viewCount - 1] = nil;
|
|
75
|
+
_viewCount--;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
52
79
|
_stopDisplayLinkIfNeeded();
|
|
53
80
|
}
|
|
54
81
|
|
|
@@ -62,7 +89,7 @@ static void _unregisterView(GleamView *view) {
|
|
|
62
89
|
CGFloat _speed;
|
|
63
90
|
CGFloat _delay;
|
|
64
91
|
CGFloat _transitionDuration;
|
|
65
|
-
NSInteger _transitionTypeValue; // 0=fade, 1=
|
|
92
|
+
NSInteger _transitionTypeValue; // 0=fade, 1=shrink, 2=collapse
|
|
66
93
|
CGFloat _intensity;
|
|
67
94
|
GleamDirection _direction;
|
|
68
95
|
UIColor *_baseColor;
|
|
@@ -73,6 +100,8 @@ static void _unregisterView(GleamView *view) {
|
|
|
73
100
|
CGFloat _transitionElapsed;
|
|
74
101
|
CGFloat _shimmerOpacity;
|
|
75
102
|
CGFloat _contentAlpha;
|
|
103
|
+
CGFloat _lastSetChildrenAlpha;
|
|
104
|
+
BOOL _didInitialSetup;
|
|
76
105
|
}
|
|
77
106
|
|
|
78
107
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
@@ -82,9 +111,15 @@ static void _unregisterView(GleamView *view) {
|
|
|
82
111
|
|
|
83
112
|
+ (void)_onFrame:(CADisplayLink *)link
|
|
84
113
|
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
NSUInteger count = _viewCount;
|
|
115
|
+
if (count == 0) return;
|
|
116
|
+
CFTimeInterval now = CACurrentMediaTime();
|
|
117
|
+
// Iterate by index — safe even if _tick triggers unregister (swap-remove)
|
|
118
|
+
// Process in reverse to handle swap-remove correctly
|
|
119
|
+
for (NSUInteger i = count; i > 0; i--) {
|
|
120
|
+
if (i - 1 < _viewCount) {
|
|
121
|
+
[_views[i - 1] _tickWithTime:now];
|
|
122
|
+
}
|
|
88
123
|
}
|
|
89
124
|
}
|
|
90
125
|
|
|
@@ -109,9 +144,10 @@ static void _unregisterView(GleamView *view) {
|
|
|
109
144
|
_isTransitioning = NO;
|
|
110
145
|
_shimmerOpacity = 1.0;
|
|
111
146
|
_contentAlpha = 0.0;
|
|
147
|
+
_lastSetChildrenAlpha = -1.0;
|
|
148
|
+
_didInitialSetup = NO;
|
|
112
149
|
|
|
113
150
|
_shimmerLayer = [CAGradientLayer layer];
|
|
114
|
-
// Disable implicit animations on the gradient layer
|
|
115
151
|
_shimmerLayer.actions = @{
|
|
116
152
|
@"startPoint": [NSNull null],
|
|
117
153
|
@"endPoint": [NSNull null],
|
|
@@ -120,7 +156,7 @@ static void _unregisterView(GleamView *view) {
|
|
|
120
156
|
@"position": [NSNull null],
|
|
121
157
|
@"transform": [NSNull null],
|
|
122
158
|
};
|
|
123
|
-
|
|
159
|
+
_shimmerLayer.locations = @[@0.0, @0.5, @1.0];
|
|
124
160
|
}
|
|
125
161
|
return self;
|
|
126
162
|
}
|
|
@@ -151,13 +187,54 @@ static void _unregisterView(GleamView *view) {
|
|
|
151
187
|
- (void)removeFromSuperview
|
|
152
188
|
{
|
|
153
189
|
[self _unregisterClock];
|
|
190
|
+
if (_isTransitioning) {
|
|
191
|
+
_isTransitioning = NO;
|
|
192
|
+
}
|
|
193
|
+
// Reset to clean state based on loading
|
|
194
|
+
CGFloat targetAlpha = _loading ? 0.0 : 1.0;
|
|
195
|
+
_contentAlpha = targetAlpha;
|
|
196
|
+
_lastSetChildrenAlpha = -1.0;
|
|
197
|
+
for (UIView *subview in self.subviews) {
|
|
198
|
+
subview.alpha = targetAlpha;
|
|
199
|
+
}
|
|
200
|
+
_lastSetChildrenAlpha = targetAlpha;
|
|
201
|
+
_shimmerOpacity = _loading ? 1.0 : 0.0;
|
|
202
|
+
_shimmerLayer.opacity = _shimmerOpacity;
|
|
154
203
|
_shimmerLayer.mask = nil;
|
|
204
|
+
_shimmerLayer.transform = CATransform3DIdentity;
|
|
205
|
+
[_shimmerLayer removeFromSuperlayer];
|
|
155
206
|
[super removeFromSuperview];
|
|
156
207
|
}
|
|
157
208
|
|
|
158
|
-
- (void)
|
|
209
|
+
- (void)prepareForRecycle
|
|
159
210
|
{
|
|
211
|
+
[super prepareForRecycle];
|
|
160
212
|
[self _unregisterClock];
|
|
213
|
+
_isTransitioning = NO;
|
|
214
|
+
_shimmerLayer.mask = nil;
|
|
215
|
+
_shimmerLayer.transform = CATransform3DIdentity;
|
|
216
|
+
_shimmerLayer.opacity = 0.0;
|
|
217
|
+
[_shimmerLayer removeFromSuperlayer];
|
|
218
|
+
_contentAlpha = 0.0;
|
|
219
|
+
_lastSetChildrenAlpha = -1.0;
|
|
220
|
+
_shimmerOpacity = 1.0;
|
|
221
|
+
_loading = YES;
|
|
222
|
+
_wasLoading = YES;
|
|
223
|
+
_didInitialSetup = NO;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
- (void)dealloc
|
|
227
|
+
{
|
|
228
|
+
if (_isRegistered) {
|
|
229
|
+
_isRegistered = NO;
|
|
230
|
+
if ([NSThread isMainThread]) {
|
|
231
|
+
_unregisterView(self);
|
|
232
|
+
} else {
|
|
233
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
234
|
+
_stopDisplayLinkIfNeeded();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
161
238
|
}
|
|
162
239
|
|
|
163
240
|
- (void)_registerClock
|
|
@@ -182,7 +259,7 @@ static void _unregisterView(GleamView *view) {
|
|
|
182
259
|
const auto &newViewProps = *std::static_pointer_cast<GleamViewProps const>(props);
|
|
183
260
|
|
|
184
261
|
if (oldViewProps.speed != newViewProps.speed) {
|
|
185
|
-
_speed = newViewProps.speed / 1000.0;
|
|
262
|
+
_speed = fmax(newViewProps.speed / 1000.0, 0.001);
|
|
186
263
|
}
|
|
187
264
|
|
|
188
265
|
if (oldViewProps.delay != newViewProps.delay) {
|
|
@@ -200,10 +277,6 @@ static void _unregisterView(GleamView *view) {
|
|
|
200
277
|
else _transitionTypeValue = 0;
|
|
201
278
|
}
|
|
202
279
|
|
|
203
|
-
if (oldViewProps.intensity != newViewProps.intensity) {
|
|
204
|
-
_intensity = fmin(fmax(newViewProps.intensity, 0.0), 1.0);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
280
|
if (oldViewProps.direction != newViewProps.direction) {
|
|
208
281
|
auto dir = newViewProps.direction;
|
|
209
282
|
if (dir == GleamViewDirection::Rtl) {
|
|
@@ -215,54 +288,77 @@ static void _unregisterView(GleamView *view) {
|
|
|
215
288
|
}
|
|
216
289
|
}
|
|
217
290
|
|
|
291
|
+
BOOL colorsChanged = NO;
|
|
292
|
+
if (oldViewProps.intensity != newViewProps.intensity) {
|
|
293
|
+
_intensity = fmin(fmax(newViewProps.intensity, 0.0), 1.0);
|
|
294
|
+
colorsChanged = YES;
|
|
295
|
+
}
|
|
296
|
+
|
|
218
297
|
if (oldViewProps.baseColor != newViewProps.baseColor) {
|
|
219
298
|
UIColor *color = RCTUIColorFromSharedColor(newViewProps.baseColor);
|
|
220
299
|
_baseColor = color ?: [UIColor colorWithRed:0.878 green:0.878 blue:0.878 alpha:1.0];
|
|
300
|
+
colorsChanged = YES;
|
|
221
301
|
}
|
|
222
302
|
|
|
223
303
|
if (oldViewProps.highlightColor != newViewProps.highlightColor) {
|
|
224
304
|
UIColor *color = RCTUIColorFromSharedColor(newViewProps.highlightColor);
|
|
225
305
|
_highlightColor = color ?: [UIColor colorWithRed:0.961 green:0.961 blue:0.961 alpha:1.0];
|
|
306
|
+
colorsChanged = YES;
|
|
226
307
|
}
|
|
227
308
|
|
|
228
309
|
if (oldViewProps.loading != newViewProps.loading) {
|
|
229
310
|
_loading = newViewProps.loading;
|
|
230
311
|
}
|
|
231
312
|
|
|
232
|
-
|
|
233
|
-
|
|
313
|
+
if (!_didInitialSetup) {
|
|
314
|
+
_didInitialSetup = YES;
|
|
315
|
+
[self _updateShimmerColors];
|
|
316
|
+
if (_loading) {
|
|
317
|
+
_wasLoading = YES;
|
|
318
|
+
[self _applyLoadingState];
|
|
319
|
+
} else {
|
|
320
|
+
_wasLoading = NO;
|
|
321
|
+
_contentAlpha = 1.0;
|
|
322
|
+
_shimmerOpacity = 0.0;
|
|
323
|
+
_lastSetChildrenAlpha = -1.0;
|
|
324
|
+
for (UIView *subview in self.subviews) {
|
|
325
|
+
subview.alpha = 1.0;
|
|
326
|
+
}
|
|
327
|
+
_lastSetChildrenAlpha = 1.0;
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
if (colorsChanged) {
|
|
331
|
+
[self _updateShimmerColors];
|
|
332
|
+
}
|
|
333
|
+
if (oldViewProps.loading != newViewProps.loading) {
|
|
334
|
+
[self _applyLoadingState];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
234
337
|
|
|
235
338
|
[super updateProps:props oldProps:oldProps];
|
|
236
339
|
}
|
|
237
340
|
|
|
238
341
|
#pragma mark - Private
|
|
239
342
|
|
|
240
|
-
|
|
241
|
-
* Compute shimmer progress from global time.
|
|
242
|
-
* All views with same speed/delay share the same progress value.
|
|
243
|
-
* Uses cosine easing for smooth looping (matches AccelerateDecelerateInterpolator).
|
|
244
|
-
*/
|
|
245
|
-
- (CGFloat)_computeProgress
|
|
343
|
+
- (CGFloat)_computeProgressWithTime:(CFTimeInterval)now
|
|
246
344
|
{
|
|
247
|
-
|
|
248
|
-
CGFloat effectiveTime = fmax(time - _delay, 0.0);
|
|
345
|
+
CGFloat effectiveTime = fmax(now - _delay, 0.0);
|
|
249
346
|
CGFloat rawProgress = fmod(effectiveTime, _speed) / _speed;
|
|
250
|
-
// Cosine easing: (1 - cos(rawProgress * PI)) / 2
|
|
251
347
|
return (1.0 - cos(rawProgress * M_PI)) / 2.0;
|
|
252
348
|
}
|
|
253
349
|
|
|
254
|
-
- (void)
|
|
350
|
+
- (void)_tickWithTime:(CFTimeInterval)now
|
|
255
351
|
{
|
|
352
|
+
if (!self.window) return;
|
|
353
|
+
|
|
256
354
|
if (_isTransitioning) {
|
|
257
355
|
_transitionElapsed += _displayLink.duration;
|
|
258
|
-
CGFloat t = fmin(_transitionElapsed / _transitionDuration, 1.0);
|
|
259
|
-
// Ease out
|
|
356
|
+
CGFloat t = _transitionDuration > 0 ? fmin(_transitionElapsed / _transitionDuration, 1.0) : 1.0;
|
|
260
357
|
CGFloat eased = 1.0 - (1.0 - t) * (1.0 - t);
|
|
261
358
|
|
|
262
359
|
switch (_transitionTypeValue) {
|
|
263
|
-
case 1: { // Shrink
|
|
264
|
-
|
|
265
|
-
[self _setChildrenAlpha:_contentAlpha];
|
|
360
|
+
case 1: { // Shrink
|
|
361
|
+
[self _setChildrenAlphaIfNeeded:eased];
|
|
266
362
|
CGFloat shrinkOpacity = 1.0 - fmin(eased * 2.5, 1.0);
|
|
267
363
|
_shimmerLayer.opacity = shrinkOpacity;
|
|
268
364
|
CGFloat scale = 1.0 - eased * 0.5;
|
|
@@ -278,12 +374,17 @@ static void _unregisterView(GleamView *view) {
|
|
|
278
374
|
mask.actions = @{@"path": [NSNull null]};
|
|
279
375
|
_shimmerLayer.mask = mask;
|
|
280
376
|
}
|
|
281
|
-
|
|
377
|
+
CGPathRef path = CGPathCreateWithRoundedRect(
|
|
378
|
+
CGRectMake(x, y, fmax(w, 0.1), fmax(h, 0.1)),
|
|
379
|
+
self.layer.cornerRadius * scale,
|
|
380
|
+
self.layer.cornerRadius * scale,
|
|
381
|
+
NULL);
|
|
382
|
+
mask.path = path;
|
|
383
|
+
CGPathRelease(path);
|
|
282
384
|
break;
|
|
283
385
|
}
|
|
284
|
-
case 2: { // Collapse
|
|
285
|
-
|
|
286
|
-
[self _setChildrenAlpha:_contentAlpha];
|
|
386
|
+
case 2: { // Collapse
|
|
387
|
+
[self _setChildrenAlphaIfNeeded:eased];
|
|
287
388
|
CGFloat collapseOpacity = 1.0 - fmin(eased * 2.5, 1.0);
|
|
288
389
|
_shimmerLayer.opacity = collapseOpacity;
|
|
289
390
|
CGRect bounds = self.bounds;
|
|
@@ -294,44 +395,51 @@ static void _unregisterView(GleamView *view) {
|
|
|
294
395
|
CGFloat x = (bounds.size.width - w) / 2.0;
|
|
295
396
|
CGFloat y = (bounds.size.height - h) / 2.0;
|
|
296
397
|
|
|
297
|
-
// Use a mask layer to clip the shimmer to the collapsing rect
|
|
298
398
|
CAShapeLayer *mask = (CAShapeLayer *)_shimmerLayer.mask;
|
|
299
399
|
if (!mask) {
|
|
300
400
|
mask = [CAShapeLayer layer];
|
|
301
401
|
mask.actions = @{@"path": [NSNull null]};
|
|
302
402
|
_shimmerLayer.mask = mask;
|
|
303
403
|
}
|
|
304
|
-
|
|
404
|
+
CGPathRef path = CGPathCreateWithRoundedRect(
|
|
405
|
+
CGRectMake(x, y, fmax(w, 0.1), fmax(h, 0.1)),
|
|
406
|
+
self.layer.cornerRadius,
|
|
407
|
+
self.layer.cornerRadius,
|
|
408
|
+
NULL);
|
|
409
|
+
mask.path = path;
|
|
410
|
+
CGPathRelease(path);
|
|
305
411
|
break;
|
|
306
412
|
}
|
|
307
413
|
default: // Fade
|
|
308
|
-
|
|
309
|
-
[self _setChildrenAlpha:_contentAlpha];
|
|
414
|
+
[self _setChildrenAlphaIfNeeded:eased];
|
|
310
415
|
_shimmerOpacity = 1.0 - eased;
|
|
311
416
|
_shimmerLayer.opacity = _shimmerOpacity;
|
|
312
417
|
break;
|
|
313
418
|
}
|
|
314
419
|
|
|
315
|
-
[self
|
|
420
|
+
[self _updateGradientPositionWithTime:now];
|
|
316
421
|
|
|
317
422
|
if (t >= 1.0) {
|
|
318
423
|
[self _finishTransition];
|
|
319
424
|
}
|
|
320
425
|
} else if (_loading) {
|
|
321
|
-
[self
|
|
426
|
+
[self _updateGradientPositionWithTime:now];
|
|
322
427
|
}
|
|
323
428
|
}
|
|
324
429
|
|
|
325
|
-
- (void)
|
|
430
|
+
- (void)_setChildrenAlphaIfNeeded:(CGFloat)alpha
|
|
326
431
|
{
|
|
432
|
+
if (fabs(alpha - _lastSetChildrenAlpha) < 0.005) return;
|
|
433
|
+
_lastSetChildrenAlpha = alpha;
|
|
434
|
+
_contentAlpha = alpha;
|
|
327
435
|
for (UIView *subview in self.subviews) {
|
|
328
436
|
subview.alpha = alpha;
|
|
329
437
|
}
|
|
330
438
|
}
|
|
331
439
|
|
|
332
|
-
- (void)
|
|
440
|
+
- (void)_updateGradientPositionWithTime:(CFTimeInterval)now
|
|
333
441
|
{
|
|
334
|
-
CGFloat progress = [self
|
|
442
|
+
CGFloat progress = [self _computeProgressWithTime:now];
|
|
335
443
|
|
|
336
444
|
CGPoint startPoint, endPoint;
|
|
337
445
|
|
|
@@ -365,8 +473,8 @@ static void _unregisterView(GleamView *view) {
|
|
|
365
473
|
{
|
|
366
474
|
UIColor *effectiveHighlight = _highlightColor;
|
|
367
475
|
if (_intensity < 1.0) {
|
|
368
|
-
CGFloat br, bg, bb, ba;
|
|
369
|
-
CGFloat hr, hg, hb, ha;
|
|
476
|
+
CGFloat br = 0, bg = 0, bb = 0, ba = 1;
|
|
477
|
+
CGFloat hr = 0, hg = 0, hb = 0, ha = 1;
|
|
370
478
|
[_baseColor getRed:&br green:&bg blue:&bb alpha:&ba];
|
|
371
479
|
[_highlightColor getRed:&hr green:&hg blue:&hb alpha:&ha];
|
|
372
480
|
CGFloat r = br + (hr - br) * _intensity;
|
|
@@ -381,7 +489,6 @@ static void _unregisterView(GleamView *view) {
|
|
|
381
489
|
(id)effectiveHighlight.CGColor,
|
|
382
490
|
(id)_baseColor.CGColor,
|
|
383
491
|
];
|
|
384
|
-
_shimmerLayer.locations = @[@0.0, @0.5, @1.0];
|
|
385
492
|
}
|
|
386
493
|
|
|
387
494
|
- (void)_applyLoadingState
|
|
@@ -389,13 +496,20 @@ static void _unregisterView(GleamView *view) {
|
|
|
389
496
|
if (_loading) {
|
|
390
497
|
_isTransitioning = NO;
|
|
391
498
|
_contentAlpha = 0.0;
|
|
499
|
+
_lastSetChildrenAlpha = -1.0;
|
|
392
500
|
_shimmerOpacity = 1.0;
|
|
393
|
-
|
|
501
|
+
for (UIView *subview in self.subviews) {
|
|
502
|
+
subview.alpha = 0.0;
|
|
503
|
+
}
|
|
504
|
+
_lastSetChildrenAlpha = 0.0;
|
|
394
505
|
_shimmerLayer.opacity = 1.0;
|
|
506
|
+
_shimmerLayer.transform = CATransform3DIdentity;
|
|
507
|
+
_shimmerLayer.mask = nil;
|
|
395
508
|
_shimmerLayer.frame = self.bounds;
|
|
396
509
|
if (_shimmerLayer.superlayer != self.layer) {
|
|
397
510
|
[self.layer addSublayer:_shimmerLayer];
|
|
398
511
|
}
|
|
512
|
+
[self _updateShimmerColors];
|
|
399
513
|
[self _registerClock];
|
|
400
514
|
_wasLoading = YES;
|
|
401
515
|
} else {
|
|
@@ -408,7 +522,12 @@ static void _unregisterView(GleamView *view) {
|
|
|
408
522
|
[self _registerClock];
|
|
409
523
|
} else {
|
|
410
524
|
[self _unregisterClock];
|
|
411
|
-
|
|
525
|
+
_lastSetChildrenAlpha = -1.0;
|
|
526
|
+
for (UIView *subview in self.subviews) {
|
|
527
|
+
subview.alpha = 1.0;
|
|
528
|
+
}
|
|
529
|
+
_lastSetChildrenAlpha = 1.0;
|
|
530
|
+
_contentAlpha = 1.0;
|
|
412
531
|
_shimmerLayer.opacity = 0.0;
|
|
413
532
|
[_shimmerLayer removeFromSuperlayer];
|
|
414
533
|
[self _emitTransitionEnd:YES];
|
|
@@ -420,13 +539,17 @@ static void _unregisterView(GleamView *view) {
|
|
|
420
539
|
{
|
|
421
540
|
_isTransitioning = NO;
|
|
422
541
|
[self _unregisterClock];
|
|
423
|
-
|
|
542
|
+
_lastSetChildrenAlpha = -1.0;
|
|
543
|
+
for (UIView *subview in self.subviews) {
|
|
544
|
+
subview.alpha = 1.0;
|
|
545
|
+
}
|
|
546
|
+
_lastSetChildrenAlpha = 1.0;
|
|
547
|
+
_contentAlpha = 1.0;
|
|
424
548
|
_shimmerLayer.opacity = 0.0;
|
|
425
549
|
_shimmerLayer.transform = CATransform3DIdentity;
|
|
426
550
|
_shimmerLayer.mask = nil;
|
|
427
551
|
_shimmerLayer.frame = self.bounds;
|
|
428
552
|
[_shimmerLayer removeFromSuperlayer];
|
|
429
|
-
[self _updateShimmerColors]; // Reset colors after dissolve
|
|
430
553
|
[self _emitTransitionEnd:YES];
|
|
431
554
|
}
|
|
432
555
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-gleam",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.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",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
"typescript": "^5.9.2"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"react": "
|
|
96
|
-
"react-native": "
|
|
95
|
+
"react": ">=18.0.0",
|
|
96
|
+
"react-native": ">=0.76.0"
|
|
97
97
|
},
|
|
98
98
|
"workspaces": [
|
|
99
99
|
"example"
|