react-native-blur-vibe 0.1.8 → 0.1.9
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/build.gradle +2 -2
- package/android/src/main/java/com/blurvibe/BlurVibeView.kt +95 -193
- package/android/src/main/java/com/blurvibe/BlurVibeViewApi31.kt +158 -185
- package/android/src/main/java/com/blurvibe/BlurVibeViewManager.kt +77 -28
- package/android/src/main/java/com/blurvibe/LegacyBlurController.kt +238 -0
- package/ios/BlurVibeView.swift +2 -0
- package/ios/BlurVibeViewFabric.mm +112 -0
- package/ios/BlurVibeViewManager.m +12 -2
- package/ios/BlurVibeViewManager.swift +9 -9
- package/lib/commonjs/BlurVibeViewNativeComponent.ts +14 -25
- package/lib/commonjs/BlurView.js +9 -30
- package/lib/commonjs/BlurView.js.map +1 -1
- package/lib/module/BlurVibeViewNativeComponent.ts +14 -25
- package/lib/module/BlurView.js +9 -30
- package/lib/module/BlurView.js.map +1 -1
- package/lib/typescript/commonjs/src/BlurVibeViewNativeComponent.d.ts +11 -9
- package/lib/typescript/commonjs/src/BlurVibeViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/BlurView.d.ts +6 -31
- package/lib/typescript/commonjs/src/BlurView.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/types.d.ts +26 -1
- package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
- package/lib/typescript/module/src/BlurVibeViewNativeComponent.d.ts +11 -9
- package/lib/typescript/module/src/BlurVibeViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/BlurView.d.ts +6 -31
- package/lib/typescript/module/src/BlurView.d.ts.map +1 -1
- package/lib/typescript/module/src/types.d.ts +26 -1
- package/lib/typescript/module/src/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/BlurVibeViewNativeComponent.ts +14 -25
- package/src/BlurView.tsx +10 -33
- package/src/types.ts +30 -1
|
@@ -28,28 +28,6 @@ import com.facebook.react.views.view.ReactViewGroup
|
|
|
28
28
|
import kotlin.math.min
|
|
29
29
|
import kotlin.random.Random
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* BlurVibeViewApi31 — GPU backdrop blur for Android API 31+
|
|
33
|
-
*
|
|
34
|
-
* Features:
|
|
35
|
-
* • Dual-RenderNode blur (backdrop-filter CSS semantics)
|
|
36
|
-
* • Progressive / gradient blur (vertical, horizontal, radial)
|
|
37
|
-
* • Noise texture overlay (tactile frosted-glass feel, like Haze)
|
|
38
|
-
* • Overlay tint with full RGBA support
|
|
39
|
-
* • Corner radius with hardware clipping
|
|
40
|
-
* • Choreographer-gated updates (max 1 capture per vsync)
|
|
41
|
-
*
|
|
42
|
-
* Progressive blur technique (from Haze docs):
|
|
43
|
-
* Uses a mask approach — a LinearGradient/RadialGradient is drawn as an
|
|
44
|
-
* alpha mask over the blur output using PorterDuff.DST_IN.
|
|
45
|
-
* This fades the blur from full-strength to zero across the view.
|
|
46
|
-
* Per Haze docs: "masks are much faster with negligible performance cost"
|
|
47
|
-
* vs true per-pixel radius variation which costs ~25% more on API 33+.
|
|
48
|
-
*
|
|
49
|
-
* Noise texture:
|
|
50
|
-
* Haze uses noise at 15% opacity by default for tactility.
|
|
51
|
-
* We generate a small tileable noise bitmap once and draw it with low alpha.
|
|
52
|
-
*/
|
|
53
31
|
@RequiresApi(Build.VERSION_CODES.S)
|
|
54
32
|
class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
55
33
|
|
|
@@ -62,35 +40,48 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
62
40
|
|
|
63
41
|
// ── Progressive blur params ────────────────────────────────────────────────
|
|
64
42
|
|
|
65
|
-
private var progressiveDirection
|
|
66
|
-
private var progressiveStartIntensity = 1f
|
|
67
|
-
private var progressiveEndIntensity = 0f
|
|
43
|
+
private var progressiveDirection = PROGRESSIVE_NONE
|
|
44
|
+
private var progressiveStartIntensity = 1f
|
|
45
|
+
private var progressiveEndIntensity = 0f
|
|
68
46
|
|
|
69
47
|
// ── Noise params ──────────────────────────────────────────────────────────
|
|
70
48
|
|
|
71
|
-
private var noiseFactor = 0.08f
|
|
49
|
+
private var noiseFactor = 0.08f
|
|
72
50
|
private var noiseBitmap: Bitmap? = null
|
|
73
|
-
private val noisePaint = Paint()
|
|
51
|
+
private val noisePaint = Paint()
|
|
74
52
|
|
|
75
53
|
// ── RenderNodes ───────────────────────────────────────────────────────────
|
|
54
|
+
//
|
|
55
|
+
// contentNode: records root-view draw commands ("what's behind me")
|
|
56
|
+
// blurNode: crops + translates contentNode to this view's position,
|
|
57
|
+
// with RenderEffect blur applied
|
|
58
|
+
//
|
|
59
|
+
// IMPORTANT — NO setUseCompositingLayer(true) on either node.
|
|
60
|
+
// Compositing layer on a re-recorded RenderNode causes GPU memory
|
|
61
|
+
// thrashing and SIGSEGV on some API 31 drivers.
|
|
62
|
+
//
|
|
63
|
+
// IMPORTANT — NO LAYER_TYPE_HARDWARE on the view itself.
|
|
64
|
+
// canvas.drawRenderNode() is only valid on a hardware-accelerated canvas
|
|
65
|
+
// that is NOT itself a hardware layer — mixing them causes SIGSEGV
|
|
66
|
+
// in RenderThread (the exact crash we saw in logcat).
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
private val
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
private val contentNode = RenderNode("BlurVibeContent")
|
|
69
|
+
private val blurNode = RenderNode("BlurVibeBlur")
|
|
70
|
+
|
|
71
|
+
// ── Recording guard — prevents double-beginRecording crashes ─────────────
|
|
72
|
+
//
|
|
73
|
+
// If captureRootIntoNode fires twice in the same frame (e.g. during
|
|
74
|
+
// layout + invalidate), a second beginRecording() on an active recording
|
|
75
|
+
// crashes the RenderThread. This flag gates it.
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
private val blurNode = RenderNode("BlurVibeBlur")
|
|
77
|
+
private var isCapturing = false
|
|
84
78
|
|
|
85
|
-
// ── Paint objects
|
|
79
|
+
// ── Paint objects ──────────────────────────────────────────────────────────
|
|
86
80
|
|
|
87
|
-
private val overlayPaint
|
|
88
|
-
private val maskPaint
|
|
81
|
+
private val overlayPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
82
|
+
private val maskPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
89
83
|
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
|
|
90
84
|
}
|
|
91
|
-
private val clearPaint = Paint().apply {
|
|
92
|
-
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
|
|
93
|
-
}
|
|
94
85
|
|
|
95
86
|
// ── Root view ─────────────────────────────────────────────────────────────
|
|
96
87
|
|
|
@@ -106,6 +97,7 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
106
97
|
invalidate()
|
|
107
98
|
}
|
|
108
99
|
}
|
|
100
|
+
|
|
109
101
|
private val preDrawListener = ViewTreeObserver.OnPreDrawListener {
|
|
110
102
|
if (!frameScheduled) {
|
|
111
103
|
frameScheduled = true
|
|
@@ -120,8 +112,10 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
120
112
|
setWillNotDraw(false)
|
|
121
113
|
super.setBackgroundColor(Color.TRANSPARENT)
|
|
122
114
|
clipToOutline = true
|
|
123
|
-
//
|
|
124
|
-
|
|
115
|
+
// DO NOT call setLayerType(LAYER_TYPE_HARDWARE) here —
|
|
116
|
+
// it conflicts with canvas.drawRenderNode() and causes SIGSEGV in RenderThread.
|
|
117
|
+
// The view uses the default layer type (LAYER_TYPE_NONE) so its canvas is
|
|
118
|
+
// the hardware-accelerated display list canvas — which supports drawRenderNode.
|
|
125
119
|
}
|
|
126
120
|
|
|
127
121
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -137,55 +131,68 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
137
131
|
blurRoot?.viewTreeObserver?.removeOnPreDrawListener(preDrawListener)
|
|
138
132
|
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
139
133
|
frameScheduled = false
|
|
140
|
-
|
|
134
|
+
isCapturing = false
|
|
135
|
+
blurRoot = null
|
|
141
136
|
noiseBitmap?.recycle()
|
|
142
137
|
noiseBitmap = null
|
|
138
|
+
// Discard RenderNode display lists to free GPU memory
|
|
139
|
+
contentNode.discardDisplayList()
|
|
140
|
+
blurNode.discardDisplayList()
|
|
143
141
|
super.onDetachedFromWindow()
|
|
144
142
|
}
|
|
145
143
|
|
|
146
144
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
147
145
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
148
|
-
blurNode
|
|
149
|
-
|
|
146
|
+
// Update blurNode bounds — contentNode bounds are set in captureRootIntoNode
|
|
147
|
+
if (w > 0 && h > 0) {
|
|
148
|
+
blurNode.setPosition(0, 0, w, h)
|
|
149
|
+
applyBlurRenderEffect()
|
|
150
|
+
}
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
// ── Capture
|
|
153
|
+
// ── Capture pipeline ───────────────────────────────────────────────────────
|
|
153
154
|
|
|
154
155
|
private fun captureRootIntoNode() {
|
|
156
|
+
if (isCapturing) return // guard against re-entrant / double recording crash
|
|
155
157
|
val root = blurRoot ?: return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
val rw = root.width; if (rw <= 0) return
|
|
159
|
+
val rh = root.height; if (rh <= 0) return
|
|
160
|
+
val vw = width; if (vw <= 0) return
|
|
161
|
+
val vh = height; if (vh <= 0) return
|
|
159
162
|
|
|
160
|
-
|
|
163
|
+
isCapturing = true
|
|
161
164
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
private fun rebuildBlurNode() {
|
|
172
|
-
val root = blurRoot ?: return
|
|
173
|
-
if (width <= 0 || height <= 0) return
|
|
174
|
-
|
|
175
|
-
val myLoc = IntArray(2); getLocationInWindow(myLoc)
|
|
176
|
-
val rootLoc = IntArray(2); root.getLocationInWindow(rootLoc)
|
|
177
|
-
val offsetX = (myLoc[0] - rootLoc[0]).toFloat()
|
|
178
|
-
val offsetY = (myLoc[1] - rootLoc[1]).toFloat()
|
|
165
|
+
// Step 1: record root-view draw into contentNode
|
|
166
|
+
contentNode.setPosition(0, 0, rw, rh)
|
|
167
|
+
val contentCanvas = contentNode.beginRecording()
|
|
168
|
+
try {
|
|
169
|
+
contentCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
|
170
|
+
root.draw(contentCanvas)
|
|
171
|
+
} finally {
|
|
172
|
+
contentNode.endRecording() // always end — even if draw() throws
|
|
173
|
+
}
|
|
179
174
|
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
// Step 2: contentNode recording is FINISHED before we reference it
|
|
176
|
+
// in blurNode. This is critical — drawing an actively-recording
|
|
177
|
+
// RenderNode into another canvas is undefined behaviour.
|
|
178
|
+
val myLoc = IntArray(2); getLocationInWindow(myLoc)
|
|
179
|
+
val rootLoc = IntArray(2); root.getLocationInWindow(rootLoc)
|
|
180
|
+
val offsetX = (myLoc[0] - rootLoc[0]).toFloat()
|
|
181
|
+
val offsetY = (myLoc[1] - rootLoc[1]).toFloat()
|
|
182
|
+
|
|
183
|
+
blurNode.setPosition(0, 0, vw, vh)
|
|
184
|
+
applyBlurRenderEffect()
|
|
185
|
+
|
|
186
|
+
val blurCanvas = blurNode.beginRecording()
|
|
187
|
+
try {
|
|
188
|
+
blurCanvas.translate(-offsetX, -offsetY)
|
|
189
|
+
blurCanvas.drawRenderNode(contentNode) // safe: contentNode recording is done
|
|
190
|
+
} finally {
|
|
191
|
+
blurNode.endRecording()
|
|
192
|
+
}
|
|
182
193
|
|
|
183
|
-
val canvas = blurNode.beginRecording()
|
|
184
|
-
try {
|
|
185
|
-
canvas.translate(-offsetX, -offsetY)
|
|
186
|
-
canvas.drawRenderNode(contentNode)
|
|
187
194
|
} finally {
|
|
188
|
-
|
|
195
|
+
isCapturing = false
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
198
|
|
|
@@ -202,34 +209,30 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
202
209
|
// ── Draw ───────────────────────────────────────────────────────────────────
|
|
203
210
|
|
|
204
211
|
override fun onDraw(canvas: Canvas) {
|
|
205
|
-
val w = width.toFloat()
|
|
206
|
-
val h = height.toFloat()
|
|
207
|
-
if (w <= 0f || h <= 0f) return
|
|
212
|
+
val w = width.toFloat(); if (w <= 0f) return
|
|
213
|
+
val h = height.toFloat(); if (h <= 0f) return
|
|
208
214
|
|
|
215
|
+
// Guard: only draw if blurNode has a valid recorded display list
|
|
209
216
|
if (!blurNode.hasDisplayList()) return
|
|
210
217
|
|
|
211
|
-
//
|
|
212
|
-
// saveLayer lets us composite blur + progressive mask as a unit
|
|
218
|
+
// Step 1: save layer for progressive mask compositing
|
|
213
219
|
val saveCount = if (progressiveDirection != PROGRESSIVE_NONE) {
|
|
214
220
|
canvas.saveLayer(0f, 0f, w, h, null)
|
|
215
|
-
} else
|
|
216
|
-
-1
|
|
217
|
-
}
|
|
221
|
+
} else -1
|
|
218
222
|
|
|
219
|
-
//
|
|
223
|
+
// Step 2: draw the blurred backdrop
|
|
220
224
|
canvas.drawRenderNode(blurNode)
|
|
221
225
|
|
|
222
|
-
//
|
|
226
|
+
// Step 3: progressive alpha mask
|
|
223
227
|
if (progressiveDirection != PROGRESSIVE_NONE && saveCount >= 0) {
|
|
224
|
-
|
|
225
|
-
if (shader != null) {
|
|
228
|
+
buildProgressiveShader(w, h)?.let { shader ->
|
|
226
229
|
maskPaint.shader = shader
|
|
227
230
|
canvas.drawRect(0f, 0f, w, h, maskPaint)
|
|
228
231
|
}
|
|
229
232
|
canvas.restoreToCount(saveCount)
|
|
230
233
|
}
|
|
231
234
|
|
|
232
|
-
//
|
|
235
|
+
// Step 4: overlay tint
|
|
233
236
|
if (Color.alpha(overlayColor) > 0) {
|
|
234
237
|
overlayPaint.color = overlayColor
|
|
235
238
|
if (cornerRadiusPx > 0f) {
|
|
@@ -239,58 +242,38 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
244
|
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
// Step 5: noise grain
|
|
246
|
+
noiseBitmap?.takeIf { !it.isRecycled }?.let { bmp ->
|
|
247
|
+
if (noiseFactor > 0f) {
|
|
248
|
+
noisePaint.alpha = (noiseFactor * 255f).toInt().coerceIn(0, 255)
|
|
249
|
+
noisePaint.shader = BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
|
|
250
|
+
canvas.drawRect(0f, 0f, w, h, noisePaint)
|
|
251
|
+
}
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
251
|
-
// ── Progressive shader
|
|
255
|
+
// ── Progressive shader ────────────────────────────────────────────────────
|
|
252
256
|
|
|
253
257
|
private fun buildProgressiveShader(w: Float, h: Float): Shader? {
|
|
254
|
-
|
|
255
|
-
val
|
|
256
|
-
val endAlpha = progressiveEndIntensity.coerceIn(0f, 1f)
|
|
257
|
-
val startColor = Color.argb((startAlpha * 255).toInt(), 0, 0, 0)
|
|
258
|
-
val endColor = Color.argb((endAlpha * 255).toInt(), 0, 0, 0)
|
|
259
|
-
|
|
258
|
+
val startColor = Color.argb((progressiveStartIntensity.coerceIn(0f,1f) * 255).toInt(), 0,0,0)
|
|
259
|
+
val endColor = Color.argb((progressiveEndIntensity.coerceIn(0f,1f) * 255).toInt(), 0,0,0)
|
|
260
260
|
return when (progressiveDirection) {
|
|
261
|
-
PROGRESSIVE_TOP_TO_BOTTOM -> LinearGradient(
|
|
262
|
-
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
PROGRESSIVE_LEFT_TO_RIGHT -> LinearGradient(
|
|
268
|
-
0f, 0f, w, 0f, startColor, endColor, Shader.TileMode.CLAMP
|
|
269
|
-
)
|
|
270
|
-
PROGRESSIVE_RIGHT_TO_LEFT -> LinearGradient(
|
|
271
|
-
w, 0f, 0f, 0f, startColor, endColor, Shader.TileMode.CLAMP
|
|
272
|
-
)
|
|
273
|
-
PROGRESSIVE_RADIAL -> RadialGradient(
|
|
274
|
-
w / 2f, h / 2f,
|
|
275
|
-
min(w, h) / 2f,
|
|
276
|
-
startColor, endColor,
|
|
277
|
-
Shader.TileMode.CLAMP
|
|
278
|
-
)
|
|
279
|
-
else -> null
|
|
261
|
+
PROGRESSIVE_TOP_TO_BOTTOM -> LinearGradient(0f,0f,0f,h, startColor,endColor, Shader.TileMode.CLAMP)
|
|
262
|
+
PROGRESSIVE_BOTTOM_TO_TOP -> LinearGradient(0f,h,0f,0f, startColor,endColor, Shader.TileMode.CLAMP)
|
|
263
|
+
PROGRESSIVE_LEFT_TO_RIGHT -> LinearGradient(0f,0f,w,0f, startColor,endColor, Shader.TileMode.CLAMP)
|
|
264
|
+
PROGRESSIVE_RIGHT_TO_LEFT -> LinearGradient(w,0f,0f,0f, startColor,endColor, Shader.TileMode.CLAMP)
|
|
265
|
+
PROGRESSIVE_RADIAL -> RadialGradient(w/2f,h/2f, min(w,h)/2f, startColor,endColor, Shader.TileMode.CLAMP)
|
|
266
|
+
else -> null
|
|
280
267
|
}
|
|
281
268
|
}
|
|
282
269
|
|
|
283
|
-
// ── Noise
|
|
284
|
-
//
|
|
285
|
-
// Generates a small (64×64) tileable noise bitmap once.
|
|
286
|
-
// Haze uses noise at 15% opacity for tactility — the fine grain
|
|
287
|
-
// breaks up the uniform blur and makes it look more like real frosted glass.
|
|
270
|
+
// ── Noise bitmap ──────────────────────────────────────────────────────────
|
|
288
271
|
|
|
289
272
|
private fun generateNoiseBitmap() {
|
|
290
|
-
if (noiseBitmap
|
|
273
|
+
if (noiseBitmap?.isRecycled == false) return
|
|
291
274
|
val size = 64
|
|
292
275
|
val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
|
293
|
-
val rng = Random(42)
|
|
276
|
+
val rng = Random(42)
|
|
294
277
|
for (x in 0 until size) {
|
|
295
278
|
for (y in 0 until size) {
|
|
296
279
|
val v = rng.nextInt(256)
|
|
@@ -303,10 +286,9 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
303
286
|
// ── Public setters ─────────────────────────────────────────────────────────
|
|
304
287
|
|
|
305
288
|
fun setBlurAmount(amount: Float) {
|
|
306
|
-
val t
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
blurRadiusY = radius
|
|
289
|
+
val t = amount.coerceIn(0f, 100f) / 100f
|
|
290
|
+
blurRadiusX = t * t * MAX_BLUR_RADIUS
|
|
291
|
+
blurRadiusY = blurRadiusX
|
|
310
292
|
applyBlurRenderEffect()
|
|
311
293
|
scheduleFrame()
|
|
312
294
|
}
|
|
@@ -329,55 +311,54 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
329
311
|
invalidate()
|
|
330
312
|
}
|
|
331
313
|
|
|
332
|
-
fun setReducedTransparencyFallbackColor(@Suppress("UNUSED_PARAMETER") color: String?) {
|
|
333
|
-
// iOS-only — no-op on Android
|
|
334
|
-
}
|
|
314
|
+
fun setReducedTransparencyFallbackColor(@Suppress("UNUSED_PARAMETER") color: String?) { }
|
|
335
315
|
|
|
336
|
-
/**
|
|
337
|
-
* Progressive blur direction.
|
|
338
|
-
* @param direction one of: "none", "topToBottom", "bottomToTop",
|
|
339
|
-
* "leftToRight", "rightToLeft", "radial"
|
|
340
|
-
*/
|
|
341
316
|
fun setProgressiveBlurDirection(direction: String?) {
|
|
342
317
|
progressiveDirection = when (direction) {
|
|
343
|
-
"topToBottom"
|
|
344
|
-
"bottomToTop"
|
|
345
|
-
"leftToRight"
|
|
346
|
-
"rightToLeft"
|
|
347
|
-
"radial"
|
|
348
|
-
else
|
|
318
|
+
"topToBottom" -> PROGRESSIVE_TOP_TO_BOTTOM
|
|
319
|
+
"bottomToTop" -> PROGRESSIVE_BOTTOM_TO_TOP
|
|
320
|
+
"leftToRight" -> PROGRESSIVE_LEFT_TO_RIGHT
|
|
321
|
+
"rightToLeft" -> PROGRESSIVE_RIGHT_TO_LEFT
|
|
322
|
+
"radial" -> PROGRESSIVE_RADIAL
|
|
323
|
+
else -> PROGRESSIVE_NONE
|
|
349
324
|
}
|
|
350
325
|
invalidate()
|
|
351
326
|
}
|
|
352
327
|
|
|
353
|
-
/**
|
|
354
|
-
* Progressive blur start intensity (0.0 = no blur, 1.0 = full blur).
|
|
355
|
-
* This is the intensity at the START of the gradient direction.
|
|
356
|
-
* Default 1.0 — full blur at top/left/center.
|
|
357
|
-
*/
|
|
358
328
|
fun setProgressiveStartIntensity(intensity: Float) {
|
|
359
|
-
progressiveStartIntensity = intensity.coerceIn(0f, 1f)
|
|
360
|
-
invalidate()
|
|
329
|
+
progressiveStartIntensity = intensity.coerceIn(0f, 1f); invalidate()
|
|
361
330
|
}
|
|
362
331
|
|
|
363
|
-
/**
|
|
364
|
-
* Progressive blur end intensity (0.0 = no blur, 1.0 = full blur).
|
|
365
|
-
* This is the intensity at the END of the gradient direction.
|
|
366
|
-
* Default 0.0 — fades to no blur at bottom/right/edge.
|
|
367
|
-
*/
|
|
368
332
|
fun setProgressiveEndIntensity(intensity: Float) {
|
|
369
|
-
progressiveEndIntensity = intensity.coerceIn(0f, 1f)
|
|
370
|
-
invalidate()
|
|
333
|
+
progressiveEndIntensity = intensity.coerceIn(0f, 1f); invalidate()
|
|
371
334
|
}
|
|
372
335
|
|
|
373
|
-
/**
|
|
374
|
-
* Noise factor — grain overlay strength for frosted-glass tactility.
|
|
375
|
-
* 0.0 = no noise, 1.0 = full noise. Default 0.08 (8%).
|
|
376
|
-
* Haze's default is 0.15. Set 0 to disable.
|
|
377
|
-
*/
|
|
378
336
|
fun setNoiseFactor(factor: Float) {
|
|
379
|
-
noiseFactor = factor.coerceIn(0f, 1f)
|
|
380
|
-
|
|
337
|
+
noiseFactor = factor.coerceIn(0f, 1f); invalidate()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fun applyBlurEnabled(enabled: Boolean) {
|
|
341
|
+
if (!enabled) {
|
|
342
|
+
blurRoot?.viewTreeObserver?.removeOnPreDrawListener(preDrawListener)
|
|
343
|
+
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
344
|
+
frameScheduled = false
|
|
345
|
+
blurNode.discardDisplayList()
|
|
346
|
+
contentNode.discardDisplayList()
|
|
347
|
+
invalidate()
|
|
348
|
+
} else {
|
|
349
|
+
blurRoot?.viewTreeObserver?.addOnPreDrawListener(preDrawListener)
|
|
350
|
+
scheduleFrame()
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
fun setAutoUpdate(autoUpdate: Boolean) {
|
|
355
|
+
if (autoUpdate) {
|
|
356
|
+
blurRoot?.viewTreeObserver?.addOnPreDrawListener(preDrawListener)
|
|
357
|
+
} else {
|
|
358
|
+
blurRoot?.viewTreeObserver?.removeOnPreDrawListener(preDrawListener)
|
|
359
|
+
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
360
|
+
frameScheduled = false
|
|
361
|
+
}
|
|
381
362
|
}
|
|
382
363
|
|
|
383
364
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
@@ -392,14 +373,12 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
392
373
|
private fun findBlurRoot(): ViewGroup? {
|
|
393
374
|
var p = parent
|
|
394
375
|
while (p != null) {
|
|
395
|
-
if ((p as? View)?.javaClass?.name == "com.swmansion.rnscreens.Screen")
|
|
396
|
-
return p as? ViewGroup
|
|
376
|
+
if ((p as? View)?.javaClass?.name == "com.swmansion.rnscreens.Screen") return p as? ViewGroup
|
|
397
377
|
p = (p as? View)?.parent
|
|
398
378
|
}
|
|
399
379
|
p = parent
|
|
400
380
|
while (p != null) {
|
|
401
|
-
if ((p as? View)?.javaClass?.name == "com.facebook.react.ReactRootView")
|
|
402
|
-
return p as? ViewGroup
|
|
381
|
+
if ((p as? View)?.javaClass?.name == "com.facebook.react.ReactRootView") return p as? ViewGroup
|
|
403
382
|
p = (p as? View)?.parent
|
|
404
383
|
}
|
|
405
384
|
return rootView as? ViewGroup
|
|
@@ -412,32 +391,26 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
412
391
|
val hex = t.removePrefix("#")
|
|
413
392
|
return try {
|
|
414
393
|
when (hex.length) {
|
|
415
|
-
3 -> Color.argb(255,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
hex.substring(0, 2).toInt(16),
|
|
426
|
-
hex.substring(2, 4).toInt(16),
|
|
427
|
-
hex.substring(4, 6).toInt(16))
|
|
394
|
+
3 -> Color.argb(255, hex[0].toString().repeat(2).toInt(16),
|
|
395
|
+
hex[1].toString().repeat(2).toInt(16),
|
|
396
|
+
hex[2].toString().repeat(2).toInt(16))
|
|
397
|
+
6 -> Color.argb(255, hex.substring(0,2).toInt(16),
|
|
398
|
+
hex.substring(2,4).toInt(16),
|
|
399
|
+
hex.substring(4,6).toInt(16))
|
|
400
|
+
8 -> Color.argb(hex.substring(6,8).toInt(16),
|
|
401
|
+
hex.substring(0,2).toInt(16),
|
|
402
|
+
hex.substring(2,4).toInt(16),
|
|
403
|
+
hex.substring(4,6).toInt(16))
|
|
428
404
|
else -> null
|
|
429
405
|
}
|
|
430
406
|
} catch (_: NumberFormatException) { null }
|
|
431
407
|
}
|
|
432
408
|
|
|
433
|
-
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
434
|
-
// Yoga handles all layout
|
|
435
|
-
}
|
|
409
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { }
|
|
436
410
|
|
|
437
411
|
companion object {
|
|
438
412
|
private const val MAX_BLUR_RADIUS = 25f
|
|
439
413
|
private const val DEFAULT_BLUR_RADIUS = 2.5f
|
|
440
|
-
|
|
441
414
|
const val PROGRESSIVE_NONE = 0
|
|
442
415
|
const val PROGRESSIVE_TOP_TO_BOTTOM = 1
|
|
443
416
|
const val PROGRESSIVE_BOTTOM_TO_TOP = 2
|