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.
Files changed (31) hide show
  1. package/android/build.gradle +2 -2
  2. package/android/src/main/java/com/blurvibe/BlurVibeView.kt +101 -192
  3. package/android/src/main/java/com/blurvibe/BlurVibeViewApi31.kt +261 -241
  4. package/android/src/main/java/com/blurvibe/BlurVibeViewManager.kt +77 -28
  5. package/android/src/main/java/com/blurvibe/LegacyBlurController.kt +258 -0
  6. package/ios/BlurVibeView.swift +2 -0
  7. package/ios/BlurVibeViewFabric.mm +112 -0
  8. package/ios/BlurVibeViewManager.m +12 -2
  9. package/ios/BlurVibeViewManager.swift +9 -9
  10. package/lib/commonjs/BlurVibeViewNativeComponent.ts +14 -25
  11. package/lib/commonjs/BlurView.js +9 -30
  12. package/lib/commonjs/BlurView.js.map +1 -1
  13. package/lib/module/BlurVibeViewNativeComponent.ts +14 -25
  14. package/lib/module/BlurView.js +9 -30
  15. package/lib/module/BlurView.js.map +1 -1
  16. package/lib/typescript/commonjs/src/BlurVibeViewNativeComponent.d.ts +11 -9
  17. package/lib/typescript/commonjs/src/BlurVibeViewNativeComponent.d.ts.map +1 -1
  18. package/lib/typescript/commonjs/src/BlurView.d.ts +6 -31
  19. package/lib/typescript/commonjs/src/BlurView.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/src/types.d.ts +26 -1
  21. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  22. package/lib/typescript/module/src/BlurVibeViewNativeComponent.d.ts +11 -9
  23. package/lib/typescript/module/src/BlurVibeViewNativeComponent.d.ts.map +1 -1
  24. package/lib/typescript/module/src/BlurView.d.ts +6 -31
  25. package/lib/typescript/module/src/BlurView.d.ts.map +1 -1
  26. package/lib/typescript/module/src/types.d.ts +26 -1
  27. package/lib/typescript/module/src/types.d.ts.map +1 -1
  28. package/package.json +11 -2
  29. package/src/BlurVibeViewNativeComponent.ts +14 -25
  30. package/src/BlurView.tsx +10 -33
  31. 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.ViewGroupManager
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
- * Extends ViewGroupManager<ViewGroup> so that both BlurVibeView (which extends
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
- * @ReactProp handlers receive ViewGroup and smart-cast via `when`.
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
- * Naming rules to avoid supertype collisions on the VIEW classes:
19
- * Manager method → View method called
20
- * setBlurBorderRadius → applyBorderRadius (ReactViewGroup has setBorderRadius)
21
- * setBlurRadiusProp → setBlurRadius (unique name on BlurVibeView)
22
- * setOverlayColorProp → setOverlayColor (uniquenot in ReactViewGroup)
23
- * setBlurTypeProp → no-op
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
- class BlurVibeViewManager : ViewGroupManager<ViewGroup>() {
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 getName() = "BlurVibeView"
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, @Suppress("UNUSED_PARAMETER") type: String?) {
45
- // iOS UIBlurEffectStyle only — no-op on Android
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) // renamed — avoids ReactViewGroup.setBorderRadius
75
- is BlurVibeView -> view.setBorderRadius(radius)
106
+ is BlurVibeViewApi31 -> view.applyBorderRadius(radius)
107
+ is BlurVibeView -> view.applyBorderRadius(radius)
76
108
  }
77
109
  }
78
110
 
79
- // ── Progressive blur props (API 31+ only) ──────────────────────────────────
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 prop (API 31+ only) ──────────────────────────────────────────────
128
+ // ── Noise (API 31+ only) ──────────────────────────────────────────────────
97
129
 
98
130
  @ReactProp(name = "noiseFactor", defaultFloat = 0.08f)
99
- fun setNoiseFactorProp(view: ViewGroup, factor: Float) {
131
+ override fun setNoiseFactor(view: ViewGroup, factor: Float) {
100
132
  if (view is BlurVibeViewApi31) view.setNoiseFactor(factor)
101
133
  }
102
134
 
103
- override fun needsCustomLayoutForChildren(): Boolean = false
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
+ }
@@ -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) // Android-only, no-op on iOS
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 - internal RN path, exists at runtime
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 - internal RN path, exists at runtime
5
- import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
4
+ // @ts-ignore
5
+ import type { WithDefault, Float, Int32} from 'react-native/Libraries/Types/CodegenTypes';
6
6
 
7
- export interface NativeBlurVibeViewProps extends ViewProps {
8
- // 0–100 blur intensity
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
- // Android API < 31 only: downsample factor 1–8
21
- blurRadius?: Int32;
22
-
23
- // Progressive blur — Android API 31+ only
24
- progressiveBlurDirection?: string;
25
- progressiveStartIntensity?: Float;
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<NativeBlurVibeViewProps>(
21
+ export default codegenNativeComponent<NativeProps>(
33
22
  'BlurVibeView'
34
- ) as HostComponent<NativeBlurVibeViewProps>;
23
+ ) as HostComponent<NativeProps>;