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
package/android/build.gradle
CHANGED
|
@@ -66,10 +66,10 @@ android {
|
|
|
66
66
|
repositories {
|
|
67
67
|
google()
|
|
68
68
|
mavenCentral()
|
|
69
|
-
maven { url 'https://jitpack.io' }
|
|
69
|
+
//maven { url 'https://jitpack.io' }
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
dependencies {
|
|
73
73
|
implementation "com.facebook.react:react-android"
|
|
74
|
-
implementation 'com.qmdeve.blurview:core:1.1.4'
|
|
74
|
+
//implementation 'com.qmdeve.blurview:core:1.1.4'
|
|
75
75
|
}
|
|
@@ -1,258 +1,167 @@
|
|
|
1
1
|
package com.blurvibe
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
4
5
|
import android.graphics.Color
|
|
5
6
|
import android.graphics.Outline
|
|
6
7
|
import android.util.TypedValue
|
|
7
8
|
import android.view.View
|
|
8
9
|
import android.view.ViewGroup
|
|
9
10
|
import android.view.ViewOutlineProvider
|
|
10
|
-
import android.view.ViewTreeObserver
|
|
11
11
|
import androidx.core.graphics.toColorInt
|
|
12
|
-
import com.
|
|
13
|
-
import com.qmdeve.blurview.widget.BlurViewGroup
|
|
12
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
* BlurVibeView — Android backdrop blur
|
|
15
|
+
* BlurVibeView — Android API 21–30 backdrop blur (zero external dependencies)
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
17
|
+
* Replaces QmBlurView with LegacyBlurController — a direct RenderScript
|
|
18
|
+
* implementation using only the Android SDK. No third-party library needed.
|
|
24
19
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* preventing full-screen blur and navigation transition artifacts.
|
|
20
|
+
* Extends ReactViewGroup so it hosts RN children correctly (Yoga layout,
|
|
21
|
+
* touch events, z-ordering all work natively).
|
|
28
22
|
*
|
|
29
|
-
*
|
|
23
|
+
* For API 31+, BlurVibeViewApi31 is used instead (RenderEffect GPU path).
|
|
30
24
|
*/
|
|
31
|
-
class BlurVibeView(context: Context) :
|
|
25
|
+
class BlurVibeView(context: Context) : ReactViewGroup(context) {
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
private var currentOverlayColor = Color.TRANSPARENT
|
|
35
|
-
private var currentCornerRadius = 0f
|
|
36
|
-
private var isBlurInitialized = false
|
|
27
|
+
// ── State ──────────────────────────────────────────────────────────────────
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
private const val MAX_BLUR_RADIUS = 25f // QmBlurView Gaussian kernel designed for 0-25
|
|
29
|
+
private var blurController: LegacyBlurController? = null
|
|
30
|
+
private var pendingBlurAmount = 10f
|
|
31
|
+
private var pendingOverlay = Color.TRANSPARENT
|
|
32
|
+
private var cornerRadiusPx = 0f
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
// Uses a squared curve so low values (0–30) stay subtle and mid-high values
|
|
46
|
-
// (50–100) produce the strong frosted-glass spread seen in CSS backdrop-blur-md/lg/xl.
|
|
47
|
-
private fun mapBlurAmountToRadius(amount: Float): Float {
|
|
48
|
-
val t = amount.coerceIn(MIN_BLUR_AMOUNT, MAX_BLUR_AMOUNT) / MAX_BLUR_AMOUNT // 0.0–1.0
|
|
49
|
-
return t * t * MAX_BLUR_RADIUS // quadratic: more spread at higher values
|
|
50
|
-
}
|
|
51
|
-
}
|
|
34
|
+
// ── Init ───────────────────────────────────────────────────────────────────
|
|
52
35
|
|
|
53
36
|
init {
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
setWillNotDraw(false)
|
|
38
|
+
super.setBackgroundColor(Color.TRANSPARENT)
|
|
56
39
|
clipToOutline = true
|
|
57
|
-
blurRounds = 2 // 2 passes = smooth Gaussian spread (frosted glass quality)
|
|
58
|
-
super.setDownsampleFactor(4.0f) // 1/4 res — eliminates pixelation, still fast
|
|
59
40
|
}
|
|
60
41
|
|
|
42
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
61
44
|
override fun onAttachedToWindow() {
|
|
62
45
|
super.onAttachedToWindow()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
val root = findBlurRoot() ?: return
|
|
47
|
+
blurController = LegacyBlurController(this, root).also {
|
|
48
|
+
it.blurRadius = mapBlurAmount(pendingBlurAmount)
|
|
49
|
+
it.overlayColor = pendingOverlay
|
|
50
|
+
}
|
|
66
51
|
}
|
|
67
52
|
|
|
68
53
|
override fun onDetachedFromWindow() {
|
|
54
|
+
blurController?.destroy()
|
|
55
|
+
blurController = null
|
|
69
56
|
super.onDetachedFromWindow()
|
|
70
|
-
isBlurInitialized = false
|
|
71
57
|
}
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
frameScheduled = false
|
|
77
|
-
try { invalidate() } catch (_: Exception) {}
|
|
59
|
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
60
|
+
super.onSizeChanged(w, h, oldw, oldh)
|
|
61
|
+
blurController?.onSizeChanged()
|
|
78
62
|
}
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
val newRoot = findNearestScreenAncestor() ?: findNearestReactRootView() ?: return
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
val blurViewGroupClass = BlurViewGroup::class.java
|
|
90
|
-
val baseField = blurViewGroupClass.getDeclaredField("mBaseBlurViewGroup")
|
|
91
|
-
baseField.isAccessible = true
|
|
92
|
-
val baseBlurViewGroup = baseField.get(this) ?: return
|
|
93
|
-
|
|
94
|
-
val baseClass = BaseBlurViewGroup::class.java
|
|
95
|
-
|
|
96
|
-
val decorViewField = baseClass.getDeclaredField("mDecorView")
|
|
97
|
-
decorViewField.isAccessible = true
|
|
98
|
-
val oldDecorView = decorViewField.get(baseBlurViewGroup) as? View
|
|
99
|
-
|
|
100
|
-
val preDrawListenerField = baseClass.getDeclaredField("preDrawListener")
|
|
101
|
-
preDrawListenerField.isAccessible = true
|
|
102
|
-
val preDrawListener = preDrawListenerField.get(baseBlurViewGroup)
|
|
103
|
-
as? ViewTreeObserver.OnPreDrawListener
|
|
104
|
-
|
|
105
|
-
if (oldDecorView != null && preDrawListener != null) {
|
|
106
|
-
// Remove listener from old root
|
|
107
|
-
oldDecorView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
108
|
-
|
|
109
|
-
// Set new root
|
|
110
|
-
decorViewField.set(baseBlurViewGroup, newRoot)
|
|
64
|
+
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
|
65
|
+
super.onWindowFocusChanged(hasWindowFocus)
|
|
66
|
+
// Re-attach to the current ViewTreeObserver after split-screen / PiP transition.
|
|
67
|
+
// Android may have killed and replaced the old observer during the mode switch.
|
|
68
|
+
if (hasWindowFocus) blurController?.reAttach()
|
|
69
|
+
}
|
|
111
70
|
|
|
112
|
-
|
|
113
|
-
// how many child invalidations happen in the same frame
|
|
114
|
-
val gatedListener = ViewTreeObserver.OnPreDrawListener {
|
|
115
|
-
if (!frameScheduled) {
|
|
116
|
-
frameScheduled = true
|
|
117
|
-
android.view.Choreographer.getInstance().postFrameCallback(frameCallback)
|
|
118
|
-
}
|
|
119
|
-
true // never block the draw pass
|
|
120
|
-
}
|
|
71
|
+
// ── Draw ───────────────────────────────────────────────────────────────────
|
|
121
72
|
|
|
122
|
-
|
|
123
|
-
|
|
73
|
+
override fun onDraw(canvas: Canvas) {
|
|
74
|
+
blurController?.draw(canvas, width.toFloat(), height.toFloat())
|
|
75
|
+
}
|
|
124
76
|
|
|
125
|
-
|
|
126
|
-
val differentRootField = baseClass.getDeclaredField("mDifferentRoot")
|
|
127
|
-
differentRootField.isAccessible = true
|
|
128
|
-
differentRootField.setBoolean(baseBlurViewGroup, newRoot.rootView != this.rootView)
|
|
77
|
+
// ── Public setters ─────────────────────────────────────────────────────────
|
|
129
78
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
forceRedrawField.setBoolean(baseBlurViewGroup, true)
|
|
134
|
-
}
|
|
135
|
-
} catch (e: Exception) {
|
|
136
|
-
// Reflection failed — QmBlurView internals changed
|
|
137
|
-
// Fall back gracefully to default decor view blur root
|
|
138
|
-
}
|
|
79
|
+
fun setBlurAmount(amount: Float) {
|
|
80
|
+
pendingBlurAmount = amount
|
|
81
|
+
blurController?.blurRadius = mapBlurAmount(amount)
|
|
139
82
|
}
|
|
140
83
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return current as? ViewGroup
|
|
146
|
-
}
|
|
147
|
-
current = current.parent
|
|
148
|
-
}
|
|
149
|
-
return null
|
|
84
|
+
fun setOverlayColor(colorString: String?) {
|
|
85
|
+
pendingOverlay = parseHexColor(colorString ?: "transparent") ?: Color.TRANSPARENT
|
|
86
|
+
blurController?.overlayColor = pendingOverlay
|
|
87
|
+
invalidate()
|
|
150
88
|
}
|
|
151
89
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (current.javaClass.name == "com.facebook.react.ReactRootView") {
|
|
156
|
-
return current as? ViewGroup
|
|
157
|
-
}
|
|
158
|
-
current = current.parent
|
|
159
|
-
}
|
|
160
|
-
return null
|
|
90
|
+
fun setBlurRadius(factor: Int) {
|
|
91
|
+
// No-op for now — LegacyBlurController uses fixed DOWNSAMPLE_FACTOR = 4
|
|
92
|
+
// Could expose downsampleFactor setter on controller if needed
|
|
161
93
|
}
|
|
162
94
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Ignore — view may not be fully attached yet
|
|
95
|
+
fun applyBorderRadius(radiusDp: Float) {
|
|
96
|
+
cornerRadiusPx = TypedValue.applyDimension(
|
|
97
|
+
TypedValue.COMPLEX_UNIT_DIP, radiusDp, context.resources.displayMetrics
|
|
98
|
+
)
|
|
99
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
100
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
101
|
+
outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx)
|
|
102
|
+
}
|
|
172
103
|
}
|
|
104
|
+
clipToOutline = cornerRadiusPx > 0f
|
|
105
|
+
invalidate()
|
|
173
106
|
}
|
|
174
107
|
|
|
175
|
-
|
|
108
|
+
fun setReducedTransparencyFallbackColor(@Suppress("UNUSED_PARAMETER") color: String?) { }
|
|
176
109
|
|
|
177
|
-
fun
|
|
178
|
-
|
|
179
|
-
|
|
110
|
+
fun applyBlurEnabled(enabled: Boolean) {
|
|
111
|
+
blurController?.enabled = enabled
|
|
112
|
+
if (!enabled) invalidate()
|
|
180
113
|
}
|
|
181
114
|
|
|
182
|
-
fun
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
super.setBackgroundColor(currentOverlayColor)
|
|
186
|
-
super.setOverlayColor(currentOverlayColor)
|
|
187
|
-
} catch (e: Exception) {}
|
|
115
|
+
fun setAutoUpdate(autoUpdate: Boolean) {
|
|
116
|
+
blurController?.autoUpdate = autoUpdate
|
|
188
117
|
}
|
|
189
118
|
|
|
190
|
-
|
|
191
|
-
// Stored for future use — QmBlurView handles accessibility fallback internally
|
|
192
|
-
}
|
|
119
|
+
// ── Layout passthrough ─────────────────────────────────────────────────────
|
|
193
120
|
|
|
194
|
-
fun
|
|
195
|
-
//
|
|
196
|
-
val downsample = radius.coerceIn(1, 8).toFloat()
|
|
197
|
-
try { super.setDownsampleFactor(downsample) } catch (e: Exception) {}
|
|
121
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
122
|
+
// Yoga handles all layout
|
|
198
123
|
}
|
|
199
124
|
|
|
200
|
-
|
|
201
|
-
currentCornerRadius = radius
|
|
202
|
-
updateCornerRadius()
|
|
203
|
-
}
|
|
125
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
204
126
|
|
|
205
|
-
private fun
|
|
206
|
-
val
|
|
207
|
-
|
|
208
|
-
currentCornerRadius,
|
|
209
|
-
context.resources.displayMetrics
|
|
210
|
-
)
|
|
211
|
-
outlineProvider = object : ViewOutlineProvider() {
|
|
212
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
213
|
-
outline.setRoundRect(0, 0, view.width, view.height, radiusPx)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
clipToOutline = true
|
|
217
|
-
try { super.setCornerRadius(radiusPx) } catch (e: Exception) {}
|
|
127
|
+
private fun mapBlurAmount(amount: Float): Float {
|
|
128
|
+
val t = amount.coerceIn(0f, 100f) / 100f
|
|
129
|
+
return t * t * 25f // quadratic curve, max 25 (RenderScript kernel limit)
|
|
218
130
|
}
|
|
219
131
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
132
|
+
private fun findBlurRoot(): ViewGroup? {
|
|
133
|
+
var p = parent
|
|
134
|
+
while (p != null) {
|
|
135
|
+
if ((p as? View)?.javaClass?.name == "com.swmansion.rnscreens.Screen") return p as? ViewGroup
|
|
136
|
+
p = (p as? View)?.parent
|
|
137
|
+
}
|
|
138
|
+
p = parent
|
|
139
|
+
while (p != null) {
|
|
140
|
+
if ((p as? View)?.javaClass?.name == "com.facebook.react.ReactRootView") return p as? ViewGroup
|
|
141
|
+
p = (p as? View)?.parent
|
|
142
|
+
}
|
|
143
|
+
return rootView as? ViewGroup
|
|
223
144
|
}
|
|
224
145
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (!s.startsWith("#")) {
|
|
231
|
-
return try { s.toColorInt() } catch (e: Exception) { null }
|
|
232
|
-
}
|
|
233
|
-
val hex = s.removePrefix("#")
|
|
146
|
+
private fun parseHexColor(s: String): Int? {
|
|
147
|
+
val t = s.trim()
|
|
148
|
+
if (t.equals("transparent", ignoreCase = true)) return Color.TRANSPARENT
|
|
149
|
+
if (!t.startsWith("#")) return try { t.toColorInt() } catch (_: Exception) { null }
|
|
150
|
+
val hex = t.removePrefix("#")
|
|
234
151
|
return try {
|
|
235
152
|
when (hex.length) {
|
|
236
|
-
3 -> Color.argb(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
hex.substring(4, 6).toInt(16)
|
|
247
|
-
)
|
|
248
|
-
8 -> Color.argb(
|
|
249
|
-
hex.substring(6, 8).toInt(16), // AA is last in #RRGGBBAA
|
|
250
|
-
hex.substring(0, 2).toInt(16),
|
|
251
|
-
hex.substring(2, 4).toInt(16),
|
|
252
|
-
hex.substring(4, 6).toInt(16)
|
|
253
|
-
)
|
|
153
|
+
3 -> Color.argb(255, hex[0].toString().repeat(2).toInt(16),
|
|
154
|
+
hex[1].toString().repeat(2).toInt(16),
|
|
155
|
+
hex[2].toString().repeat(2).toInt(16))
|
|
156
|
+
6 -> Color.argb(255, hex.substring(0,2).toInt(16),
|
|
157
|
+
hex.substring(2,4).toInt(16),
|
|
158
|
+
hex.substring(4,6).toInt(16))
|
|
159
|
+
8 -> Color.argb(hex.substring(6,8).toInt(16),
|
|
160
|
+
hex.substring(0,2).toInt(16),
|
|
161
|
+
hex.substring(2,4).toInt(16),
|
|
162
|
+
hex.substring(4,6).toInt(16))
|
|
254
163
|
else -> null
|
|
255
164
|
}
|
|
256
|
-
} catch (
|
|
165
|
+
} catch (_: NumberFormatException) { null }
|
|
257
166
|
}
|
|
258
167
|
}
|