@onekeyfe/react-native-skeleton 3.0.95 → 3.0.98
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/android/src/main/java/com/margelo/nitro/skeleton/OneKeySkeletonRenderer.kt +173 -0
- package/android/src/main/java/com/margelo/nitro/skeleton/Skeleton.kt +63 -194
- package/ios/Skeleton.swift +58 -272
- package/ios/SkeletonRenderer.swift +146 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridSkeletonStateUpdater.cpp +42 -23
- package/lib/nitrogen/generated/android/c++/views/JHybridSkeletonStateUpdater.hpp +6 -1
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/skeleton/views/HybridSkeletonManager.kt +26 -10
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/skeleton/views/HybridSkeletonStateUpdater.kt +2 -2
- package/lib/nitrogen/generated/ios/c++/views/HybridSkeletonComponent.mm +50 -27
- package/lib/nitrogen/generated/shared/c++/views/HybridSkeletonComponent.cpp +7 -65
- package/lib/nitrogen/generated/shared/c++/views/HybridSkeletonComponent.hpp +26 -50
- package/nitrogen/generated/android/c++/views/JHybridSkeletonStateUpdater.cpp +42 -23
- package/nitrogen/generated/android/c++/views/JHybridSkeletonStateUpdater.hpp +6 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/skeleton/views/HybridSkeletonManager.kt +26 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/skeleton/views/HybridSkeletonStateUpdater.kt +2 -2
- package/nitrogen/generated/ios/c++/views/HybridSkeletonComponent.mm +50 -27
- package/nitrogen/generated/shared/c++/views/HybridSkeletonComponent.cpp +7 -65
- package/nitrogen/generated/shared/c++/views/HybridSkeletonComponent.hpp +26 -50
- package/package.json +4 -4
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package com.margelo.nitro.skeleton
|
|
2
|
+
|
|
3
|
+
import android.graphics.Canvas
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.LinearGradient
|
|
6
|
+
import android.graphics.Matrix
|
|
7
|
+
import android.graphics.Paint
|
|
8
|
+
import android.graphics.RectF
|
|
9
|
+
import android.graphics.Shader
|
|
10
|
+
import android.view.Choreographer
|
|
11
|
+
import java.lang.ref.WeakReference
|
|
12
|
+
|
|
13
|
+
public val ONEKEY_SKELETON_DEFAULT_COLORS: IntArray = intArrayOf(
|
|
14
|
+
Color.rgb(210, 210, 210),
|
|
15
|
+
Color.rgb(235, 235, 235),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* One process-wide frame clock for every active skeleton renderer. The clock
|
|
20
|
+
* owns no Views and keeps listeners weak, avoiding one ValueAnimator per image.
|
|
21
|
+
*/
|
|
22
|
+
private object OneKeySkeletonClock : Choreographer.FrameCallback {
|
|
23
|
+
interface Listener {
|
|
24
|
+
fun onSkeletonFrame(frameTimeNanos: Long)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private val listeners = ArrayList<WeakReference<Listener>>()
|
|
28
|
+
private var scheduled = false
|
|
29
|
+
|
|
30
|
+
fun register(listener: Listener) {
|
|
31
|
+
var index = listeners.size - 1
|
|
32
|
+
while (index >= 0) {
|
|
33
|
+
val current = listeners[index].get()
|
|
34
|
+
if (current == null) {
|
|
35
|
+
listeners.removeAt(index)
|
|
36
|
+
} else if (current === listener) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
index--
|
|
40
|
+
}
|
|
41
|
+
listeners.add(WeakReference(listener))
|
|
42
|
+
if (!scheduled) {
|
|
43
|
+
scheduled = true
|
|
44
|
+
Choreographer.getInstance().postFrameCallback(this)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun unregister(listener: Listener) {
|
|
49
|
+
var index = listeners.size - 1
|
|
50
|
+
while (index >= 0) {
|
|
51
|
+
val current = listeners[index].get()
|
|
52
|
+
if (current == null || current === listener) listeners.removeAt(index)
|
|
53
|
+
index--
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun doFrame(frameTimeNanos: Long) {
|
|
58
|
+
scheduled = false
|
|
59
|
+
if (listeners.isEmpty()) return
|
|
60
|
+
var index = 0
|
|
61
|
+
while (index < listeners.size) {
|
|
62
|
+
val listener = listeners[index].get()
|
|
63
|
+
if (listener == null) {
|
|
64
|
+
listeners.removeAt(index)
|
|
65
|
+
} else {
|
|
66
|
+
listener.onSkeletonFrame(frameTimeNanos)
|
|
67
|
+
index++
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (listeners.isNotEmpty()) {
|
|
71
|
+
scheduled = true
|
|
72
|
+
Choreographer.getInstance().postFrameCallback(this)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* View-independent shimmer renderer shared by Skeleton and OneKeyImage.
|
|
79
|
+
* Paint, Matrix and LinearGradient are retained and only rebuilt when bounds or
|
|
80
|
+
* colors change; draw() performs no shader/paint/matrix allocation.
|
|
81
|
+
*/
|
|
82
|
+
public class OneKeySkeletonRenderer(
|
|
83
|
+
private val invalidate: () -> Unit,
|
|
84
|
+
) {
|
|
85
|
+
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
86
|
+
private val shaderMatrix = Matrix()
|
|
87
|
+
private val drawBounds = RectF()
|
|
88
|
+
private var colors = ONEKEY_SKELETON_DEFAULT_COLORS.copyOf()
|
|
89
|
+
private var shader: LinearGradient? = null
|
|
90
|
+
private var shaderWidth = -1f
|
|
91
|
+
private var durationNanos = 3_000_000_000L
|
|
92
|
+
private var startedAtNanos = 0L
|
|
93
|
+
private var phase = 0f
|
|
94
|
+
private var running = false
|
|
95
|
+
private val frameListener = object : OneKeySkeletonClock.Listener {
|
|
96
|
+
override fun onSkeletonFrame(frameTimeNanos: Long) {
|
|
97
|
+
handleFrame(frameTimeNanos)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public fun updateStyle(colors: IntArray?, durationSeconds: Double?) {
|
|
102
|
+
val nextColors = colors?.takeIf { it.size >= 2 }?.copyOfRange(0, 2)
|
|
103
|
+
?: ONEKEY_SKELETON_DEFAULT_COLORS.copyOf()
|
|
104
|
+
val colorsChanged = !this.colors.contentEquals(nextColors)
|
|
105
|
+
this.colors = nextColors
|
|
106
|
+
durationNanos = ((durationSeconds ?: 3.0).coerceAtLeast(0.1) * 1_000_000_000L).toLong()
|
|
107
|
+
if (colorsChanged) invalidateShader()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public fun updateBounds(width: Int, height: Int) {
|
|
111
|
+
val nextWidth = width.coerceAtLeast(0).toFloat()
|
|
112
|
+
val nextHeight = height.coerceAtLeast(0).toFloat()
|
|
113
|
+
if (drawBounds.width() != nextWidth || drawBounds.height() != nextHeight) {
|
|
114
|
+
drawBounds.set(0f, 0f, nextWidth, nextHeight)
|
|
115
|
+
invalidateShader()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public fun start() {
|
|
120
|
+
if (running) return
|
|
121
|
+
running = true
|
|
122
|
+
startedAtNanos = 0L
|
|
123
|
+
OneKeySkeletonClock.register(frameListener)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public fun stop() {
|
|
127
|
+
if (!running) return
|
|
128
|
+
running = false
|
|
129
|
+
OneKeySkeletonClock.unregister(frameListener)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public fun draw(canvas: Canvas) {
|
|
133
|
+
if (drawBounds.isEmpty) return
|
|
134
|
+
ensureShader()
|
|
135
|
+
val width = drawBounds.width()
|
|
136
|
+
shaderMatrix.setTranslate(-width + (phase * width * 3f), 0f)
|
|
137
|
+
shader?.setLocalMatrix(shaderMatrix)
|
|
138
|
+
canvas.drawRect(drawBounds, paint)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private fun handleFrame(frameTimeNanos: Long) {
|
|
142
|
+
if (!running) return
|
|
143
|
+
if (startedAtNanos == 0L) startedAtNanos = frameTimeNanos
|
|
144
|
+
phase = ((frameTimeNanos - startedAtNanos) % durationNanos).toFloat() / durationNanos.toFloat()
|
|
145
|
+
invalidate()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private fun ensureShader() {
|
|
149
|
+
val width = drawBounds.width()
|
|
150
|
+
if (shader != null && shaderWidth == width) return
|
|
151
|
+
shader = LinearGradient(
|
|
152
|
+
0f,
|
|
153
|
+
0f,
|
|
154
|
+
width,
|
|
155
|
+
0f,
|
|
156
|
+
intArrayOf(colors[0], colors[1], colors[0]),
|
|
157
|
+
SHADER_STOPS,
|
|
158
|
+
Shader.TileMode.CLAMP,
|
|
159
|
+
)
|
|
160
|
+
shaderWidth = width
|
|
161
|
+
paint.shader = shader
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private fun invalidateShader() {
|
|
165
|
+
shader = null
|
|
166
|
+
shaderWidth = -1f
|
|
167
|
+
paint.shader = null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private companion object {
|
|
171
|
+
val SHADER_STOPS = floatArrayOf(0f, 0.5f, 1f)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -1,232 +1,101 @@
|
|
|
1
1
|
package com.margelo.nitro.skeleton
|
|
2
2
|
|
|
3
|
-
import android.animation.ValueAnimator
|
|
4
3
|
import android.graphics.Canvas
|
|
5
4
|
import android.graphics.Color
|
|
6
|
-
import android.graphics.LinearGradient
|
|
7
|
-
import android.graphics.Paint
|
|
8
|
-
import android.graphics.Shader
|
|
9
5
|
import android.view.View
|
|
10
|
-
import android.view.animation.LinearInterpolator
|
|
11
6
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
12
7
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
13
|
-
import androidx.core.graphics.toColorInt
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
val
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
9
|
+
private class SkeletonHostView(context: ThemedReactContext) : View(context) {
|
|
10
|
+
private val renderer = OneKeySkeletonRenderer { postInvalidateOnAnimation() }
|
|
11
|
+
var disposed = false
|
|
12
|
+
private var aggregatedVisible = true
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private var shimmerPaint: Paint = Paint()
|
|
27
|
-
private var translateX: Float = 0f
|
|
28
|
-
|
|
29
|
-
// Animation properties
|
|
30
|
-
private var customGradientColors: IntArray? = null
|
|
31
|
-
private var animationSpeed: Long = 3000L
|
|
32
|
-
|
|
33
|
-
// Memory safety flags
|
|
34
|
-
private var isDisposed: Boolean = false
|
|
35
|
-
private var retryCount: Int = 0
|
|
36
|
-
private val maxRetryCount: Int = 10
|
|
37
|
-
private var pendingStartRunnable: Runnable? = null
|
|
38
|
-
|
|
39
|
-
// Guard against high-frequency afterUpdate() ValueAnimator churn (REACT-NATIVE-40K ANR).
|
|
40
|
-
// Fabric calls afterUpdate() on every prop commit, and list/order-book screens update
|
|
41
|
-
// very frequently. Unconditionally restarting the shimmer cancels/rebuilds the
|
|
42
|
-
// ValueAnimator each time (ValueAnimator.cancel -> AnimationHandler.removeCallback ->
|
|
43
|
-
// ArrayList.indexOf), blocking the main thread. We cache a signature of the inputs that
|
|
44
|
-
// actually drive the animation (speed + gradient colors + view size) and only restart
|
|
45
|
-
// when that signature changes. If nothing relevant changed and the shimmer is already
|
|
46
|
-
// running, we leave the existing animator untouched (no cancel, no rebuild).
|
|
47
|
-
private var lastShimmerSignature: String? = null
|
|
48
|
-
private var isShimmerRunning: Boolean = false
|
|
49
|
-
|
|
50
|
-
// Build a signature from every input that decides the shimmer animation:
|
|
51
|
-
// - animationSpeed -> ValueAnimator.duration
|
|
52
|
-
// - gradient colors -> shimmer gradient / draw output
|
|
53
|
-
// - view width/height -> animation travel range (-width..width*2) and draw bounds
|
|
54
|
-
private fun currentShimmerSignature(): String {
|
|
55
|
-
val colors = customGradientColors ?: DEFAULT_GRADIENT_COLORS
|
|
56
|
-
val colorsKey = colors.joinToString(",")
|
|
57
|
-
return "$animationSpeed|$colorsKey|${view.width}x${view.height}"
|
|
14
|
+
fun updateRenderer(colors: IntArray?, durationSeconds: Double) {
|
|
15
|
+
setBackgroundColor(colors?.firstOrNull() ?: ONEKEY_SKELETON_DEFAULT_COLORS[0])
|
|
16
|
+
renderer.updateStyle(colors, durationSeconds)
|
|
17
|
+
renderer.updateBounds(width, height)
|
|
18
|
+
syncAnimationState()
|
|
58
19
|
}
|
|
59
20
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
override fun onDraw(canvas: Canvas) {
|
|
63
|
-
super.onDraw(canvas)
|
|
64
|
-
|
|
65
|
-
if (width > 0 && height > 0) {
|
|
66
|
-
val colors = customGradientColors ?: DEFAULT_GRADIENT_COLORS
|
|
67
|
-
val backgroundColor = colors[0]
|
|
68
|
-
val highlightColor = colors[1]
|
|
69
|
-
|
|
70
|
-
val gradient = LinearGradient(
|
|
71
|
-
translateX - width,
|
|
72
|
-
0f,
|
|
73
|
-
translateX,
|
|
74
|
-
0f,
|
|
75
|
-
intArrayOf(backgroundColor, highlightColor, backgroundColor),
|
|
76
|
-
floatArrayOf(0f, 0.5f, 1f),
|
|
77
|
-
Shader.TileMode.CLAMP
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
shimmerPaint.shader = gradient
|
|
81
|
-
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), shimmerPaint)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
21
|
+
fun stopRenderer() {
|
|
22
|
+
renderer.stop()
|
|
84
23
|
}
|
|
85
24
|
|
|
86
|
-
override
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
animationSpeed = ((value ?: 3.0) * 1000).toLong()
|
|
91
|
-
restartShimmer()
|
|
92
|
-
}
|
|
25
|
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
26
|
+
super.onSizeChanged(w, h, oldw, oldh)
|
|
27
|
+
renderer.updateBounds(w, h)
|
|
28
|
+
}
|
|
93
29
|
|
|
94
|
-
override
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (value != null) {
|
|
99
|
-
customGradientColors = value.map { hexStringToColor(it) }.toIntArray()
|
|
100
|
-
restartShimmer()
|
|
101
|
-
}
|
|
102
|
-
}
|
|
30
|
+
override fun onDraw(canvas: Canvas) {
|
|
31
|
+
super.onDraw(canvas)
|
|
32
|
+
renderer.draw(canvas)
|
|
33
|
+
}
|
|
103
34
|
|
|
104
|
-
|
|
105
|
-
|
|
35
|
+
override fun onAttachedToWindow() {
|
|
36
|
+
super.onAttachedToWindow()
|
|
37
|
+
syncAnimationState()
|
|
106
38
|
}
|
|
107
39
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
40
|
+
override fun onDetachedFromWindow() {
|
|
41
|
+
super.onDetachedFromWindow()
|
|
42
|
+
syncAnimationState()
|
|
112
43
|
}
|
|
113
44
|
|
|
114
|
-
|
|
115
|
-
|
|
45
|
+
override fun onVisibilityAggregated(isVisible: Boolean) {
|
|
46
|
+
super.onVisibilityAggregated(isVisible)
|
|
47
|
+
aggregatedVisible = isVisible
|
|
48
|
+
syncAnimationState()
|
|
49
|
+
}
|
|
116
50
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
startShimmerInternal()
|
|
51
|
+
private fun syncAnimationState() {
|
|
52
|
+
if (!disposed && isAttachedToWindow && aggregatedVisible) renderer.start() else renderer.stop()
|
|
120
53
|
}
|
|
54
|
+
}
|
|
121
55
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
android.util.Log.w("HybridSkeleton", "Max retry count reached. View bounds are still empty.")
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
retryCount++
|
|
133
|
-
// Clean up previous pending runnable
|
|
134
|
-
pendingStartRunnable?.let { view.removeCallbacks(it) }
|
|
135
|
-
|
|
136
|
-
// Create new runnable and schedule
|
|
137
|
-
val runnable = Runnable {
|
|
138
|
-
if (!isDisposed) {
|
|
139
|
-
startShimmerInternal()
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
pendingStartRunnable = runnable
|
|
143
|
-
view.postDelayed(runnable, 100)
|
|
144
|
-
return
|
|
145
|
-
}
|
|
56
|
+
/** Thin Nitro adapter around the reusable view-independent renderer. */
|
|
57
|
+
@DoNotStrip
|
|
58
|
+
class HybridSkeleton(context: ThemedReactContext) : HybridSkeletonSpec() {
|
|
59
|
+
private val hostView = SkeletonHostView(context)
|
|
60
|
+
private var colors: IntArray? = null
|
|
61
|
+
private var durationSeconds = 3.0
|
|
146
62
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
shimmerAnimator = ValueAnimator.ofFloat(-width, width * 2).apply {
|
|
150
|
-
duration = animationSpeed
|
|
151
|
-
repeatCount = ValueAnimator.INFINITE
|
|
152
|
-
interpolator = LinearInterpolator()
|
|
153
|
-
addUpdateListener { animation ->
|
|
154
|
-
if (!isDisposed) {
|
|
155
|
-
translateX = animation.animatedValue as Float
|
|
156
|
-
view.invalidate()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
start()
|
|
160
|
-
}
|
|
63
|
+
override val view: View = hostView
|
|
161
64
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
private fun stopShimmer() {
|
|
169
|
-
// Clean up pending runnable
|
|
170
|
-
pendingStartRunnable?.let {
|
|
171
|
-
view.removeCallbacks(it)
|
|
172
|
-
pendingStartRunnable = null
|
|
65
|
+
override var shimmerSpeed: Double?
|
|
66
|
+
get() = durationSeconds
|
|
67
|
+
set(value) {
|
|
68
|
+
if (hostView.disposed) return
|
|
69
|
+
durationSeconds = (value ?: 3.0).coerceAtLeast(0.1)
|
|
70
|
+
updateRenderer()
|
|
173
71
|
}
|
|
174
72
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
73
|
+
override var shimmerGradientColors: Array<String>?
|
|
74
|
+
get() = colors?.map { String.format("#%06X", 0xFFFFFF and it) }?.toTypedArray()
|
|
75
|
+
set(value) {
|
|
76
|
+
if (hostView.disposed) return
|
|
77
|
+
colors = value?.takeIf { it.size >= 2 }?.take(2)?.map(::parseColor)?.toIntArray()
|
|
78
|
+
updateRenderer()
|
|
179
79
|
}
|
|
180
|
-
shimmerAnimator = null
|
|
181
|
-
isShimmerRunning = false
|
|
182
|
-
}
|
|
183
80
|
|
|
184
81
|
override fun afterUpdate() {
|
|
185
82
|
super.afterUpdate()
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
// Defensive guard against ValueAnimator churn (REACT-NATIVE-40K ANR):
|
|
189
|
-
// afterUpdate() runs on every Fabric prop commit. Only restart the shimmer when an
|
|
190
|
-
// input that actually drives the animation changed (speed / colors / view size).
|
|
191
|
-
// If the shimmer is already running and nothing relevant changed, leave the existing
|
|
192
|
-
// ValueAnimator alone to avoid main-thread cancel/rebuild churn.
|
|
193
|
-
val signature = currentShimmerSignature()
|
|
194
|
-
if (isShimmerRunning && shimmerAnimator != null && signature == lastShimmerSignature) {
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
restartShimmer()
|
|
83
|
+
if (!hostView.disposed) updateRenderer()
|
|
199
84
|
}
|
|
200
85
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
startShimmerInternal()
|
|
86
|
+
override fun dispose() {
|
|
87
|
+
hostView.disposed = true
|
|
88
|
+
hostView.stopRenderer()
|
|
89
|
+
super.dispose()
|
|
206
90
|
}
|
|
207
91
|
|
|
208
|
-
private fun
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (hexSanitized.startsWith("#")) {
|
|
212
|
-
hexSanitized = hexSanitized.substring(1)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return try {
|
|
216
|
-
Color.parseColor("#$hexSanitized")
|
|
217
|
-
} catch (e: Exception) {
|
|
218
|
-
DEFAULT_GRADIENT_COLORS[0]
|
|
219
|
-
}
|
|
92
|
+
private fun updateRenderer() {
|
|
93
|
+
hostView.updateRenderer(colors, durationSeconds)
|
|
220
94
|
}
|
|
221
95
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// Stop all animations and clean up
|
|
227
|
-
stopShimmer()
|
|
228
|
-
|
|
229
|
-
// Clear view references to prevent memory leaks
|
|
230
|
-
view.invalidate()
|
|
96
|
+
private fun parseColor(value: String): Int = try {
|
|
97
|
+
Color.parseColor(value)
|
|
98
|
+
} catch (_: IllegalArgumentException) {
|
|
99
|
+
ONEKEY_SKELETON_DEFAULT_COLORS[0]
|
|
231
100
|
}
|
|
232
101
|
}
|