stream-chat-expo 9.1.2 → 9.1.3-beta.2
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.
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
package com.streamchatreactnative
|
|
2
2
|
|
|
3
|
-
import android.animation.ValueAnimator
|
|
4
3
|
import android.content.Context
|
|
5
4
|
import android.graphics.Canvas
|
|
6
5
|
import android.graphics.Color
|
|
7
6
|
import android.graphics.LinearGradient
|
|
8
7
|
import android.graphics.Matrix
|
|
9
8
|
import android.graphics.Paint
|
|
9
|
+
import android.graphics.Rect
|
|
10
10
|
import android.graphics.Shader
|
|
11
11
|
import android.util.AttributeSet
|
|
12
|
+
import android.view.Choreographer
|
|
12
13
|
import android.view.View
|
|
13
|
-
import android.view.animation.LinearInterpolator
|
|
14
14
|
import android.widget.FrameLayout
|
|
15
15
|
import kotlin.math.roundToInt
|
|
16
16
|
|
|
@@ -38,12 +38,12 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
38
38
|
isDither = true
|
|
39
39
|
}
|
|
40
40
|
private val shimmerMatrix = Matrix()
|
|
41
|
+
private val visibleViewportRect = Rect()
|
|
41
42
|
|
|
42
43
|
private var shimmerShader: LinearGradient? = null
|
|
43
44
|
private var shimmerTranslateX: Float = 0f
|
|
44
|
-
private var
|
|
45
|
-
private var
|
|
46
|
-
private var animator: ValueAnimator? = null
|
|
45
|
+
private var isRegisteredForShimmerFrames: Boolean = false
|
|
46
|
+
private var shimmerStartTimeNanos: Long = UNSET_FRAME_TIME_NANOS
|
|
47
47
|
|
|
48
48
|
init {
|
|
49
49
|
setWillNotDraw(false)
|
|
@@ -68,6 +68,7 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
68
68
|
if (duration > 0) duration.toLong() else DEFAULT_DURATION_MS
|
|
69
69
|
if (durationMs == normalizedDurationMs) return
|
|
70
70
|
durationMs = normalizedDurationMs
|
|
71
|
+
shimmerStartTimeNanos = UNSET_FRAME_TIME_NANOS
|
|
71
72
|
updateAnimatorState()
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -79,8 +80,8 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
fun updateAnimatorState() {
|
|
82
|
-
// Centralized lifecycle gate for
|
|
83
|
-
// hidden views
|
|
83
|
+
// Centralized lifecycle gate for the shared frame clock. This keeps shimmer off for detached or
|
|
84
|
+
// hidden views and prevents every mounted shimmer from owning a separate ValueAnimator.
|
|
84
85
|
if (shouldAnimateShimmer()) {
|
|
85
86
|
startShimmer()
|
|
86
87
|
} else {
|
|
@@ -96,7 +97,7 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
override fun onDetachedFromWindow() {
|
|
99
|
-
// Detached views are not drawable;
|
|
100
|
+
// Detached views are not drawable; unregister so a future attach starts cleanly.
|
|
100
101
|
stopShimmer()
|
|
101
102
|
super.onDetachedFromWindow()
|
|
102
103
|
}
|
|
@@ -114,9 +115,7 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
114
115
|
|
|
115
116
|
override fun onVisibilityChanged(changedView: View, visibility: Int) {
|
|
116
117
|
super.onVisibilityChanged(changedView, visibility)
|
|
117
|
-
|
|
118
|
-
updateAnimatorState()
|
|
119
|
-
}
|
|
118
|
+
updateAnimatorState()
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
override fun dispatchDraw(canvas: Canvas) {
|
|
@@ -155,13 +154,10 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
155
154
|
return
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
//
|
|
157
|
+
// Match iOS CAGradientLayer shimmer stops so both platforms have the same visual falloff.
|
|
159
158
|
val shimmerWidth = (viewWidth * SHIMMER_STRIP_WIDTH_RATIO).coerceAtLeast(1f)
|
|
160
159
|
val transparentHighlight = colorWithAlpha(gradientColor, 0f)
|
|
161
|
-
val edgeBase = colorWithAlpha(gradientColor, EDGE_HIGHLIGHT_ALPHA_FACTOR)
|
|
162
160
|
val softBase = colorWithAlpha(gradientColor, SOFT_HIGHLIGHT_ALPHA_FACTOR)
|
|
163
|
-
val mediumBase = colorWithAlpha(gradientColor, MID_HIGHLIGHT_ALPHA_FACTOR)
|
|
164
|
-
val innerBase = colorWithAlpha(gradientColor, INNER_HIGHLIGHT_ALPHA_FACTOR)
|
|
165
161
|
shimmerShader = LinearGradient(
|
|
166
162
|
0f,
|
|
167
163
|
0f,
|
|
@@ -169,28 +165,16 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
169
165
|
0f,
|
|
170
166
|
intArrayOf(
|
|
171
167
|
transparentHighlight,
|
|
172
|
-
edgeBase,
|
|
173
168
|
softBase,
|
|
174
|
-
mediumBase,
|
|
175
|
-
innerBase,
|
|
176
169
|
gradientColor,
|
|
177
|
-
innerBase,
|
|
178
|
-
mediumBase,
|
|
179
170
|
softBase,
|
|
180
|
-
edgeBase,
|
|
181
171
|
transparentHighlight,
|
|
182
172
|
),
|
|
183
173
|
floatArrayOf(
|
|
184
174
|
0f,
|
|
185
|
-
0.
|
|
186
|
-
0.2f,
|
|
187
|
-
0.32f,
|
|
188
|
-
0.4f,
|
|
175
|
+
0.35f,
|
|
189
176
|
0.5f,
|
|
190
|
-
0.
|
|
191
|
-
0.68f,
|
|
192
|
-
0.8f,
|
|
193
|
-
0.92f,
|
|
177
|
+
0.65f,
|
|
194
178
|
1f,
|
|
195
179
|
),
|
|
196
180
|
Shader.TileMode.CLAMP,
|
|
@@ -198,34 +182,59 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
198
182
|
}
|
|
199
183
|
|
|
200
184
|
private fun startShimmer() {
|
|
185
|
+
if (isRegisteredForShimmerFrames) return
|
|
201
186
|
val viewWidth = width.toFloat()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
187
|
+
shimmerStartTimeNanos = UNSET_FRAME_TIME_NANOS
|
|
188
|
+
if (viewWidth > 0f) {
|
|
189
|
+
shimmerTranslateX = -(viewWidth * SHIMMER_STRIP_WIDTH_RATIO).coerceAtLeast(1f)
|
|
190
|
+
}
|
|
191
|
+
isRegisteredForShimmerFrames = true
|
|
192
|
+
StreamShimmerFrameClock.register(this)
|
|
193
|
+
}
|
|
205
194
|
|
|
206
|
-
|
|
195
|
+
private fun stopShimmer() {
|
|
196
|
+
if (isRegisteredForShimmerFrames) {
|
|
197
|
+
isRegisteredForShimmerFrames = false
|
|
198
|
+
StreamShimmerFrameClock.unregister(this)
|
|
199
|
+
}
|
|
200
|
+
resetShimmerFrameState()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
internal fun onSharedShimmerFrame(frameTimeNanos: Long) {
|
|
204
|
+
val viewWidth = width.toFloat()
|
|
205
|
+
if (viewWidth <= 0f || !hasVisibleViewport()) return
|
|
206
|
+
|
|
207
|
+
if (shimmerStartTimeNanos == UNSET_FRAME_TIME_NANOS) {
|
|
208
|
+
shimmerStartTimeNanos = frameTimeNanos
|
|
209
|
+
}
|
|
207
210
|
|
|
208
211
|
// Animate from fully offscreen left to fully offscreen right so the strip enters/exits cleanly.
|
|
209
212
|
val shimmerWidth = (viewWidth * SHIMMER_STRIP_WIDTH_RATIO).coerceAtLeast(1f)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
addUpdateListener {
|
|
217
|
-
shimmerTranslateX = it.animatedValue as Float
|
|
218
|
-
invalidate()
|
|
219
|
-
}
|
|
220
|
-
start()
|
|
221
|
-
}
|
|
213
|
+
val durationNanos = (durationMs * NANOS_PER_MILLISECOND).coerceAtLeast(1L)
|
|
214
|
+
val elapsedNanos = (frameTimeNanos - shimmerStartTimeNanos).coerceAtLeast(0L)
|
|
215
|
+
val progress = (elapsedNanos % durationNanos).toFloat() / durationNanos.toFloat()
|
|
216
|
+
|
|
217
|
+
shimmerTranslateX = -shimmerWidth + ((viewWidth + shimmerWidth) * progress)
|
|
218
|
+
invalidate()
|
|
222
219
|
}
|
|
223
220
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
internal fun onRemovedFromSharedFrameClock() {
|
|
222
|
+
isRegisteredForShimmerFrames = false
|
|
223
|
+
resetShimmerFrameState()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
internal fun shouldRunSharedShimmerFrame(): Boolean {
|
|
227
|
+
return shouldAnimateShimmer()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private fun hasVisibleViewport(): Boolean {
|
|
231
|
+
visibleViewportRect.setEmpty()
|
|
232
|
+
return getGlobalVisibleRect(visibleViewportRect) && !visibleViewportRect.isEmpty
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fun resetShimmerFrameState() {
|
|
236
|
+
shimmerStartTimeNanos = UNSET_FRAME_TIME_NANOS
|
|
237
|
+
shimmerTranslateX = 0f
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
private fun shouldAnimateShimmer(): Boolean {
|
|
@@ -251,10 +260,51 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
|
|
|
251
260
|
private const val DEFAULT_BASE_COLOR = 0x00FFFFFF
|
|
252
261
|
private const val DEFAULT_DURATION_MS = 1200L
|
|
253
262
|
private const val DEFAULT_GRADIENT_COLOR = 0x59FFFFFF
|
|
263
|
+
private const val NANOS_PER_MILLISECOND = 1_000_000L
|
|
254
264
|
private const val SHIMMER_STRIP_WIDTH_RATIO = 1.25f
|
|
255
|
-
private const val EDGE_HIGHLIGHT_ALPHA_FACTOR = 0.1f
|
|
256
265
|
private const val SOFT_HIGHLIGHT_ALPHA_FACTOR = 0.24f
|
|
257
|
-
private const val
|
|
258
|
-
|
|
266
|
+
private const val UNSET_FRAME_TIME_NANOS = -1L
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private object StreamShimmerFrameClock : Choreographer.FrameCallback {
|
|
271
|
+
private val activeViews = LinkedHashSet<StreamShimmerFrameLayout>()
|
|
272
|
+
private var frameScheduled = false
|
|
273
|
+
|
|
274
|
+
fun register(view: StreamShimmerFrameLayout) {
|
|
275
|
+
activeViews.add(view)
|
|
276
|
+
scheduleNextFrame()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fun unregister(view: StreamShimmerFrameLayout) {
|
|
280
|
+
activeViews.remove(view)
|
|
281
|
+
if (activeViews.isEmpty() && frameScheduled) {
|
|
282
|
+
Choreographer.getInstance().removeFrameCallback(this)
|
|
283
|
+
frameScheduled = false
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
override fun doFrame(frameTimeNanos: Long) {
|
|
288
|
+
frameScheduled = false
|
|
289
|
+
if (activeViews.isEmpty()) return
|
|
290
|
+
|
|
291
|
+
val iterator = activeViews.iterator()
|
|
292
|
+
while (iterator.hasNext()) {
|
|
293
|
+
val view = iterator.next()
|
|
294
|
+
if (view.shouldRunSharedShimmerFrame()) {
|
|
295
|
+
view.onSharedShimmerFrame(frameTimeNanos)
|
|
296
|
+
} else {
|
|
297
|
+
iterator.remove()
|
|
298
|
+
view.onRemovedFromSharedFrameClock()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
scheduleNextFrame()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private fun scheduleNextFrame() {
|
|
306
|
+
if (frameScheduled || activeViews.isEmpty()) return
|
|
307
|
+
Choreographer.getInstance().postFrameCallback(this)
|
|
308
|
+
frameScheduled = true
|
|
259
309
|
}
|
|
260
310
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-chat-expo",
|
|
3
3
|
"description": "The official Expo SDK for Stream Chat, a service for building chat applications",
|
|
4
|
-
"version": "9.1.2",
|
|
4
|
+
"version": "9.1.3-beta.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"company": "Stream.io Inc",
|
|
7
7
|
"name": "Stream.io Inc"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"types": "types/index.d.ts",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"mime": "^4.0.7",
|
|
30
|
-
"stream-chat-react-native-core": "9.1.2"
|
|
30
|
+
"stream-chat-react-native-core": "9.1.3-beta.2"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"expo": ">=52.0.0",
|