react-native-blur-vibe 0.1.1 → 0.1.3
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 +7 -0
- package/android/src/main/java/com/blurvibe/BlurVibePackage.kt +3 -1
- package/android/src/main/java/com/blurvibe/BlurVibeView.kt +188 -122
- package/android/src/main/java/com/blurvibe/BlurVibeViewManager.kt +18 -12
- package/ios/BlurVibeView.swift +113 -72
- package/ios/BlurVibeViewManager.m +9 -0
- package/ios/BlurVibeViewManager.swift +6 -4
- package/ios/Views/BlurEffectView.swift +85 -0
- package/ios/Views/BlurVibeSwiftUIView.swift +46 -0
- package/ios/react-native-blur-vibe.podspec +4 -2
- package/lib/commonjs/BlurVibeViewNativeComponent.ts +25 -2
- package/lib/module/BlurVibeViewNativeComponent.ts +25 -2
- package/lib/typescript/commonjs/src/BlurVibeViewNativeComponent.d.ts +14 -0
- package/lib/typescript/commonjs/src/BlurVibeViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/BlurVibeViewNativeComponent.d.ts +14 -0
- package/lib/typescript/module/src/BlurVibeViewNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/BlurVibeViewNativeComponent.ts +25 -2
package/android/build.gradle
CHANGED
|
@@ -6,7 +6,9 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
7
|
|
|
8
8
|
class BlurVibePackage : ReactPackage {
|
|
9
|
-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
10
|
+
emptyList()
|
|
11
|
+
|
|
10
12
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
11
13
|
listOf(BlurVibeViewManager())
|
|
12
14
|
}
|
|
@@ -1,174 +1,240 @@
|
|
|
1
1
|
package com.blurvibe
|
|
2
2
|
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
3
|
import android.content.Context
|
|
5
|
-
import android.graphics.Bitmap
|
|
6
|
-
import android.graphics.Canvas
|
|
7
4
|
import android.graphics.Color
|
|
8
|
-
import android.graphics.
|
|
9
|
-
import android.
|
|
10
|
-
import android.os.Build
|
|
11
|
-
import android.renderscript.Allocation
|
|
12
|
-
import android.renderscript.Element
|
|
13
|
-
import android.renderscript.RenderScript
|
|
14
|
-
import android.renderscript.ScriptIntrinsicBlur
|
|
5
|
+
import android.graphics.Outline
|
|
6
|
+
import android.util.TypedValue
|
|
15
7
|
import android.view.View
|
|
16
8
|
import android.view.ViewGroup
|
|
17
|
-
import android.
|
|
9
|
+
import android.view.ViewOutlineProvider
|
|
10
|
+
import android.view.ViewTreeObserver
|
|
11
|
+
import androidx.core.graphics.toColorInt
|
|
12
|
+
import com.qmdeve.blurview.base.BaseBlurViewGroup
|
|
13
|
+
import com.qmdeve.blurview.widget.BlurViewGroup
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
|
-
* BlurVibeView — Android implementation
|
|
16
|
+
* BlurVibeView — Android backdrop blur implementation
|
|
21
17
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Extends QmBlurView's BlurViewGroup — a high-performance blur library
|
|
19
|
+
* that correctly implements CSS backdrop-filter: blur() semantics:
|
|
20
|
+
* - Blurs content BEHIND the view, not the view itself
|
|
21
|
+
* - Hardware accelerated via native blur algorithms
|
|
22
|
+
* - Handles scroll, animation, zIndex, absolute positioning correctly
|
|
23
|
+
* - Never causes draw loops or bitmap capture on the JS thread
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Uses reflection to redirect the blur capture root from the activity
|
|
26
|
+
* decor view to the nearest ReactRootView or react-native-screens Screen,
|
|
27
|
+
* preventing full-screen blur and navigation transition artifacts.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
29
|
+
* Credit: approach adapted from sbaiahmed1/react-native-blur
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
private var
|
|
36
|
-
private var
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
class BlurVibeView(context: Context) : BlurViewGroup(context, null) {
|
|
32
|
+
|
|
33
|
+
private var currentBlurRadius = DEFAULT_BLUR_RADIUS
|
|
34
|
+
private var currentOverlayColor = Color.TRANSPARENT
|
|
35
|
+
private var currentCornerRadius = 0f
|
|
36
|
+
private var isBlurInitialized = false
|
|
37
|
+
|
|
38
|
+
companion object {
|
|
39
|
+
private const val DEFAULT_BLUR_RADIUS = 10f
|
|
40
|
+
private const val MIN_BLUR_AMOUNT = 0f
|
|
41
|
+
private const val MAX_BLUR_AMOUNT = 100f
|
|
42
|
+
private const val MAX_BLUR_RADIUS = 100f
|
|
43
|
+
|
|
44
|
+
// Maps 0–100 blurAmount to 0–25 QmBlurView radius range
|
|
45
|
+
private fun mapBlurAmountToRadius(amount: Float): Float {
|
|
46
|
+
val clamped = amount.coerceIn(MIN_BLUR_AMOUNT, MAX_BLUR_AMOUNT)
|
|
47
|
+
return (clamped / MAX_BLUR_AMOUNT) * MAX_BLUR_RADIUS
|
|
48
|
+
}
|
|
49
|
+
}
|
|
39
50
|
|
|
40
51
|
init {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
applyBlur()
|
|
52
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
53
|
+
clipChildren = true
|
|
54
|
+
clipToOutline = true
|
|
55
|
+
blurRounds = 5
|
|
56
|
+
super.setDownsampleFactor(6.0f)
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
override fun onAttachedToWindow() {
|
|
60
|
+
super.onAttachedToWindow()
|
|
61
|
+
if (isBlurInitialized) return
|
|
62
|
+
swapBlurRootToOptimalAncestor()
|
|
63
|
+
initializeBlur()
|
|
54
64
|
}
|
|
55
65
|
|
|
56
|
-
fun
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
override fun onDetachedFromWindow() {
|
|
67
|
+
super.onDetachedFromWindow()
|
|
68
|
+
isBlurInitialized = false
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Uses reflection to redirect QmBlurView's internal blur capture root
|
|
73
|
+
* from the activity decor view to the nearest Screen or ReactRootView.
|
|
74
|
+
* This prevents the full-screen blur issue when BlurVibeView is used
|
|
75
|
+
* inside a ScrollView or with absolute positioning and zIndex.
|
|
76
|
+
*/
|
|
77
|
+
private fun swapBlurRootToOptimalAncestor() {
|
|
78
|
+
val newRoot = findNearestScreenAncestor() ?: findNearestReactRootView() ?: return
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
try {
|
|
81
|
+
val blurViewGroupClass = BlurViewGroup::class.java
|
|
82
|
+
val baseField = blurViewGroupClass.getDeclaredField("mBaseBlurViewGroup")
|
|
83
|
+
baseField.isAccessible = true
|
|
84
|
+
val baseBlurViewGroup = baseField.get(this) ?: return
|
|
85
|
+
|
|
86
|
+
val baseClass = BaseBlurViewGroup::class.java
|
|
69
87
|
|
|
70
|
-
|
|
88
|
+
val decorViewField = baseClass.getDeclaredField("mDecorView")
|
|
89
|
+
decorViewField.isAccessible = true
|
|
90
|
+
val oldDecorView = decorViewField.get(baseBlurViewGroup) as? View
|
|
91
|
+
|
|
92
|
+
val preDrawListenerField = baseClass.getDeclaredField("preDrawListener")
|
|
93
|
+
preDrawListenerField.isAccessible = true
|
|
94
|
+
val preDrawListener = preDrawListenerField.get(baseBlurViewGroup)
|
|
95
|
+
as? ViewTreeObserver.OnPreDrawListener
|
|
96
|
+
|
|
97
|
+
if (oldDecorView != null && preDrawListener != null) {
|
|
98
|
+
// Remove listener from old root
|
|
99
|
+
oldDecorView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
100
|
+
|
|
101
|
+
// Set new root
|
|
102
|
+
decorViewField.set(baseBlurViewGroup, newRoot)
|
|
103
|
+
|
|
104
|
+
// Add listener to new root
|
|
105
|
+
newRoot.viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
|
106
|
+
|
|
107
|
+
// Update mDifferentRoot flag
|
|
108
|
+
val differentRootField = baseClass.getDeclaredField("mDifferentRoot")
|
|
109
|
+
differentRootField.isAccessible = true
|
|
110
|
+
differentRootField.setBoolean(baseBlurViewGroup, newRoot.rootView != this.rootView)
|
|
111
|
+
|
|
112
|
+
// Force redraw
|
|
113
|
+
val forceRedrawField = baseClass.getDeclaredField("mForceRedraw")
|
|
114
|
+
forceRedrawField.isAccessible = true
|
|
115
|
+
forceRedrawField.setBoolean(baseBlurViewGroup, true)
|
|
116
|
+
}
|
|
117
|
+
} catch (e: Exception) {
|
|
118
|
+
// Reflection failed — QmBlurView internals changed
|
|
119
|
+
// Fall back gracefully to default decor view blur root
|
|
120
|
+
}
|
|
121
|
+
}
|
|
71
122
|
|
|
72
|
-
private fun
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
123
|
+
private fun findNearestScreenAncestor(): ViewGroup? {
|
|
124
|
+
var current = parent
|
|
125
|
+
while (current != null) {
|
|
126
|
+
if (current.javaClass.name == "com.swmansion.rnscreens.Screen") {
|
|
127
|
+
return current as? ViewGroup
|
|
128
|
+
}
|
|
129
|
+
current = current.parent
|
|
77
130
|
}
|
|
78
|
-
|
|
131
|
+
return null
|
|
79
132
|
}
|
|
80
133
|
|
|
81
|
-
private fun
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
134
|
+
private fun findNearestReactRootView(): ViewGroup? {
|
|
135
|
+
var current = parent
|
|
136
|
+
while (current != null) {
|
|
137
|
+
if (current.javaClass.name == "com.facebook.react.ReactRootView") {
|
|
138
|
+
return current as? ViewGroup
|
|
139
|
+
}
|
|
140
|
+
current = current.parent
|
|
87
141
|
}
|
|
142
|
+
return null
|
|
88
143
|
}
|
|
89
144
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
val parentView = parent as? ViewGroup ?: return
|
|
93
|
-
if (width <= 0 || height <= 0) return
|
|
145
|
+
private fun initializeBlur() {
|
|
146
|
+
if (isBlurInitialized) return
|
|
94
147
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
canvas.scale(1f / blurRadiusDownscale, 1f / blurRadiusDownscale)
|
|
100
|
-
canvas.translate(-left.toFloat(), -top.toFloat())
|
|
101
|
-
parentView.draw(canvas)
|
|
102
|
-
|
|
103
|
-
val rs = RenderScript.create(context)
|
|
104
|
-
val input = Allocation.createFromBitmap(rs, bitmap)
|
|
105
|
-
val output = Allocation.createTyped(rs, input.type)
|
|
106
|
-
val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
|
|
107
|
-
val sigma = (blurAmountValue / 100f * 25f).coerceIn(1f, 25f)
|
|
108
|
-
script.setRadius(sigma)
|
|
109
|
-
script.setInput(input)
|
|
110
|
-
script.forEach(output)
|
|
111
|
-
output.copyTo(bitmap)
|
|
112
|
-
rs.destroy()
|
|
113
|
-
|
|
114
|
-
background = android.graphics.drawable.BitmapDrawable(resources, bitmap)
|
|
148
|
+
super.setBlurRadius(currentBlurRadius)
|
|
149
|
+
super.setOverlayColor(currentOverlayColor)
|
|
150
|
+
updateCornerRadius()
|
|
151
|
+
isBlurInitialized = true
|
|
115
152
|
} catch (e: Exception) {
|
|
116
|
-
|
|
153
|
+
// Ignore — view may not be fully attached yet
|
|
117
154
|
}
|
|
118
155
|
}
|
|
119
156
|
|
|
120
|
-
// MARK: -
|
|
157
|
+
// MARK: - Public setters
|
|
121
158
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
159
|
+
fun setBlurAmount(amount: Float) {
|
|
160
|
+
currentBlurRadius = mapBlurAmountToRadius(amount)
|
|
161
|
+
try { super.setBlurRadius(currentBlurRadius) } catch (e: Exception) {}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fun setOverlayColor(colorString: String?) {
|
|
165
|
+
currentOverlayColor = parseHexColor(colorString ?: "transparent") ?: Color.TRANSPARENT
|
|
166
|
+
try {
|
|
167
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
168
|
+
super.setOverlayColor(currentOverlayColor)
|
|
169
|
+
} catch (e: Exception) {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fun setReducedTransparencyFallbackColor(colorString: String?) {
|
|
173
|
+
// Stored for future use — QmBlurView handles accessibility fallback internally
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fun setBlurRadius(radius: Int) {
|
|
177
|
+
// blurRadius is the Android downscale factor — map to QmBlurView's downsample factor
|
|
178
|
+
val downsample = radius.coerceIn(1, 8).toFloat()
|
|
179
|
+
try { super.setDownsampleFactor(downsample) } catch (e: Exception) {}
|
|
129
180
|
}
|
|
130
181
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
182
|
+
fun setBorderRadius(radius: Float) {
|
|
183
|
+
currentCornerRadius = radius
|
|
184
|
+
updateCornerRadius()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun updateCornerRadius() {
|
|
188
|
+
val radiusPx = TypedValue.applyDimension(
|
|
189
|
+
TypedValue.COMPLEX_UNIT_DIP,
|
|
190
|
+
currentCornerRadius,
|
|
191
|
+
context.resources.displayMetrics
|
|
192
|
+
)
|
|
193
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
194
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
195
|
+
outline.setRoundRect(0, 0, view.width, view.height, radiusPx)
|
|
196
|
+
}
|
|
135
197
|
}
|
|
198
|
+
clipToOutline = true
|
|
199
|
+
try { super.setCornerRadius(radiusPx) } catch (e: Exception) {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// React Native handles layout — prevent superclass from interfering
|
|
203
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
204
|
+
// No-op: layout handled by React Native's Yoga engine
|
|
136
205
|
}
|
|
137
206
|
|
|
138
207
|
// MARK: - Color parser
|
|
139
208
|
// Supports: "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
140
|
-
// Returns null if unparseable (caller uses fallback)
|
|
141
209
|
private fun parseHexColor(colorString: String): Int? {
|
|
142
210
|
val s = colorString.trim()
|
|
143
211
|
if (s.equals("transparent", ignoreCase = true)) return Color.TRANSPARENT
|
|
144
|
-
if (!s.startsWith("#"))
|
|
145
|
-
|
|
212
|
+
if (!s.startsWith("#")) {
|
|
213
|
+
return try { s.toColorInt() } catch (e: Exception) { null }
|
|
214
|
+
}
|
|
146
215
|
val hex = s.removePrefix("#")
|
|
147
216
|
return try {
|
|
148
217
|
when (hex.length) {
|
|
149
|
-
3 ->
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
6 ->
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
8 ->
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
218
|
+
3 -> Color.argb(
|
|
219
|
+
255,
|
|
220
|
+
hex[0].toString().repeat(2).toInt(16),
|
|
221
|
+
hex[1].toString().repeat(2).toInt(16),
|
|
222
|
+
hex[2].toString().repeat(2).toInt(16)
|
|
223
|
+
)
|
|
224
|
+
6 -> Color.argb(
|
|
225
|
+
255,
|
|
226
|
+
hex.substring(0, 2).toInt(16),
|
|
227
|
+
hex.substring(2, 4).toInt(16),
|
|
228
|
+
hex.substring(4, 6).toInt(16)
|
|
229
|
+
)
|
|
230
|
+
8 -> Color.argb(
|
|
231
|
+
hex.substring(6, 8).toInt(16), // AA is last in #RRGGBBAA
|
|
232
|
+
hex.substring(0, 2).toInt(16),
|
|
233
|
+
hex.substring(2, 4).toInt(16),
|
|
234
|
+
hex.substring(4, 6).toInt(16)
|
|
235
|
+
)
|
|
168
236
|
else -> null
|
|
169
237
|
}
|
|
170
|
-
} catch (e: NumberFormatException) {
|
|
171
|
-
null
|
|
172
|
-
}
|
|
238
|
+
} catch (e: NumberFormatException) { null }
|
|
173
239
|
}
|
|
174
240
|
}
|
|
@@ -1,44 +1,50 @@
|
|
|
1
1
|
package com.blurvibe
|
|
2
2
|
|
|
3
|
-
import com.facebook.react.uimanager.SimpleViewManager
|
|
4
3
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
4
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
5
5
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* BlurVibeViewManager
|
|
9
|
+
*
|
|
10
|
+
* ViewGroupManager — BlurVibeView (which extends BlurViewGroup/FrameLayout)
|
|
11
|
+
* hosts React children, so we must use ViewGroupManager, not SimpleViewManager.
|
|
12
|
+
*/
|
|
13
|
+
class BlurVibeViewManager : ViewGroupManager<BlurVibeView>() {
|
|
8
14
|
|
|
9
15
|
override fun getName() = "BlurVibeView"
|
|
10
16
|
|
|
11
17
|
override fun createViewInstance(context: ThemedReactContext) = BlurVibeView(context)
|
|
12
18
|
|
|
13
|
-
// Float — matches TS codegen Float type ✅
|
|
14
19
|
@ReactProp(name = "blurAmount", defaultFloat = 10f)
|
|
15
20
|
fun setBlurAmount(view: BlurVibeView, amount: Float) {
|
|
16
21
|
view.setBlurAmount(amount)
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
// String — matches TS codegen string type ✅ (no-op on Android)
|
|
20
24
|
@ReactProp(name = "blurType")
|
|
21
25
|
fun setBlurType(view: BlurVibeView, type: String?) {
|
|
22
|
-
// No-op on Android — blurType
|
|
26
|
+
// No-op on Android — blurType maps to iOS UIBlurEffectStyle only
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
// String — matches TS codegen string type ✅
|
|
26
|
-
// We parse hex manually in BlurVibeView for full alpha control
|
|
27
|
-
// Do NOT use Int with customType="Color" — RN reorders alpha bytes unexpectedly
|
|
28
29
|
@ReactProp(name = "overlayColor")
|
|
29
30
|
fun setOverlayColor(view: BlurVibeView, color: String?) {
|
|
30
|
-
view.setOverlayColor(color
|
|
31
|
+
view.setOverlayColor(color)
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
// String — matches TS codegen string type ✅
|
|
34
34
|
@ReactProp(name = "reducedTransparencyFallbackColor")
|
|
35
35
|
fun setReducedTransparencyFallbackColor(view: BlurVibeView, color: String?) {
|
|
36
|
-
view.setReducedTransparencyFallbackColor(color
|
|
36
|
+
view.setReducedTransparencyFallbackColor(color)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Int32 — matches TS codegen Int32 type ✅
|
|
40
39
|
@ReactProp(name = "blurRadius", defaultInt = 4)
|
|
41
40
|
fun setBlurRadius(view: BlurVibeView, radius: Int) {
|
|
42
41
|
view.setBlurRadius(radius)
|
|
43
42
|
}
|
|
43
|
+
|
|
44
|
+
override fun onDropViewInstance(view: BlurVibeView) {
|
|
45
|
+
super.onDropViewInstance(view)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// React Native's Yoga handles child layout — return false
|
|
49
|
+
override fun needsCustomLayoutForChildren(): Boolean = false
|
|
44
50
|
}
|
package/ios/BlurVibeView.swift
CHANGED
|
@@ -1,92 +1,106 @@
|
|
|
1
|
+
// BlurVibeView.swift
|
|
2
|
+
// UIKit wrapper that hosts the SwiftUI blur view via UIHostingController.
|
|
3
|
+
// Approach mirrors sbaiahmed1/react-native-blur's AdvancedBlurView.
|
|
4
|
+
|
|
5
|
+
import SwiftUI
|
|
1
6
|
import UIKit
|
|
2
7
|
|
|
3
8
|
@objc(BlurVibeView)
|
|
4
9
|
class BlurVibeView: UIView {
|
|
5
10
|
|
|
6
|
-
// MARK: - Private
|
|
7
|
-
private var blurEffectView: UIVisualEffectView?
|
|
8
|
-
private let overlayView = UIView()
|
|
11
|
+
// MARK: - Private
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
private var hostingController: UIHostingController<BlurVibeSwiftUIView>?
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
@objc var blurAmount: NSNumber = 10 { didSet { updateBlur() } }
|
|
15
|
+
// MARK: - Props
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
@objc var blurType: NSString = "light" { didSet {
|
|
17
|
+
@objc var blurAmount: NSNumber = 10 { didSet { updateView() } }
|
|
18
|
+
@objc var blurType: NSString = "light" { didSet { updateView() } }
|
|
17
19
|
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
|
|
21
|
-
@objc var overlayColor: NSString = "transparent" { didSet { updateOverlay() } }
|
|
20
|
+
/// Hex overlay color on top of blur — works on iOS AND Android.
|
|
21
|
+
/// "#00000000" = transparent (pure blur), "#00000080" = tinted blur
|
|
22
|
+
@objc var overlayColor: NSString = "transparent" { didSet { updateView() } }
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
@objc var reducedTransparencyFallbackColor: NSString = "#F2F2F2" { didSet { updateBlur() } }
|
|
24
|
+
@objc var reducedTransparencyFallbackColor: NSString = "#F2F2F2" { didSet { updateView() } }
|
|
25
25
|
|
|
26
|
-
/// Android-only downscale factor
|
|
26
|
+
/// Android-only downscale factor — accepted here as no-op to avoid prop warning
|
|
27
27
|
@objc var blurRadius: NSNumber = 4
|
|
28
28
|
|
|
29
29
|
// MARK: - Init
|
|
30
|
-
override init(frame: CGRect) { super.init(frame: frame); commonInit() }
|
|
31
|
-
required init?(coder: NSCoder) { super.init(coder: coder); commonInit() }
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
override init(frame: CGRect) {
|
|
32
|
+
super.init(frame: frame)
|
|
33
|
+
backgroundColor = .clear
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
required init?(coder: NSCoder) {
|
|
37
|
+
super.init(coder: coder)
|
|
34
38
|
backgroundColor = .clear
|
|
35
|
-
clipsToBounds = true
|
|
36
|
-
overlayView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
37
|
-
overlayView.isUserInteractionEnabled = false
|
|
38
|
-
updateBlur()
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// MARK: - Layout
|
|
42
|
+
|
|
42
43
|
override func layoutSubviews() {
|
|
43
44
|
super.layoutSubviews()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
// Defer hosting controller setup until we have a valid frame
|
|
46
|
+
// Prevents issues with initial render in complex layouts (e.g. FlashList)
|
|
47
|
+
if hostingController == nil && bounds.width > 0 && bounds.height > 0 {
|
|
48
|
+
setupHostingController()
|
|
49
|
+
} else {
|
|
50
|
+
hostingController?.view.frame = bounds
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
// MARK: -
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
54
|
+
// MARK: - Hosting Controller
|
|
55
|
+
|
|
56
|
+
private func setupHostingController() {
|
|
57
|
+
// Remove existing hosting controller cleanly
|
|
58
|
+
if let old = hostingController {
|
|
59
|
+
old.view.removeFromSuperview()
|
|
60
|
+
old.removeFromParent()
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
hostingController = nil
|
|
63
|
+
|
|
64
|
+
let swiftUIView = makeSwiftUIView()
|
|
65
|
+
let hosting = UIHostingController(rootView: swiftUIView)
|
|
66
|
+
hosting.view.backgroundColor = .clear
|
|
67
|
+
hosting.view.frame = bounds
|
|
68
|
+
hosting.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
69
|
+
|
|
70
|
+
// Insert at index 0 — stays behind React children
|
|
71
|
+
if !subviews.isEmpty {
|
|
72
|
+
insertSubview(hosting.view, at: 0)
|
|
65
73
|
} else {
|
|
66
|
-
|
|
67
|
-
newBlurView.frame = bounds
|
|
68
|
-
newBlurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
69
|
-
blurEffectView = newBlurView
|
|
70
|
-
insertSubview(newBlurView, at: 0)
|
|
71
|
-
}
|
|
72
|
-
if overlayView.superview == nil {
|
|
73
|
-
insertSubview(overlayView, aboveSubview: blurEffectView!)
|
|
74
|
+
addSubview(hosting.view)
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
+
|
|
77
|
+
hostingController = hosting
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
private func
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} else {
|
|
83
|
-
|
|
80
|
+
private func updateView() {
|
|
81
|
+
if let hosting = hostingController {
|
|
82
|
+
// Update root view without recreating the controller — avoids jank
|
|
83
|
+
hosting.rootView = makeSwiftUIView()
|
|
84
|
+
} else if bounds.width > 0 && bounds.height > 0 {
|
|
85
|
+
setupHostingController()
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
private func makeSwiftUIView() -> BlurVibeSwiftUIView {
|
|
90
|
+
return BlurVibeSwiftUIView(
|
|
91
|
+
blurAmount: Double(truncating: blurAmount),
|
|
92
|
+
blurStyle: blurStyleFromString(blurType as String),
|
|
93
|
+
overlayColor: parseColor(overlayColor as String) ?? .clear,
|
|
94
|
+
reducedTransparencyFallbackColor: parseColor(reducedTransparencyFallbackColor as String)
|
|
95
|
+
?? UIColor(white: 0.95, alpha: 1)
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
// MARK: - Blur Style Map
|
|
88
|
-
|
|
100
|
+
|
|
101
|
+
private func blurStyleFromString(_ type: String) -> UIBlurEffect.Style {
|
|
89
102
|
switch type {
|
|
103
|
+
case "xlight": return .extraLight
|
|
90
104
|
case "dark": return .dark
|
|
91
105
|
case "extraLight": return .extraLight
|
|
92
106
|
case "regular": return .regular
|
|
@@ -111,34 +125,61 @@ class BlurVibeView: UIView {
|
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
// MARK: - Color Parser
|
|
114
|
-
// Supports: "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
128
|
+
// Supports: "transparent", named colors, "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
129
|
+
|
|
115
130
|
private func parseColor(_ colorString: String) -> UIColor? {
|
|
131
|
+
let s = colorString.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
132
|
+
|
|
133
|
+
// Named colors
|
|
134
|
+
let namedColors: [String: UIColor] = [
|
|
135
|
+
"transparent": .clear, "clear": .clear,
|
|
136
|
+
"white": .white, "black": .black,
|
|
137
|
+
"red": .red, "green": .green, "blue": .blue,
|
|
138
|
+
"gray": .gray, "grey": .gray,
|
|
139
|
+
]
|
|
140
|
+
if let named = namedColors[s] { return named }
|
|
141
|
+
|
|
142
|
+
// Hex colors
|
|
116
143
|
var hex = colorString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
117
144
|
guard hex.hasPrefix("#") else { return nil }
|
|
118
145
|
hex.removeFirst()
|
|
146
|
+
|
|
147
|
+
// Validate hex chars
|
|
148
|
+
let validHex = CharacterSet(charactersIn: "0123456789ABCDEFabcdef")
|
|
149
|
+
guard hex.unicodeScalars.allSatisfy({ validHex.contains($0) }) else { return nil }
|
|
150
|
+
|
|
119
151
|
var rgbValue: UInt64 = 0
|
|
120
152
|
Scanner(string: hex).scanHexInt64(&rgbValue)
|
|
153
|
+
|
|
121
154
|
switch hex.count {
|
|
122
|
-
case 3: // #RGB
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
case 3: // #RGB → expand
|
|
156
|
+
let r = (rgbValue & 0xF00) >> 8; let g = (rgbValue & 0x0F0) >> 4; let b = rgbValue & 0x00F
|
|
157
|
+
return UIColor(red: CGFloat(r | (r << 4)) / 255, green: CGFloat(g | (g << 4)) / 255,
|
|
158
|
+
blue: CGFloat(b | (b << 4)) / 255, alpha: 1)
|
|
159
|
+
case 4: // #RGBA → expand
|
|
160
|
+
let r = (rgbValue & 0xF000) >> 12; let g = (rgbValue & 0x0F00) >> 8
|
|
161
|
+
let b = (rgbValue & 0x00F0) >> 4; let a = rgbValue & 0x000F
|
|
162
|
+
return UIColor(red: CGFloat(r | (r << 4)) / 255, green: CGFloat(g | (g << 4)) / 255,
|
|
163
|
+
blue: CGFloat(b | (b << 4)) / 255, alpha: CGFloat(a | (a << 4)) / 255)
|
|
128
164
|
case 6: // #RRGGBB
|
|
129
|
-
return UIColor(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
blue: CGFloat( rgbValue & 0x0000FF ) / 255,
|
|
133
|
-
alpha: 1)
|
|
165
|
+
return UIColor(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255,
|
|
166
|
+
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255,
|
|
167
|
+
blue: CGFloat(rgbValue & 0x0000FF) / 255, alpha: 1)
|
|
134
168
|
case 8: // #RRGGBBAA
|
|
135
|
-
return UIColor(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
alpha: CGFloat( rgbValue & 0x000000FF ) / 255)
|
|
169
|
+
return UIColor(red: CGFloat((rgbValue & 0xFF000000) >> 24) / 255,
|
|
170
|
+
green: CGFloat((rgbValue & 0x00FF0000) >> 16) / 255,
|
|
171
|
+
blue: CGFloat((rgbValue & 0x0000FF00) >> 8) / 255,
|
|
172
|
+
alpha: CGFloat(rgbValue & 0x000000FF) / 255)
|
|
140
173
|
default:
|
|
141
174
|
return nil
|
|
142
175
|
}
|
|
143
176
|
}
|
|
177
|
+
|
|
178
|
+
// MARK: - Cleanup
|
|
179
|
+
|
|
180
|
+
deinit {
|
|
181
|
+
hostingController?.view.removeFromSuperview()
|
|
182
|
+
hostingController?.removeFromParent()
|
|
183
|
+
hostingController = nil
|
|
184
|
+
}
|
|
144
185
|
}
|
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
RCT_EXTERN_MODULE(BlurVibeViewManager, RCTViewManager)
|
|
5
5
|
|
|
6
|
+
// Float → NSNumber matches TS Float
|
|
6
7
|
RCT_EXPORT_VIEW_PROPERTY(blurAmount, NSNumber)
|
|
8
|
+
|
|
9
|
+
// String → NSString matches TS string
|
|
7
10
|
RCT_EXPORT_VIEW_PROPERTY(blurType, NSString)
|
|
11
|
+
|
|
12
|
+
// String → NSString matches TS string
|
|
8
13
|
RCT_EXPORT_VIEW_PROPERTY(overlayColor, NSString)
|
|
14
|
+
|
|
15
|
+
// String → NSString matches TS string
|
|
9
16
|
RCT_EXPORT_VIEW_PROPERTY(reducedTransparencyFallbackColor, NSString)
|
|
17
|
+
|
|
18
|
+
// Int32 → NSNumber matches TS Int32 (no-op in Swift)
|
|
10
19
|
RCT_EXPORT_VIEW_PROPERTY(blurRadius, NSNumber)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* BlurVibeViewManager
|
|
5
|
+
*
|
|
6
|
+
* RCTViewManager subclass — registers BlurVibeView with React Native.
|
|
7
|
+
* requiresMainQueueSetup = true because we create UIKit views.
|
|
8
|
+
*/
|
|
3
9
|
@objc(BlurVibeViewManager)
|
|
4
10
|
class BlurVibeViewManager: RCTViewManager {
|
|
5
11
|
|
|
@@ -10,8 +16,4 @@ class BlurVibeViewManager: RCTViewManager {
|
|
|
10
16
|
override static func requiresMainQueueSetup() -> Bool {
|
|
11
17
|
return true
|
|
12
18
|
}
|
|
13
|
-
|
|
14
|
-
@objc override func constantsToExport() -> [AnyHashable: Any]! {
|
|
15
|
-
return [:]
|
|
16
|
-
}
|
|
17
19
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// BlurEffectView.swift
|
|
2
|
+
// UIVisualEffectView with UIViewPropertyAnimator for custom blur intensity.
|
|
3
|
+
// This is the ONLY correct way to achieve custom blur radius on iOS.
|
|
4
|
+
// UIBlurEffect itself ignores any custom intensity — the animator interpolates it.
|
|
5
|
+
|
|
6
|
+
import SwiftUI
|
|
7
|
+
import UIKit
|
|
8
|
+
|
|
9
|
+
// MARK: - UIKit blur with custom intensity
|
|
10
|
+
|
|
11
|
+
class BlurEffectView: UIVisualEffectView {
|
|
12
|
+
private var animator: UIViewPropertyAnimator?
|
|
13
|
+
private var blurStyle: UIBlurEffect.Style = .systemMaterial
|
|
14
|
+
private var intensity: Double = 1.0
|
|
15
|
+
|
|
16
|
+
override init(effect: UIVisualEffect?) {
|
|
17
|
+
super.init(effect: effect)
|
|
18
|
+
setupBlur()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
required init?(coder: NSCoder) {
|
|
22
|
+
super.init(coder: coder)
|
|
23
|
+
setupBlur()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func updateBlur(style: UIBlurEffect.Style, intensity: Double) {
|
|
27
|
+
// Skip expensive animator recreation when nothing changed
|
|
28
|
+
guard style != self.blurStyle || intensity != self.intensity else { return }
|
|
29
|
+
self.blurStyle = style
|
|
30
|
+
self.intensity = intensity
|
|
31
|
+
setupBlur()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override func didMoveToWindow() {
|
|
35
|
+
super.didMoveToWindow()
|
|
36
|
+
guard window != nil else { return }
|
|
37
|
+
// UIKit resumes paused CAAnimations when view re-joins a window.
|
|
38
|
+
// Re-pause and re-lock the fraction to prevent blur drifting to full intensity.
|
|
39
|
+
animator?.pauseAnimation()
|
|
40
|
+
animator?.fractionComplete = intensity
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func setupBlur() {
|
|
44
|
+
if let existing = animator, existing.state == .active {
|
|
45
|
+
existing.stopAnimation(true)
|
|
46
|
+
}
|
|
47
|
+
animator = nil
|
|
48
|
+
effect = nil
|
|
49
|
+
|
|
50
|
+
let newAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
|
|
51
|
+
newAnimator.addAnimations { [weak self] in
|
|
52
|
+
self?.effect = UIBlurEffect(style: self?.blurStyle ?? .systemMaterial)
|
|
53
|
+
}
|
|
54
|
+
// pausesOnCompletion: keeps animator .active even at fraction 1.0
|
|
55
|
+
// so didMoveToWindow can always safely call pauseAnimation()
|
|
56
|
+
newAnimator.pausesOnCompletion = true
|
|
57
|
+
newAnimator.startAnimation()
|
|
58
|
+
newAnimator.pauseAnimation()
|
|
59
|
+
newAnimator.fractionComplete = intensity
|
|
60
|
+
animator = newAnimator
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
deinit {
|
|
64
|
+
if let animator = animator, animator.state == .active {
|
|
65
|
+
animator.stopAnimation(true)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// MARK: - SwiftUI wrapper for BlurEffectView
|
|
71
|
+
|
|
72
|
+
struct BlurVibeEffect: UIViewRepresentable {
|
|
73
|
+
var style: UIBlurEffect.Style = .systemMaterial
|
|
74
|
+
var intensity: Double = 1.0
|
|
75
|
+
|
|
76
|
+
func makeUIView(context: Context) -> BlurEffectView {
|
|
77
|
+
let view = BlurEffectView(effect: nil)
|
|
78
|
+
view.updateBlur(style: style, intensity: intensity)
|
|
79
|
+
return view
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func updateUIView(_ uiView: BlurEffectView, context: Context) {
|
|
83
|
+
uiView.updateBlur(style: style, intensity: intensity)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// BlurVibeSwiftUIView.swift
|
|
2
|
+
// SwiftUI view that composes blur effect + overlay color.
|
|
3
|
+
|
|
4
|
+
import SwiftUI
|
|
5
|
+
import UIKit
|
|
6
|
+
|
|
7
|
+
struct BlurVibeSwiftUIView: View {
|
|
8
|
+
let blurAmount: Double
|
|
9
|
+
let blurStyle: UIBlurEffect.Style
|
|
10
|
+
let overlayColor: UIColor
|
|
11
|
+
let reducedTransparencyFallbackColor: UIColor
|
|
12
|
+
|
|
13
|
+
private let isReducedTransparencyEnabled = UIAccessibility.isReduceTransparencyEnabled
|
|
14
|
+
|
|
15
|
+
// Map 0–100 blurAmount to 0.0–1.0 animator fraction
|
|
16
|
+
private var blurIntensity: Double {
|
|
17
|
+
(blurAmount / 100.0).clamped(to: 0.0...1.0)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var body: some View {
|
|
21
|
+
if isReducedTransparencyEnabled {
|
|
22
|
+
// Accessibility: Reduce Transparency is ON — show solid fallback color
|
|
23
|
+
Rectangle()
|
|
24
|
+
.fill(Color(reducedTransparencyFallbackColor))
|
|
25
|
+
} else {
|
|
26
|
+
ZStack {
|
|
27
|
+
// Layer 1: backdrop blur (what's behind this view gets blurred)
|
|
28
|
+
BlurVibeEffect(style: blurStyle, intensity: blurIntensity)
|
|
29
|
+
|
|
30
|
+
// Layer 2: overlay color with alpha on top of blur
|
|
31
|
+
// This is our overlayColor prop — same as CSS background-color with alpha
|
|
32
|
+
// "#00000000" = transparent = pure blur shows through
|
|
33
|
+
// "#00000080" = 50% black tint over blur
|
|
34
|
+
// "#000000FF" = fully opaque = blur hidden
|
|
35
|
+
Rectangle()
|
|
36
|
+
.fill(Color(overlayColor))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
extension Comparable {
|
|
43
|
+
func clamped(to range: ClosedRange<Self>) -> Self {
|
|
44
|
+
min(max(self, range.lowerBound), range.upperBound)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -9,13 +9,15 @@ Pod::Spec.new do |s|
|
|
|
9
9
|
s.homepage = package["homepage"]
|
|
10
10
|
s.license = package["license"]
|
|
11
11
|
s.authors = package["author"]
|
|
12
|
-
s.platforms = { :ios => "
|
|
12
|
+
s.platforms = { :ios => "14.0" }
|
|
13
13
|
s.source = { :git => "https://github.com/I-am-Pritam-20/react-native-blur-vibe.git", :tag => "#{s.version}" }
|
|
14
|
+
|
|
15
|
+
# Include all Swift and ObjC files in ios/ and ios/Views/
|
|
14
16
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
+
|
|
15
18
|
s.requires_arc = true
|
|
16
19
|
|
|
17
20
|
s.dependency "React-Core"
|
|
18
21
|
|
|
19
|
-
# New Architecture (Fabric) support
|
|
20
22
|
install_modules_dependencies(s)
|
|
21
23
|
end
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
// @ts-ignore -
|
|
1
|
+
// @ts-ignore - internal RN path, exists at runtime
|
|
2
2
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
3
3
|
import type { HostComponent, ViewProps } from 'react-native';
|
|
4
|
-
// @ts-ignore -
|
|
4
|
+
// @ts-ignore - internal RN path, exists at runtime
|
|
5
5
|
import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* NativeComponent codegen spec for BlurVibeView.
|
|
9
|
+
*
|
|
10
|
+
* Type mapping (JS → Native):
|
|
11
|
+
* Float → NSNumber (iOS) / Float (Android)
|
|
12
|
+
* string → NSString (iOS) / String (Android)
|
|
13
|
+
* Int32 → NSNumber (iOS) / Int (Android)
|
|
14
|
+
*
|
|
15
|
+
* Color props (overlayColor, reducedTransparencyFallbackColor) use
|
|
16
|
+
* plain `string` — NOT the RN `ColorValue` type — because we parse
|
|
17
|
+
* hex manually on both platforms for full alpha channel control.
|
|
18
|
+
* Using ColorValue would trigger RN's color normalization which
|
|
19
|
+
* reorders alpha bytes and breaks #RRGGBBAA format.
|
|
20
|
+
*/
|
|
7
21
|
export interface NativeBlurVibeViewProps extends ViewProps {
|
|
22
|
+
// 0–100 blur intensity
|
|
8
23
|
blurAmount?: Float;
|
|
24
|
+
|
|
25
|
+
// iOS UIBlurEffectStyle name — no-op on Android
|
|
9
26
|
blurType?: string;
|
|
27
|
+
|
|
28
|
+
// Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
10
29
|
overlayColor?: string;
|
|
30
|
+
|
|
31
|
+
// Fallback when blur unavailable (Reduce Transparency / old API)
|
|
11
32
|
reducedTransparencyFallbackColor?: string;
|
|
33
|
+
|
|
34
|
+
// Android downscale factor 1–8 — no-op on iOS
|
|
12
35
|
blurRadius?: Int32;
|
|
13
36
|
}
|
|
14
37
|
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
// @ts-ignore -
|
|
1
|
+
// @ts-ignore - internal RN path, exists at runtime
|
|
2
2
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
3
3
|
import type { HostComponent, ViewProps } from 'react-native';
|
|
4
|
-
// @ts-ignore -
|
|
4
|
+
// @ts-ignore - internal RN path, exists at runtime
|
|
5
5
|
import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* NativeComponent codegen spec for BlurVibeView.
|
|
9
|
+
*
|
|
10
|
+
* Type mapping (JS → Native):
|
|
11
|
+
* Float → NSNumber (iOS) / Float (Android)
|
|
12
|
+
* string → NSString (iOS) / String (Android)
|
|
13
|
+
* Int32 → NSNumber (iOS) / Int (Android)
|
|
14
|
+
*
|
|
15
|
+
* Color props (overlayColor, reducedTransparencyFallbackColor) use
|
|
16
|
+
* plain `string` — NOT the RN `ColorValue` type — because we parse
|
|
17
|
+
* hex manually on both platforms for full alpha channel control.
|
|
18
|
+
* Using ColorValue would trigger RN's color normalization which
|
|
19
|
+
* reorders alpha bytes and breaks #RRGGBBAA format.
|
|
20
|
+
*/
|
|
7
21
|
export interface NativeBlurVibeViewProps extends ViewProps {
|
|
22
|
+
// 0–100 blur intensity
|
|
8
23
|
blurAmount?: Float;
|
|
24
|
+
|
|
25
|
+
// iOS UIBlurEffectStyle name — no-op on Android
|
|
9
26
|
blurType?: string;
|
|
27
|
+
|
|
28
|
+
// Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
10
29
|
overlayColor?: string;
|
|
30
|
+
|
|
31
|
+
// Fallback when blur unavailable (Reduce Transparency / old API)
|
|
11
32
|
reducedTransparencyFallbackColor?: string;
|
|
33
|
+
|
|
34
|
+
// Android downscale factor 1–8 — no-op on iOS
|
|
12
35
|
blurRadius?: Int32;
|
|
13
36
|
}
|
|
14
37
|
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { HostComponent, ViewProps } from 'react-native';
|
|
2
2
|
import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
/**
|
|
4
|
+
* NativeComponent codegen spec for BlurVibeView.
|
|
5
|
+
*
|
|
6
|
+
* Type mapping (JS → Native):
|
|
7
|
+
* Float → NSNumber (iOS) / Float (Android)
|
|
8
|
+
* string → NSString (iOS) / String (Android)
|
|
9
|
+
* Int32 → NSNumber (iOS) / Int (Android)
|
|
10
|
+
*
|
|
11
|
+
* Color props (overlayColor, reducedTransparencyFallbackColor) use
|
|
12
|
+
* plain `string` — NOT the RN `ColorValue` type — because we parse
|
|
13
|
+
* hex manually on both platforms for full alpha channel control.
|
|
14
|
+
* Using ColorValue would trigger RN's color normalization which
|
|
15
|
+
* reorders alpha bytes and breaks #RRGGBBAA format.
|
|
16
|
+
*/
|
|
3
17
|
export interface NativeBlurVibeViewProps extends ViewProps {
|
|
4
18
|
blurAmount?: Float;
|
|
5
19
|
blurType?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,uBAAwB,SAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IAExD,UAAU,CAAC,EAAE,KAAK,CAAC;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAG1C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;wBAII,aAAa,CAAC,uBAAuB,CAAC;AAF3C,wBAE4C"}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { HostComponent, ViewProps } from 'react-native';
|
|
2
2
|
import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
/**
|
|
4
|
+
* NativeComponent codegen spec for BlurVibeView.
|
|
5
|
+
*
|
|
6
|
+
* Type mapping (JS → Native):
|
|
7
|
+
* Float → NSNumber (iOS) / Float (Android)
|
|
8
|
+
* string → NSString (iOS) / String (Android)
|
|
9
|
+
* Int32 → NSNumber (iOS) / Int (Android)
|
|
10
|
+
*
|
|
11
|
+
* Color props (overlayColor, reducedTransparencyFallbackColor) use
|
|
12
|
+
* plain `string` — NOT the RN `ColorValue` type — because we parse
|
|
13
|
+
* hex manually on both platforms for full alpha channel control.
|
|
14
|
+
* Using ColorValue would trigger RN's color normalization which
|
|
15
|
+
* reorders alpha bytes and breaks #RRGGBBAA format.
|
|
16
|
+
*/
|
|
3
17
|
export interface NativeBlurVibeViewProps extends ViewProps {
|
|
4
18
|
blurAmount?: Float;
|
|
5
19
|
blurType?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,uBAAwB,SAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IAExD,UAAU,CAAC,EAAE,KAAK,CAAC;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAG1C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;wBAII,aAAa,CAAC,uBAAuB,CAAC;AAF3C,wBAE4C"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
// @ts-ignore -
|
|
1
|
+
// @ts-ignore - internal RN path, exists at runtime
|
|
2
2
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
3
3
|
import type { HostComponent, ViewProps } from 'react-native';
|
|
4
|
-
// @ts-ignore -
|
|
4
|
+
// @ts-ignore - internal RN path, exists at runtime
|
|
5
5
|
import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* NativeComponent codegen spec for BlurVibeView.
|
|
9
|
+
*
|
|
10
|
+
* Type mapping (JS → Native):
|
|
11
|
+
* Float → NSNumber (iOS) / Float (Android)
|
|
12
|
+
* string → NSString (iOS) / String (Android)
|
|
13
|
+
* Int32 → NSNumber (iOS) / Int (Android)
|
|
14
|
+
*
|
|
15
|
+
* Color props (overlayColor, reducedTransparencyFallbackColor) use
|
|
16
|
+
* plain `string` — NOT the RN `ColorValue` type — because we parse
|
|
17
|
+
* hex manually on both platforms for full alpha channel control.
|
|
18
|
+
* Using ColorValue would trigger RN's color normalization which
|
|
19
|
+
* reorders alpha bytes and breaks #RRGGBBAA format.
|
|
20
|
+
*/
|
|
7
21
|
export interface NativeBlurVibeViewProps extends ViewProps {
|
|
22
|
+
// 0–100 blur intensity
|
|
8
23
|
blurAmount?: Float;
|
|
24
|
+
|
|
25
|
+
// iOS UIBlurEffectStyle name — no-op on Android
|
|
9
26
|
blurType?: string;
|
|
27
|
+
|
|
28
|
+
// Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
|
|
10
29
|
overlayColor?: string;
|
|
30
|
+
|
|
31
|
+
// Fallback when blur unavailable (Reduce Transparency / old API)
|
|
11
32
|
reducedTransparencyFallbackColor?: string;
|
|
33
|
+
|
|
34
|
+
// Android downscale factor 1–8 — no-op on iOS
|
|
12
35
|
blurRadius?: Int32;
|
|
13
36
|
}
|
|
14
37
|
|