react-native-blur-vibe 0.1.8 → 0.1.10
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 +101 -192
- package/android/src/main/java/com/blurvibe/BlurVibeViewApi31.kt +261 -241
- package/android/src/main/java/com/blurvibe/BlurVibeViewManager.kt +77 -28
- package/android/src/main/java/com/blurvibe/LegacyBlurController.kt +258 -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
|
@@ -2,29 +2,47 @@ package com.blurvibe
|
|
|
2
2
|
|
|
3
3
|
import android.os.Build
|
|
4
4
|
import android.view.ViewGroup
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
7
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
5
8
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
-
import com.facebook.react.uimanager.
|
|
9
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
7
10
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
11
|
+
import com.facebook.react.viewmanagers.BlurVibeViewManagerDelegate
|
|
12
|
+
import com.facebook.react.viewmanagers.BlurVibeViewManagerInterface
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* BlurVibeViewManager
|
|
11
16
|
*
|
|
12
|
-
*
|
|
13
|
-
* BlurViewGroup, not ReactViewGroup) and BlurVibeViewApi31 (which extends
|
|
14
|
-
* ReactViewGroup) satisfy the type bound.
|
|
17
|
+
* Supports BOTH Old Architecture (Paper) and New Architecture (Fabric):
|
|
15
18
|
*
|
|
16
|
-
*
|
|
19
|
+
* Old Arch: React Native calls @ReactProp methods directly via reflection.
|
|
20
|
+
* New Arch: React Native calls the generated BlurVibeViewManagerDelegate,
|
|
21
|
+
* which forwards to our BlurVibeViewManagerInterface implementation.
|
|
17
22
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* The dual-arch pattern (SimpleViewManager + ViewManagerDelegate + Interface)
|
|
24
|
+
* is the official React Native recommended approach for library interop.
|
|
25
|
+
* Reference: https://reactnative.dev/docs/the-new-architecture/backward-compatibility-with-libraries
|
|
26
|
+
*
|
|
27
|
+
* Type param is ViewGroup — lowest common supertype of:
|
|
28
|
+
* BlurVibeViewApi31 (extends ReactViewGroup extends ViewGroup)
|
|
29
|
+
* BlurVibeView (extends ReactViewGroup extends ViewGroup)
|
|
30
|
+
*
|
|
31
|
+
* Naming rules (prevent supertype method hiding):
|
|
32
|
+
* setBlurBorderRadius → not setBorderRadius (BaseViewManager has it)
|
|
33
|
+
* setBlurRadiusProp → not setBlurRadius (safety)
|
|
34
|
+
* setBlurTypeProp → not setBlurType (safety)
|
|
35
|
+
* setOverlayColorProp → not setOverlayColor (safety)
|
|
24
36
|
*/
|
|
25
|
-
|
|
37
|
+
@ReactModule(name = BlurVibeViewManager.NAME)
|
|
38
|
+
class BlurVibeViewManager : SimpleViewManager<ViewGroup>(),
|
|
39
|
+
BlurVibeViewManagerInterface<ViewGroup> {
|
|
40
|
+
|
|
41
|
+
private val delegate = BlurVibeViewManagerDelegate(this)
|
|
26
42
|
|
|
27
|
-
override fun
|
|
43
|
+
override fun getDelegate(): ViewManagerDelegate<ViewGroup> = delegate
|
|
44
|
+
|
|
45
|
+
override fun getName(): String = NAME
|
|
28
46
|
|
|
29
47
|
override fun createViewInstance(context: ThemedReactContext): ViewGroup =
|
|
30
48
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) BlurVibeViewApi31(context)
|
|
@@ -33,7 +51,7 @@ class BlurVibeViewManager : ViewGroupManager<ViewGroup>() {
|
|
|
33
51
|
// ── Core props ─────────────────────────────────────────────────────────────
|
|
34
52
|
|
|
35
53
|
@ReactProp(name = "blurAmount", defaultFloat = 10f)
|
|
36
|
-
fun setBlurAmount(view: ViewGroup, amount: Float) {
|
|
54
|
+
override fun setBlurAmount(view: ViewGroup, amount: Float) {
|
|
37
55
|
when (view) {
|
|
38
56
|
is BlurVibeViewApi31 -> view.setBlurAmount(amount)
|
|
39
57
|
is BlurVibeView -> view.setBlurAmount(amount)
|
|
@@ -41,8 +59,8 @@ class BlurVibeViewManager : ViewGroupManager<ViewGroup>() {
|
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
@ReactProp(name = "blurType")
|
|
44
|
-
fun setBlurTypeProp(view: ViewGroup,
|
|
45
|
-
// iOS UIBlurEffectStyle
|
|
62
|
+
fun setBlurTypeProp(view: ViewGroup, type: String?) {
|
|
63
|
+
// iOS UIBlurEffectStyle — no-op on Android
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
@ReactProp(name = "overlayColor")
|
|
@@ -54,7 +72,7 @@ class BlurVibeViewManager : ViewGroupManager<ViewGroup>() {
|
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
@ReactProp(name = "reducedTransparencyFallbackColor")
|
|
57
|
-
fun setReducedTransparencyFallbackColor(view: ViewGroup, color: String?) {
|
|
75
|
+
override fun setReducedTransparencyFallbackColor(view: ViewGroup, color: String?) {
|
|
58
76
|
when (view) {
|
|
59
77
|
is BlurVibeViewApi31 -> view.setReducedTransparencyFallbackColor(color)
|
|
60
78
|
is BlurVibeView -> view.setReducedTransparencyFallbackColor(color)
|
|
@@ -63,42 +81,73 @@ class BlurVibeViewManager : ViewGroupManager<ViewGroup>() {
|
|
|
63
81
|
|
|
64
82
|
@ReactProp(name = "blurRadius", defaultInt = 4)
|
|
65
83
|
fun setBlurRadiusProp(view: ViewGroup, radius: Int) {
|
|
66
|
-
// API < 31 only — QmBlurView downsample factor
|
|
67
|
-
// API 31+ uses full-res RenderNode, downsample irrelevant
|
|
68
84
|
if (view is BlurVibeView) view.setBlurRadius(radius)
|
|
69
85
|
}
|
|
70
86
|
|
|
87
|
+
@ReactProp(name = "enabled", defaultBoolean = true)
|
|
88
|
+
override fun setEnabled(view: ViewGroup, enabled: Boolean) {
|
|
89
|
+
when (view) {
|
|
90
|
+
is BlurVibeViewApi31 -> view.applyBlurEnabled(enabled)
|
|
91
|
+
is BlurVibeView -> view.applyBlurEnabled(enabled)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@ReactProp(name = "autoUpdate", defaultBoolean = true)
|
|
96
|
+
override fun setAutoUpdate(view: ViewGroup, autoUpdate: Boolean) {
|
|
97
|
+
when (view) {
|
|
98
|
+
is BlurVibeViewApi31 -> view.setAutoUpdate(autoUpdate)
|
|
99
|
+
is BlurVibeView -> view.setAutoUpdate(autoUpdate)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
71
103
|
@ReactProp(name = "borderRadius", defaultFloat = 0f)
|
|
72
104
|
fun setBlurBorderRadius(view: ViewGroup, radius: Float) {
|
|
73
105
|
when (view) {
|
|
74
|
-
is BlurVibeViewApi31 -> view.applyBorderRadius(radius)
|
|
75
|
-
is BlurVibeView -> view.
|
|
106
|
+
is BlurVibeViewApi31 -> view.applyBorderRadius(radius)
|
|
107
|
+
is BlurVibeView -> view.applyBorderRadius(radius)
|
|
76
108
|
}
|
|
77
109
|
}
|
|
78
110
|
|
|
79
|
-
// ── Progressive blur
|
|
111
|
+
// ── Progressive blur (API 31+ only) ───────────────────────────────────────
|
|
80
112
|
|
|
81
113
|
@ReactProp(name = "progressiveBlurDirection")
|
|
82
|
-
fun setProgressiveBlurDirection(view: ViewGroup, direction: String?) {
|
|
114
|
+
override fun setProgressiveBlurDirection(view: ViewGroup, direction: String?) {
|
|
83
115
|
if (view is BlurVibeViewApi31) view.setProgressiveBlurDirection(direction)
|
|
84
116
|
}
|
|
85
117
|
|
|
86
118
|
@ReactProp(name = "progressiveStartIntensity", defaultFloat = 1f)
|
|
87
|
-
fun setProgressiveStartIntensity(view: ViewGroup, intensity: Float) {
|
|
119
|
+
override fun setProgressiveStartIntensity(view: ViewGroup, intensity: Float) {
|
|
88
120
|
if (view is BlurVibeViewApi31) view.setProgressiveStartIntensity(intensity)
|
|
89
121
|
}
|
|
90
122
|
|
|
91
123
|
@ReactProp(name = "progressiveEndIntensity", defaultFloat = 0f)
|
|
92
|
-
fun setProgressiveEndIntensity(view: ViewGroup, intensity: Float) {
|
|
124
|
+
override fun setProgressiveEndIntensity(view: ViewGroup, intensity: Float) {
|
|
93
125
|
if (view is BlurVibeViewApi31) view.setProgressiveEndIntensity(intensity)
|
|
94
126
|
}
|
|
95
127
|
|
|
96
|
-
// ── Noise
|
|
128
|
+
// ── Noise (API 31+ only) ──────────────────────────────────────────────────
|
|
97
129
|
|
|
98
130
|
@ReactProp(name = "noiseFactor", defaultFloat = 0.08f)
|
|
99
|
-
fun
|
|
131
|
+
override fun setNoiseFactor(view: ViewGroup, factor: Float) {
|
|
100
132
|
if (view is BlurVibeViewApi31) view.setNoiseFactor(factor)
|
|
101
133
|
}
|
|
102
134
|
|
|
103
|
-
|
|
135
|
+
// ── Interface methods not mapped to @ReactProp (required by codegen) ───────
|
|
136
|
+
|
|
137
|
+
override fun setBlurRadius(view: ViewGroup, radius: Int) {
|
|
138
|
+
if (view is BlurVibeView) view.setBlurRadius(radius)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override fun setBlurType(view: ViewGroup, type: String?) {
|
|
142
|
+
// iOS only — no-op
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override fun setOverlayColor(view: ViewGroup, color: String?) {
|
|
146
|
+
setOverlayColorProp(view, color)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
companion object {
|
|
151
|
+
const val NAME = "BlurVibeView"
|
|
152
|
+
}
|
|
104
153
|
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
package com.blurvibe
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Paint
|
|
7
|
+
import android.graphics.PorterDuff
|
|
8
|
+
import android.graphics.Rect
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.renderscript.Allocation
|
|
11
|
+
import android.renderscript.Element
|
|
12
|
+
import android.renderscript.RenderScript
|
|
13
|
+
import android.renderscript.ScriptIntrinsicBlur
|
|
14
|
+
import android.view.Choreographer
|
|
15
|
+
import android.view.View
|
|
16
|
+
import android.view.ViewGroup
|
|
17
|
+
import android.view.ViewTreeObserver
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* LegacyBlurController — zero-dependency backdrop blur for Android API 21–30.
|
|
21
|
+
*
|
|
22
|
+
* Replaces QmBlurView with a direct RenderScript implementation.
|
|
23
|
+
* RenderScript is part of the Android SDK — no external library needed.
|
|
24
|
+
*
|
|
25
|
+
* Pipeline per vsync:
|
|
26
|
+
* preDrawListener (sets dirty flag only)
|
|
27
|
+
* → Choreographer.FrameCallback (once per vsync)
|
|
28
|
+
* → rootView.draw() into captureBitmap (main thread)
|
|
29
|
+
* → downsample into scaledBitmap
|
|
30
|
+
* → ScriptIntrinsicBlur.forEach() (RenderScript, GPU-accelerated)
|
|
31
|
+
* → view.invalidate()
|
|
32
|
+
* → onDraw: drawBitmap(scaledBitmap) + overlay tint
|
|
33
|
+
*
|
|
34
|
+
* Key optimisations vs naive implementation:
|
|
35
|
+
* - Choreographer gate: max 1 capture per vsync regardless of invalidation count
|
|
36
|
+
* - Bitmap pool: captureBitmap + scaledBitmap reused each frame (zero GC)
|
|
37
|
+
* - RenderScript Allocation pool: inputAlloc + outputAlloc reused (zero GC)
|
|
38
|
+
* - Blur rounds = 2: two passes for smooth spread without pixelation
|
|
39
|
+
* - Downsample factor = 4: captures at 1/16 resolution, blur hides pixel detail
|
|
40
|
+
*/
|
|
41
|
+
@Suppress("DEPRECATION") // RenderScript deprecated in API 31 — we only use this on API < 31
|
|
42
|
+
internal class LegacyBlurController(
|
|
43
|
+
private val view: View,
|
|
44
|
+
private val rootView: ViewGroup
|
|
45
|
+
) {
|
|
46
|
+
|
|
47
|
+
companion object {
|
|
48
|
+
private const val DOWNSAMPLE_FACTOR = 4f // capture at 1/4 linear resolution (1/16 pixels)
|
|
49
|
+
private const val BLUR_RADIUS = 8f // RenderScript Gaussian radius (1–25)
|
|
50
|
+
private const val BLUR_ROUNDS = 2 // passes — 2 gives smooth spread
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Bitmap pool ────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
private var captureBitmap: Bitmap? = null // full-res root capture
|
|
56
|
+
private var scaledBitmap: Bitmap? = null // downsampled before blur
|
|
57
|
+
private val capturePaint = Paint(Paint.FILTER_BITMAP_FLAG)
|
|
58
|
+
private val drawPaint = Paint(Paint.FILTER_BITMAP_FLAG)
|
|
59
|
+
|
|
60
|
+
// ── RenderScript pool ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
private var rs: RenderScript? = null
|
|
63
|
+
private var blurScript: ScriptIntrinsicBlur? = null
|
|
64
|
+
private var inputAlloc: Allocation? = null
|
|
65
|
+
private var outputAlloc: Allocation? = null
|
|
66
|
+
|
|
67
|
+
// ── State ──────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
var overlayColor: Int = Color.TRANSPARENT
|
|
70
|
+
var blurRadius: Float = BLUR_RADIUS
|
|
71
|
+
var enabled: Boolean = true
|
|
72
|
+
set(value) { field = value; if (!value) invalidatePool() }
|
|
73
|
+
var autoUpdate: Boolean = true
|
|
74
|
+
set(value) {
|
|
75
|
+
field = value
|
|
76
|
+
if (value) safeAddPreDrawListener()
|
|
77
|
+
else rootView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private var frameScheduled = false
|
|
81
|
+
private var isCapturing = false
|
|
82
|
+
|
|
83
|
+
// ── Choreographer gate ────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
private val frameCallback = Choreographer.FrameCallback {
|
|
86
|
+
frameScheduled = false
|
|
87
|
+
if (enabled) captureAndBlur()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private val preDrawListener = ViewTreeObserver.OnPreDrawListener {
|
|
91
|
+
if (!frameScheduled && enabled) {
|
|
92
|
+
frameScheduled = true
|
|
93
|
+
Choreographer.getInstance().postFrameCallback(frameCallback)
|
|
94
|
+
}
|
|
95
|
+
true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Init ───────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
init {
|
|
101
|
+
initRenderScript()
|
|
102
|
+
safeAddPreDrawListener()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun initRenderScript() {
|
|
106
|
+
try {
|
|
107
|
+
rs = RenderScript.create(view.context)
|
|
108
|
+
blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
|
|
109
|
+
} catch (_: Exception) {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Capture + blur ─────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
private fun captureAndBlur() {
|
|
115
|
+
if (isCapturing) return
|
|
116
|
+
val rw = rootView.width; if (rw <= 0) return
|
|
117
|
+
val rh = rootView.height; if (rh <= 0) return
|
|
118
|
+
val vw = view.width; if (vw <= 0) return
|
|
119
|
+
val vh = view.height; if (vh <= 0) return
|
|
120
|
+
|
|
121
|
+
val sw = (vw / DOWNSAMPLE_FACTOR).toInt().coerceAtLeast(1)
|
|
122
|
+
val sh = (vh / DOWNSAMPLE_FACTOR).toInt().coerceAtLeast(1)
|
|
123
|
+
|
|
124
|
+
isCapturing = true
|
|
125
|
+
try {
|
|
126
|
+
// ① Compute offset of view within root
|
|
127
|
+
val myLoc = IntArray(2); view.getLocationInWindow(myLoc)
|
|
128
|
+
val rootLoc = IntArray(2); rootView.getLocationInWindow(rootLoc)
|
|
129
|
+
val offsetX = myLoc[0] - rootLoc[0]
|
|
130
|
+
val offsetY = myLoc[1] - rootLoc[1]
|
|
131
|
+
|
|
132
|
+
// ② Allocate bitmaps (reuse if size matches)
|
|
133
|
+
val capture = reuseBitmap(captureBitmap, vw, vh).also { captureBitmap = it }
|
|
134
|
+
val scaled = reuseBitmap(scaledBitmap, sw, sh).also { scaledBitmap = it }
|
|
135
|
+
|
|
136
|
+
// ③ Capture just the region behind this view from root
|
|
137
|
+
val c = Canvas(capture)
|
|
138
|
+
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
|
139
|
+
c.translate(-offsetX.toFloat(), -offsetY.toFloat())
|
|
140
|
+
rootView.draw(c)
|
|
141
|
+
|
|
142
|
+
// ④ Downsample
|
|
143
|
+
val sc = Canvas(scaled)
|
|
144
|
+
sc.drawBitmap(capture,
|
|
145
|
+
Rect(0, 0, capture.width, capture.height),
|
|
146
|
+
Rect(0, 0, scaled.width, scaled.height),
|
|
147
|
+
capturePaint)
|
|
148
|
+
|
|
149
|
+
// ⑤ Blur (2 rounds for smooth spread)
|
|
150
|
+
repeat(BLUR_ROUNDS) { blurBitmap(scaled) }
|
|
151
|
+
|
|
152
|
+
// ⑥ Trigger redraw with new bitmap
|
|
153
|
+
view.invalidate()
|
|
154
|
+
|
|
155
|
+
} catch (_: Exception) {
|
|
156
|
+
} finally {
|
|
157
|
+
isCapturing = false
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun blurBitmap(bitmap: Bitmap) {
|
|
162
|
+
val rs = this.rs ?: return softwareBlur(bitmap)
|
|
163
|
+
val sc = this.blurScript ?: return softwareBlur(bitmap)
|
|
164
|
+
try {
|
|
165
|
+
val inA = reuseAlloc(inputAlloc, bitmap, rs).also { inputAlloc = it }
|
|
166
|
+
val outA = reuseAlloc(outputAlloc, bitmap, rs).also { outputAlloc = it }
|
|
167
|
+
inA.copyFrom(bitmap)
|
|
168
|
+
sc.setRadius(blurRadius.coerceIn(1f, 25f))
|
|
169
|
+
sc.setInput(inA)
|
|
170
|
+
sc.forEach(outA)
|
|
171
|
+
outA.copyTo(bitmap)
|
|
172
|
+
} catch (_: Exception) {
|
|
173
|
+
softwareBlur(bitmap)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fun softwareBlur(bitmap: Bitmap) {
|
|
178
|
+
// Pure software Gaussian fallback (slower but always works)
|
|
179
|
+
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
180
|
+
maskFilter = android.graphics.BlurMaskFilter(blurRadius, android.graphics.BlurMaskFilter.Blur.NORMAL)
|
|
181
|
+
}
|
|
182
|
+
Canvas(bitmap).drawBitmap(bitmap, 0f, 0f, paint)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Draw — called from BlurVibeView.onDraw() ──────────────────────────────
|
|
186
|
+
|
|
187
|
+
fun draw(canvas: Canvas, viewWidth: Float, viewHeight: Float) {
|
|
188
|
+
scaledBitmap?.takeIf { !it.isRecycled }?.let { bmp ->
|
|
189
|
+
canvas.drawBitmap(bmp, null,
|
|
190
|
+
android.graphics.RectF(0f, 0f, viewWidth, viewHeight), drawPaint)
|
|
191
|
+
}
|
|
192
|
+
if (Color.alpha(overlayColor) > 0) {
|
|
193
|
+
canvas.drawColor(overlayColor)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
fun onSizeChanged() {
|
|
200
|
+
invalidatePool()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private fun invalidatePool() {
|
|
204
|
+
captureBitmap?.recycle(); captureBitmap = null
|
|
205
|
+
scaledBitmap?.recycle(); scaledBitmap = null
|
|
206
|
+
inputAlloc?.destroy(); inputAlloc = null
|
|
207
|
+
outputAlloc?.destroy(); outputAlloc = null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Multi-window / split-screen / PiP safety ──────────────────────────────
|
|
211
|
+
//
|
|
212
|
+
// Called by BlurVibeView.onWindowFocusChanged(hasFocus=true).
|
|
213
|
+
// Re-attaches the preDrawListener to the rootView's current
|
|
214
|
+
// (possibly newly created) ViewTreeObserver after a window mode transition.
|
|
215
|
+
|
|
216
|
+
fun reAttach() {
|
|
217
|
+
if (enabled && autoUpdate) {
|
|
218
|
+
safeAddPreDrawListener()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fun safeAddPreDrawListener() {
|
|
223
|
+
val vto = rootView.viewTreeObserver
|
|
224
|
+
vto.removeOnPreDrawListener(preDrawListener) // no-op if not attached
|
|
225
|
+
if (vto.isAlive) {
|
|
226
|
+
vto.addOnPreDrawListener(preDrawListener)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fun destroy() {
|
|
231
|
+
rootView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
232
|
+
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
233
|
+
inputAlloc?.destroy()
|
|
234
|
+
outputAlloc?.destroy()
|
|
235
|
+
blurScript?.destroy()
|
|
236
|
+
rs?.destroy()
|
|
237
|
+
captureBitmap?.recycle()
|
|
238
|
+
scaledBitmap?.recycle()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Bitmap / Allocation helpers ───────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
private fun reuseBitmap(existing: Bitmap?, w: Int, h: Int): Bitmap {
|
|
244
|
+
if (existing != null && !existing.isRecycled
|
|
245
|
+
&& existing.width == w && existing.height == h) return existing
|
|
246
|
+
existing?.recycle()
|
|
247
|
+
return Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private fun reuseAlloc(existing: Allocation?, src: Bitmap, rs: RenderScript): Allocation {
|
|
251
|
+
if (existing != null
|
|
252
|
+
&& existing.type.x == src.width
|
|
253
|
+
&& existing.type.y == src.height) return existing
|
|
254
|
+
existing?.destroy()
|
|
255
|
+
return Allocation.createFromBitmap(rs, src,
|
|
256
|
+
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT)
|
|
257
|
+
}
|
|
258
|
+
}
|
package/ios/BlurVibeView.swift
CHANGED
|
@@ -24,6 +24,8 @@ class BlurVibeView: UIView {
|
|
|
24
24
|
@objc var progressiveStartIntensity: NSNumber = 1.0 { didSet { updateView() } }
|
|
25
25
|
@objc var progressiveEndIntensity: NSNumber = 0.0 { didSet { updateView() } }
|
|
26
26
|
@objc var noiseFactor: NSNumber = 0.08 { didSet { updateView() } }
|
|
27
|
+
@objc var enabled: NSNumber = 1 { didSet { updateView() } }
|
|
28
|
+
@objc var autoUpdate: NSNumber = 1 { didSet { updateView() } }
|
|
27
29
|
|
|
28
30
|
// MARK: - Init
|
|
29
31
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// BlurVibeViewFabric.mm
|
|
2
|
+
// Fabric (New Architecture) component view for BlurVibeView.
|
|
3
|
+
//
|
|
4
|
+
// This file is compiled ONLY when React Native's New Architecture is enabled
|
|
5
|
+
// (RCT_NEW_ARCH_ENABLED=1). The old arch path (BlurVibeViewManager.m +
|
|
6
|
+
// BlurVibeViewManager.swift + BlurVibeView.swift) continues to work unchanged
|
|
7
|
+
// on Old Architecture projects.
|
|
8
|
+
//
|
|
9
|
+
// Architecture routing:
|
|
10
|
+
// New Arch (Fabric) → BlurVibeViewFabric (this file)
|
|
11
|
+
// Old Arch (Paper) → BlurVibeViewManager.m + BlurVibeView.swift
|
|
12
|
+
//
|
|
13
|
+
// Both render the same SwiftUI layer (BlurVibeSwiftUIView) so visual output
|
|
14
|
+
// is identical on both architectures.
|
|
15
|
+
|
|
16
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
17
|
+
|
|
18
|
+
#import <React/RCTViewComponentView.h>
|
|
19
|
+
#import <UIKit/UIKit.h>
|
|
20
|
+
#import <objc/runtime.h>
|
|
21
|
+
|
|
22
|
+
// Generated codegen headers (produced by `pod install` / yarn codegen)
|
|
23
|
+
#import <react/renderer/components/BlurVibeSpec/ComponentDescriptors.h>
|
|
24
|
+
#import <react/renderer/components/BlurVibeSpec/EventEmitters.h>
|
|
25
|
+
#import <react/renderer/components/BlurVibeSpec/Props.h>
|
|
26
|
+
#import <react/renderer/components/BlurVibeSpec/RCTComponentViewHelpers.h>
|
|
27
|
+
#import "RCTFabricComponentsPlugins.h"
|
|
28
|
+
|
|
29
|
+
// Import Swift types via the generated module header
|
|
30
|
+
// (react-native-blur-vibe-Swift.h is generated by Xcode for Swift/ObjC interop)
|
|
31
|
+
#import "react_native_blur_vibe-Swift.h"
|
|
32
|
+
|
|
33
|
+
using namespace facebook::react;
|
|
34
|
+
|
|
35
|
+
@interface BlurVibeViewFabric () <RCTBlurVibeViewViewProtocol>
|
|
36
|
+
@end
|
|
37
|
+
|
|
38
|
+
@implementation BlurVibeViewFabric {
|
|
39
|
+
// The UIKit view that hosts the SwiftUI blur layer.
|
|
40
|
+
// BlurVibeView is defined in BlurVibeView.swift — reused across both arches.
|
|
41
|
+
BlurVibeView *_blurView;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Fabric component descriptor ────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
47
|
+
return concreteComponentDescriptorProvider<BlurVibeViewComponentDescriptor>();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Init ───────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
53
|
+
if (self = [super initWithFrame:frame]) {
|
|
54
|
+
static const auto defaultProps = std::make_shared<const BlurVibeViewProps>();
|
|
55
|
+
_props = defaultProps;
|
|
56
|
+
|
|
57
|
+
_blurView = [[BlurVibeView alloc] initWithFrame:CGRectZero];
|
|
58
|
+
_blurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
59
|
+
|
|
60
|
+
self.contentView = _blurView;
|
|
61
|
+
self.backgroundColor = UIColor.clearColor;
|
|
62
|
+
}
|
|
63
|
+
return self;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Layout ─────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
- (void)layoutSubviews {
|
|
69
|
+
[super layoutSubviews];
|
|
70
|
+
_blurView.frame = self.bounds;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Props update (called by Fabric on prop changes) ────────────────────────
|
|
74
|
+
|
|
75
|
+
- (void)updateProps:(Props::Shared const &)props
|
|
76
|
+
oldProps:(Props::Shared const &)oldProps {
|
|
77
|
+
|
|
78
|
+
const auto &p = *std::static_pointer_cast<const BlurVibeViewProps>(props);
|
|
79
|
+
|
|
80
|
+
// Map codegen C++ props → Swift @objc vars on BlurVibeView
|
|
81
|
+
_blurView.blurAmount = @(p.blurAmount);
|
|
82
|
+
_blurView.blurType = [NSString stringWithUTF8String:p.blurType.c_str()];
|
|
83
|
+
_blurView.overlayColor = [NSString stringWithUTF8String:p.overlayColor.c_str()];
|
|
84
|
+
_blurView.reducedTransparencyFallbackColor =
|
|
85
|
+
[NSString stringWithUTF8String:p.reducedTransparencyFallbackColor.c_str()];
|
|
86
|
+
_blurView.blurRadius = @(p.blurRadius);
|
|
87
|
+
|
|
88
|
+
// enabled / autoUpdate
|
|
89
|
+
_blurView.enabled = p.enabled ? @YES : @NO;
|
|
90
|
+
_blurView.autoUpdate = p.autoUpdate ? @YES : @NO;
|
|
91
|
+
|
|
92
|
+
// Progressive blur
|
|
93
|
+
_blurView.progressiveBlurDirection =
|
|
94
|
+
[NSString stringWithUTF8String:p.progressiveBlurDirection.c_str()];
|
|
95
|
+
_blurView.progressiveStartIntensity = @(p.progressiveStartIntensity);
|
|
96
|
+
_blurView.progressiveEndIntensity = @(p.progressiveEndIntensity);
|
|
97
|
+
|
|
98
|
+
// Noise
|
|
99
|
+
_blurView.noiseFactor = @(p.noiseFactor);
|
|
100
|
+
|
|
101
|
+
[super updateProps:props oldProps:oldProps];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Fabric component registration ──────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
Class<RCTComponentViewProtocol> BlurVibeViewFabricCls(void) {
|
|
107
|
+
return BlurVibeViewFabric.class;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@end
|
|
111
|
+
|
|
112
|
+
#endif // RCT_NEW_ARCH_ENABLED
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// BlurVibeViewManager.m
|
|
2
|
+
// Old Architecture (Paper) prop exports.
|
|
3
|
+
// On New Architecture, props are handled by BlurVibeViewFabric.mm via codegen.
|
|
4
|
+
|
|
5
|
+
#ifndef RCT_NEW_ARCH_ENABLED
|
|
6
|
+
|
|
1
7
|
#import <React/RCTViewManager.h>
|
|
2
8
|
#import <React/RCTUIManager.h>
|
|
3
9
|
|
|
@@ -8,7 +14,9 @@ RCT_EXPORT_VIEW_PROPERTY(blurAmount, NSNumber)
|
|
|
8
14
|
RCT_EXPORT_VIEW_PROPERTY(blurType, NSString)
|
|
9
15
|
RCT_EXPORT_VIEW_PROPERTY(overlayColor, NSString)
|
|
10
16
|
RCT_EXPORT_VIEW_PROPERTY(reducedTransparencyFallbackColor, NSString)
|
|
11
|
-
RCT_EXPORT_VIEW_PROPERTY(blurRadius, NSNumber)
|
|
17
|
+
RCT_EXPORT_VIEW_PROPERTY(blurRadius, NSNumber)
|
|
18
|
+
RCT_EXPORT_VIEW_PROPERTY(enabled, NSNumber)
|
|
19
|
+
RCT_EXPORT_VIEW_PROPERTY(autoUpdate, NSNumber)
|
|
12
20
|
|
|
13
21
|
// ── Progressive blur props ──────────────────────────────────────────────────
|
|
14
22
|
RCT_EXPORT_VIEW_PROPERTY(progressiveBlurDirection, NSString)
|
|
@@ -16,4 +24,6 @@ RCT_EXPORT_VIEW_PROPERTY(progressiveStartIntensity, NSNumber)
|
|
|
16
24
|
RCT_EXPORT_VIEW_PROPERTY(progressiveEndIntensity, NSNumber)
|
|
17
25
|
|
|
18
26
|
// ── Noise prop ──────────────────────────────────────────────────────────────
|
|
19
|
-
RCT_EXPORT_VIEW_PROPERTY(noiseFactor, NSNumber)
|
|
27
|
+
RCT_EXPORT_VIEW_PROPERTY(noiseFactor, NSNumber)
|
|
28
|
+
|
|
29
|
+
#endif // RCT_NEW_ARCH_ENABLED
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
// BlurVibeViewManager.swift
|
|
2
|
+
// Old Architecture (Paper) view manager.
|
|
3
|
+
// On New Architecture, BlurVibeViewFabric.mm handles component registration.
|
|
4
|
+
|
|
5
|
+
#if !RCT_NEW_ARCH_ENABLED
|
|
6
|
+
|
|
1
7
|
import Foundation
|
|
2
8
|
|
|
3
|
-
/**
|
|
4
|
-
* BlurVibeViewManager
|
|
5
|
-
*
|
|
6
|
-
* RCTViewManager subclass — registers BlurVibeView with React Native.
|
|
7
|
-
* requiresMainQueueSetup = true because we create UIKit views.
|
|
8
|
-
*/
|
|
9
9
|
@objc(BlurVibeViewManager)
|
|
10
10
|
class BlurVibeViewManager: RCTViewManager {
|
|
11
|
-
|
|
12
11
|
override func view() -> UIView! {
|
|
13
12
|
return BlurVibeView()
|
|
14
13
|
}
|
|
15
|
-
|
|
16
14
|
override static func requiresMainQueueSetup() -> Bool {
|
|
17
15
|
return true
|
|
18
16
|
}
|
|
19
|
-
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#endif
|
|
@@ -1,34 +1,23 @@
|
|
|
1
|
-
// @ts-ignore
|
|
1
|
+
// @ts-ignore
|
|
2
2
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
3
3
|
import type { HostComponent, ViewProps } from 'react-native';
|
|
4
|
-
// @ts-ignore
|
|
5
|
-
import type { Float, Int32
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import type { WithDefault, Float, Int32} from 'react-native/Libraries/Types/CodegenTypes';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
blurAmount?: Float;
|
|
10
|
-
|
|
11
|
-
// iOS UIBlurEffectStyle name — no-op on Android
|
|
7
|
+
interface NativeProps extends ViewProps {
|
|
8
|
+
blurAmount?: WithDefault<Float, 10>;
|
|
12
9
|
blurType?: string;
|
|
13
|
-
|
|
14
|
-
// Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
15
10
|
overlayColor?: string;
|
|
16
|
-
|
|
17
|
-
// Fallback when blur unavailable
|
|
18
11
|
reducedTransparencyFallbackColor?: string;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
progressiveEndIntensity?: Float;
|
|
27
|
-
|
|
28
|
-
// Noise grain overlay — Android API 31+ only
|
|
29
|
-
noiseFactor?: Float;
|
|
12
|
+
blurRadius?: WithDefault<Int32, 4>;
|
|
13
|
+
enabled?: WithDefault<boolean, true>;
|
|
14
|
+
autoUpdate?: WithDefault<boolean, true>;
|
|
15
|
+
progressiveBlurDirection?: WithDefault<string, 'none'>;
|
|
16
|
+
progressiveStartIntensity?: WithDefault<Float, 1>;
|
|
17
|
+
progressiveEndIntensity?: WithDefault<Float, 0>;
|
|
18
|
+
noiseFactor?: WithDefault<Float, 0.08>;
|
|
30
19
|
}
|
|
31
20
|
|
|
32
|
-
export default codegenNativeComponent<
|
|
21
|
+
export default codegenNativeComponent<NativeProps>(
|
|
33
22
|
'BlurVibeView'
|
|
34
|
-
) as HostComponent<
|
|
23
|
+
) as HostComponent<NativeProps>;
|