@sbaiahmed1/react-native-blur 4.5.4 → 4.5.5-beta.1
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.
|
@@ -3,15 +3,15 @@ package com.sbaiahmed1.reactnativeblur
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Color
|
|
5
5
|
import android.graphics.Outline
|
|
6
|
-
import android.os.Build
|
|
7
6
|
import android.util.AttributeSet
|
|
8
7
|
import android.util.Log
|
|
9
8
|
import android.util.TypedValue
|
|
10
9
|
import android.view.View
|
|
11
10
|
import android.view.ViewGroup
|
|
12
11
|
import android.view.ViewOutlineProvider
|
|
13
|
-
import
|
|
12
|
+
import android.view.ViewTreeObserver
|
|
14
13
|
import com.qmdeve.blurview.widget.BlurViewGroup
|
|
14
|
+
import com.qmdeve.blurview.base.BaseBlurViewGroup
|
|
15
15
|
import androidx.core.graphics.toColorInt
|
|
16
16
|
|
|
17
17
|
import android.view.View.MeasureSpec
|
|
@@ -22,6 +22,10 @@ import android.view.View.MeasureSpec
|
|
|
22
22
|
*
|
|
23
23
|
* QmBlurView is a high-performance blur library that uses native blur algorithms
|
|
24
24
|
* implemented with underlying Native calls for optimal performance.
|
|
25
|
+
*
|
|
26
|
+
* Uses reflection to redirect the blur capture root from the activity decor view
|
|
27
|
+
* to the nearest react-native-screens Screen ancestor, preventing flickering and
|
|
28
|
+
* wrong frame capture during navigation transitions.
|
|
25
29
|
*/
|
|
26
30
|
class ReactNativeBlurView : BlurViewGroup {
|
|
27
31
|
private var currentBlurRadius = DEFAULT_BLUR_RADIUS
|
|
@@ -31,12 +35,14 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
31
35
|
private var glassOpacity: Float = 1.0f
|
|
32
36
|
private var viewType: String = "blur"
|
|
33
37
|
private var glassType: String = "clear"
|
|
38
|
+
private var isBlurInitialized: Boolean = false
|
|
39
|
+
private var initRunnable: Runnable? = null
|
|
34
40
|
|
|
35
41
|
companion object {
|
|
36
42
|
private const val TAG = "ReactNativeBlurView"
|
|
37
43
|
private const val MAX_BLUR_RADIUS = 100f
|
|
38
44
|
private const val DEFAULT_BLUR_RADIUS = 10f
|
|
39
|
-
private const val DEBUG = false
|
|
45
|
+
private const val DEBUG = false
|
|
40
46
|
|
|
41
47
|
// Cross-platform blur amount constants
|
|
42
48
|
private const val MIN_BLUR_AMOUNT = 0f
|
|
@@ -69,32 +75,166 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
constructor(context: Context?) : super(context, null) {
|
|
72
|
-
|
|
78
|
+
setupView()
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
|
76
|
-
|
|
82
|
+
setupView()
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
86
|
+
* Initial view setup in constructor - only sets up visual defaults.
|
|
87
|
+
* Blur initialization is deferred to onAttachedToWindow to ensure the
|
|
88
|
+
* view hierarchy is fully mounted, preventing flickering and wrong frame capture.
|
|
82
89
|
*/
|
|
83
|
-
private fun
|
|
90
|
+
private fun setupView() {
|
|
91
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
92
|
+
clipChildren = true
|
|
93
|
+
clipToOutline = true
|
|
94
|
+
super.setDownsampleFactor(6.0F)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Called when the view is attached to a window.
|
|
99
|
+
* After QmBlurView's onAttachedToWindow sets the decor view as blur root,
|
|
100
|
+
* we use reflection to redirect it to the nearest Screen ancestor.
|
|
101
|
+
* This scopes the blur capture to just the current screen, preventing
|
|
102
|
+
* navigation transition artifacts.
|
|
103
|
+
*/
|
|
104
|
+
override fun onAttachedToWindow() {
|
|
105
|
+
super.onAttachedToWindow()
|
|
106
|
+
|
|
107
|
+
if (isBlurInitialized) return
|
|
108
|
+
|
|
109
|
+
// Defer the blur root swap to next frame so the view tree is fully mounted
|
|
110
|
+
val runnable = Runnable {
|
|
111
|
+
initRunnable = null
|
|
112
|
+
if (isBlurInitialized) return@Runnable
|
|
113
|
+
swapBlurRootToScreenAncestor()
|
|
114
|
+
initializeBlur()
|
|
115
|
+
}
|
|
116
|
+
initRunnable = runnable
|
|
117
|
+
post(runnable)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Uses reflection to redirect QmBlurView's internal blur capture root
|
|
122
|
+
* from the activity decor view to the nearest react-native-screens Screen ancestor.
|
|
123
|
+
*
|
|
124
|
+
* Reflection path: BlurViewGroup.mBaseBlurViewGroup -> BaseBlurViewGroup.mDecorView
|
|
125
|
+
* Also moves the OnPreDrawListener from the old root to the new one.
|
|
126
|
+
*/
|
|
127
|
+
private fun swapBlurRootToScreenAncestor() {
|
|
128
|
+
// Pinned to QmBlurView 1.1.4 – depends on: mBaseBlurViewGroup, mDecorView, preDrawListener, mDifferentRoot, mForceRedraw
|
|
129
|
+
val newRoot = findOptimalBlurRoot() ?: return
|
|
130
|
+
|
|
84
131
|
try {
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
132
|
+
// Step 1: Get BlurViewGroup's private mBaseBlurViewGroup field
|
|
133
|
+
val blurViewGroupClass = BlurViewGroup::class.java
|
|
134
|
+
val baseField = blurViewGroupClass.getDeclaredField("mBaseBlurViewGroup")
|
|
135
|
+
baseField.isAccessible = true
|
|
136
|
+
val baseBlurViewGroup = baseField.get(this) ?: return
|
|
137
|
+
|
|
138
|
+
// Step 2: Get BaseBlurViewGroup's private fields
|
|
139
|
+
val baseClass = BaseBlurViewGroup::class.java
|
|
140
|
+
|
|
141
|
+
val decorViewField = baseClass.getDeclaredField("mDecorView")
|
|
142
|
+
decorViewField.isAccessible = true
|
|
143
|
+
val oldDecorView = decorViewField.get(baseBlurViewGroup) as? View
|
|
144
|
+
|
|
145
|
+
val preDrawListenerField = baseClass.getDeclaredField("preDrawListener")
|
|
146
|
+
preDrawListenerField.isAccessible = true
|
|
147
|
+
val preDrawListener = preDrawListenerField.get(baseBlurViewGroup) as? ViewTreeObserver.OnPreDrawListener
|
|
148
|
+
|
|
149
|
+
if (oldDecorView == null) {
|
|
150
|
+
logWarning("swapBlurRootToScreenAncestor: oldDecorView is null, skipping swap – falling back to decor view")
|
|
151
|
+
}
|
|
152
|
+
if (preDrawListener == null) {
|
|
153
|
+
logWarning("swapBlurRootToScreenAncestor: preDrawListener is null, skipping swap – falling back to decor view")
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (preDrawListener != null && oldDecorView != null) {
|
|
157
|
+
// Step 3: Remove listener from old root's ViewTreeObserver
|
|
158
|
+
try {
|
|
159
|
+
oldDecorView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
160
|
+
} catch (e: Exception) {
|
|
161
|
+
logDebug("Could not remove old pre-draw listener: ${e.message}")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 4: Set new root as mDecorView
|
|
165
|
+
decorViewField.set(baseBlurViewGroup, newRoot)
|
|
166
|
+
|
|
167
|
+
// Step 5: Add listener to new root's ViewTreeObserver
|
|
168
|
+
newRoot.viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
|
169
|
+
|
|
170
|
+
// Step 6: Update mDifferentRoot flag
|
|
171
|
+
val differentRootField = baseClass.getDeclaredField("mDifferentRoot")
|
|
172
|
+
differentRootField.isAccessible = true
|
|
173
|
+
differentRootField.setBoolean(baseBlurViewGroup, newRoot.rootView != this.rootView)
|
|
174
|
+
|
|
175
|
+
// Step 7: Force a redraw
|
|
176
|
+
val forceRedrawField = baseClass.getDeclaredField("mForceRedraw")
|
|
177
|
+
forceRedrawField.isAccessible = true
|
|
178
|
+
forceRedrawField.setBoolean(baseBlurViewGroup, true)
|
|
90
179
|
|
|
91
|
-
|
|
180
|
+
logDebug("Swapped blur root to: ${newRoot.javaClass.simpleName} (was: ${oldDecorView.javaClass.simpleName})")
|
|
181
|
+
}
|
|
182
|
+
} catch (e: NoSuchFieldException) {
|
|
183
|
+
logWarning("Reflection failed - QmBlurView field not found: ${e.message}. Falling back to decor view.")
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
logWarning("Failed to swap blur root: ${e.message}. Falling back to decor view.")
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Finds the optimal view to use as blur capture root.
|
|
191
|
+
* Priority: nearest react-native-screens Screen > android.R.id.content > parent
|
|
192
|
+
*/
|
|
193
|
+
private fun findOptimalBlurRoot(): ViewGroup? {
|
|
194
|
+
return findNearestScreenAncestor() ?: getContentViewFallback()
|
|
195
|
+
}
|
|
92
196
|
|
|
93
|
-
|
|
94
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Walks up the view hierarchy looking for react-native-screens Screen components
|
|
199
|
+
* using class name detection to avoid hard dependencies on react-native-screens.
|
|
200
|
+
*/
|
|
201
|
+
private fun findNearestScreenAncestor(): ViewGroup? {
|
|
202
|
+
var currentParent = this.parent
|
|
203
|
+
while (currentParent != null) {
|
|
204
|
+
if (currentParent.javaClass.name == "com.swmansion.rnscreens.Screen") {
|
|
205
|
+
return currentParent as? ViewGroup
|
|
95
206
|
}
|
|
207
|
+
currentParent = currentParent.parent
|
|
208
|
+
}
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Falls back to android.R.id.content or the activity root view.
|
|
214
|
+
*/
|
|
215
|
+
private fun getContentViewFallback(): ViewGroup? {
|
|
216
|
+
try {
|
|
217
|
+
val activity = context as? android.app.Activity
|
|
218
|
+
activity?.findViewById<ViewGroup>(android.R.id.content)?.let { return it }
|
|
219
|
+
} catch (e: Exception) {
|
|
220
|
+
logDebug("Could not access activity content view: ${e.message}")
|
|
221
|
+
}
|
|
222
|
+
return this.parent as? ViewGroup
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Initialize the blur view with current settings.
|
|
227
|
+
* Called after the view is attached and the blur root has been swapped.
|
|
228
|
+
* Guarded by isBlurInitialized to prevent duplicate setup.
|
|
229
|
+
*/
|
|
230
|
+
private fun initializeBlur() {
|
|
231
|
+
if (isBlurInitialized) return
|
|
96
232
|
|
|
233
|
+
try {
|
|
234
|
+
super.setBlurRadius(currentBlurRadius)
|
|
235
|
+
super.setOverlayColor(currentOverlayColor)
|
|
97
236
|
updateCornerRadius()
|
|
237
|
+
isBlurInitialized = true
|
|
98
238
|
|
|
99
239
|
logDebug("QmBlurView initialized with blurRadius: $currentBlurRadius, overlayColor: $currentOverlayColor")
|
|
100
240
|
} catch (e: Exception) {
|
|
@@ -104,7 +244,8 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
104
244
|
|
|
105
245
|
/**
|
|
106
246
|
* Called when the view is detached from a window.
|
|
107
|
-
* Performs cleanup to prevent memory leaks
|
|
247
|
+
* Performs cleanup to prevent memory leaks and resets initialization state
|
|
248
|
+
* so blur is re-initialized on next attach (e.g. navigation transitions).
|
|
108
249
|
*/
|
|
109
250
|
override fun onDetachedFromWindow() {
|
|
110
251
|
super.onDetachedFromWindow()
|
|
@@ -116,7 +257,9 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
116
257
|
* Helps prevent memory leaks and ensures clean state.
|
|
117
258
|
*/
|
|
118
259
|
fun cleanup() {
|
|
119
|
-
|
|
260
|
+
isBlurInitialized = false
|
|
261
|
+
initRunnable?.let { removeCallbacks(it) }
|
|
262
|
+
initRunnable = null
|
|
120
263
|
logDebug("View cleaned up")
|
|
121
264
|
}
|
|
122
265
|
|
|
@@ -129,7 +272,6 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
129
272
|
logDebug("setBlurAmount: $amount -> $currentBlurRadius (mapped from 0-100 to 0-25 range)")
|
|
130
273
|
|
|
131
274
|
try {
|
|
132
|
-
// QmBlurView uses setBlurRadius() to set blur intensity
|
|
133
275
|
super.setBlurRadius(currentBlurRadius)
|
|
134
276
|
} catch (e: Exception) {
|
|
135
277
|
logError("Failed to set blur radius: ${e.message}", e)
|
|
@@ -146,12 +288,8 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
146
288
|
logDebug("setBlurType: $type -> ${blurType.name}")
|
|
147
289
|
|
|
148
290
|
try {
|
|
149
|
-
|
|
291
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
150
292
|
super.setOverlayColor(currentOverlayColor)
|
|
151
|
-
|
|
152
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
153
|
-
super.setBackgroundColor(currentOverlayColor)
|
|
154
|
-
}
|
|
155
293
|
} catch (e: Exception) {
|
|
156
294
|
logError("Failed to set overlay color: ${e.message}", e)
|
|
157
295
|
}
|
|
@@ -249,11 +387,8 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
249
387
|
"blur" -> {
|
|
250
388
|
// Restore original blur overlay color
|
|
251
389
|
try {
|
|
390
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
252
391
|
super.setOverlayColor(currentOverlayColor)
|
|
253
|
-
|
|
254
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
255
|
-
super.setBackgroundColor(currentOverlayColor)
|
|
256
|
-
}
|
|
257
392
|
} catch (e: Exception) {
|
|
258
393
|
logError("Failed to restore blur overlay: ${e.message}", e)
|
|
259
394
|
}
|
|
@@ -286,16 +421,12 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
286
421
|
context.resources.displayMetrics
|
|
287
422
|
)
|
|
288
423
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
override fun getOutline(view: View, outline: Outline?) {
|
|
293
|
-
outline?.setRoundRect(0, 0, view.width, view.height, radiusInPixels)
|
|
294
|
-
}
|
|
424
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
425
|
+
override fun getOutline(view: View, outline: Outline?) {
|
|
426
|
+
outline?.setRoundRect(0, 0, view.width, view.height, radiusInPixels)
|
|
295
427
|
}
|
|
296
|
-
|
|
297
|
-
clipToOutline = true
|
|
298
428
|
}
|
|
429
|
+
clipToOutline = true
|
|
299
430
|
|
|
300
431
|
super.setCornerRadius(radiusInPixels)
|
|
301
432
|
logDebug("Updated corner radius: ${currentCornerRadius}dp -> ${radiusInPixels}px")
|
package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt
CHANGED
|
@@ -10,10 +10,11 @@ import android.graphics.PorterDuffXfermode
|
|
|
10
10
|
import android.graphics.Shader
|
|
11
11
|
import android.util.AttributeSet
|
|
12
12
|
import android.util.Log
|
|
13
|
+
import android.view.View
|
|
14
|
+
import android.view.ViewGroup
|
|
13
15
|
import android.widget.FrameLayout
|
|
14
16
|
import android.view.View.MeasureSpec
|
|
15
17
|
import com.qmdeve.blurview.widget.BlurView
|
|
16
|
-
import androidx.core.graphics.toColorInt
|
|
17
18
|
import kotlin.math.max
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -33,12 +34,13 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
33
34
|
private var currentDirection = "topToBottom"
|
|
34
35
|
private var currentStartOffset = 0.0f
|
|
35
36
|
private var hasExplicitBackground: Boolean = false
|
|
37
|
+
private var isBlurInitialized: Boolean = false
|
|
36
38
|
|
|
37
39
|
companion object {
|
|
38
40
|
private const val TAG = "ReactNativeProgressiveBlur"
|
|
39
41
|
private const val MAX_BLUR_RADIUS = 100f
|
|
40
42
|
private const val DEFAULT_BLUR_RADIUS = 10f
|
|
41
|
-
private const val DEBUG =
|
|
43
|
+
private const val DEBUG = false
|
|
42
44
|
|
|
43
45
|
// Cross-platform blur amount constants
|
|
44
46
|
private const val MIN_BLUR_AMOUNT = 0f
|
|
@@ -72,36 +74,69 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
constructor(context: Context) : super(context) {
|
|
75
|
-
|
|
77
|
+
setupView()
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
|
79
|
-
|
|
81
|
+
setupView()
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
|
-
*
|
|
85
|
+
* Initial view setup in constructor - only sets up visual defaults and gradient paint.
|
|
86
|
+
* Blur child creation is deferred to onAttachedToWindow.
|
|
84
87
|
*/
|
|
85
|
-
private fun
|
|
88
|
+
private fun setupView() {
|
|
89
|
+
// Set up the gradient paint
|
|
90
|
+
gradientPaint.style = Paint.Style.FILL
|
|
91
|
+
setWillNotDraw(false)
|
|
92
|
+
|
|
93
|
+
// Set transparent background for the container
|
|
94
|
+
super.setBackgroundColor(Color.TRANSPARENT)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Called when the view is attached to a window.
|
|
99
|
+
* Defers blur initialization to the next frame to ensure the view tree is ready.
|
|
100
|
+
*/
|
|
101
|
+
override fun onAttachedToWindow() {
|
|
102
|
+
super.onAttachedToWindow()
|
|
103
|
+
|
|
104
|
+
if (!isBlurInitialized) {
|
|
105
|
+
post {
|
|
106
|
+
initializeBlurChild()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize the internal blur view child after the view tree is ready.
|
|
113
|
+
* Also swaps the blur capture root to the nearest Screen ancestor.
|
|
114
|
+
*/
|
|
115
|
+
private fun initializeBlurChild() {
|
|
116
|
+
if (isBlurInitialized) return
|
|
117
|
+
|
|
86
118
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
if (blurView == null) {
|
|
120
|
+
blurView = BlurView(context, null).apply {
|
|
121
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
122
|
+
setDownsampleFactor(6.0F)
|
|
123
|
+
blurRounds = 3
|
|
124
|
+
}
|
|
125
|
+
addView(blurView)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
blurView?.apply {
|
|
90
129
|
setBlurRadius(currentBlurRadius)
|
|
91
|
-
setDownsampleFactor(6.0F)
|
|
92
|
-
blurRounds = 3
|
|
93
130
|
overlayColor = currentOverlayColor
|
|
94
131
|
setBackgroundColor(currentOverlayColor)
|
|
95
132
|
}
|
|
96
|
-
addView(blurView)
|
|
97
133
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Set transparent background for the container
|
|
103
|
-
super.setBackgroundColor(Color.TRANSPARENT)
|
|
134
|
+
// Swap blur root after BlurView is attached (deferred to let it attach first)
|
|
135
|
+
blurView?.post {
|
|
136
|
+
swapBlurRootToScreenAncestor()
|
|
137
|
+
}
|
|
104
138
|
|
|
139
|
+
isBlurInitialized = true
|
|
105
140
|
logDebug("Initialized progressive blur with blur + gradient approach")
|
|
106
141
|
updateGradient()
|
|
107
142
|
|
|
@@ -110,6 +145,79 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
110
145
|
}
|
|
111
146
|
}
|
|
112
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Redirects the internal BlurView's blur capture root from the activity decor view
|
|
150
|
+
* to the nearest react-native-screens Screen ancestor.
|
|
151
|
+
*
|
|
152
|
+
* BaseBlurView has public mDecorView and preDrawListener fields, so no reflection needed.
|
|
153
|
+
*/
|
|
154
|
+
private fun swapBlurRootToScreenAncestor() {
|
|
155
|
+
val bv = blurView ?: return
|
|
156
|
+
val newRoot = findOptimalBlurRoot() ?: return
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
val oldDecorView = bv.mDecorView
|
|
160
|
+
val listener = bv.preDrawListener
|
|
161
|
+
|
|
162
|
+
if (oldDecorView != null && listener != null) {
|
|
163
|
+
// Remove listener from old root
|
|
164
|
+
try {
|
|
165
|
+
oldDecorView.viewTreeObserver.removeOnPreDrawListener(listener)
|
|
166
|
+
} catch (e: Exception) {
|
|
167
|
+
logDebug("Could not remove old pre-draw listener: ${e.message}")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Set new root
|
|
171
|
+
bv.mDecorView = newRoot
|
|
172
|
+
|
|
173
|
+
// Add listener to new root
|
|
174
|
+
newRoot.viewTreeObserver.addOnPreDrawListener(listener)
|
|
175
|
+
|
|
176
|
+
// Update mDifferentRoot flag
|
|
177
|
+
bv.mDifferentRoot = newRoot.rootView != bv.rootView
|
|
178
|
+
|
|
179
|
+
logDebug("Progressive blur: swapped root to ${newRoot.javaClass.simpleName}")
|
|
180
|
+
}
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
logWarning("Failed to swap progressive blur root: ${e.message}")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Finds the optimal view to use as blur capture root.
|
|
188
|
+
* Priority: nearest react-native-screens Screen > android.R.id.content > parent
|
|
189
|
+
*/
|
|
190
|
+
private fun findOptimalBlurRoot(): ViewGroup? {
|
|
191
|
+
return findNearestScreenAncestor() ?: getContentViewFallback()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Walks up the view hierarchy looking for react-native-screens Screen components.
|
|
196
|
+
*/
|
|
197
|
+
private fun findNearestScreenAncestor(): ViewGroup? {
|
|
198
|
+
var currentParent = this.parent
|
|
199
|
+
while (currentParent != null) {
|
|
200
|
+
if (currentParent.javaClass.name == "com.swmansion.rnscreens.Screen") {
|
|
201
|
+
return currentParent as? ViewGroup
|
|
202
|
+
}
|
|
203
|
+
currentParent = currentParent.parent
|
|
204
|
+
}
|
|
205
|
+
return null
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Falls back to android.R.id.content or the activity root view.
|
|
210
|
+
*/
|
|
211
|
+
private fun getContentViewFallback(): ViewGroup? {
|
|
212
|
+
try {
|
|
213
|
+
val activity = context as? android.app.Activity
|
|
214
|
+
activity?.findViewById<ViewGroup>(android.R.id.content)?.let { return it }
|
|
215
|
+
} catch (e: Exception) {
|
|
216
|
+
logDebug("Could not access activity content view: ${e.message}")
|
|
217
|
+
}
|
|
218
|
+
return this.parent as? ViewGroup
|
|
219
|
+
}
|
|
220
|
+
|
|
113
221
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
114
222
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
115
223
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
@@ -252,11 +360,12 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
252
360
|
|
|
253
361
|
/**
|
|
254
362
|
* Cleanup method to prevent memory leaks.
|
|
363
|
+
* Resets initialization state so blur is re-initialized on next attach.
|
|
255
364
|
*/
|
|
256
365
|
fun cleanup() {
|
|
257
366
|
hasExplicitBackground = false
|
|
367
|
+
isBlurInitialized = false
|
|
258
368
|
removeCallbacks(null)
|
|
259
|
-
blurView = null
|
|
260
369
|
logDebug("View cleaned up")
|
|
261
370
|
}
|
|
262
371
|
|