@sbaiahmed1/react-native-blur 4.6.1 → 4.6.2

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 (53) hide show
  1. package/README.md +16 -11
  2. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/BlurType.kt +24 -22
  3. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurSwitch.kt +19 -1
  4. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurSwitchManager.kt +5 -0
  5. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt +63 -10
  6. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurViewManager.kt +5 -0
  7. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt +60 -10
  8. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurViewManager.kt +5 -0
  9. package/ios/Views/BlurEffectView.swift +32 -19
  10. package/lib/module/BlurSwitch.js +11 -6
  11. package/lib/module/BlurSwitch.js.map +1 -1
  12. package/lib/module/BlurView.js +2 -0
  13. package/lib/module/BlurView.js.map +1 -1
  14. package/lib/module/LiquidGlassContainer.js.map +1 -1
  15. package/lib/module/LiquidGlassView.js.map +1 -1
  16. package/lib/module/ProgressiveBlurView.js +3 -0
  17. package/lib/module/ProgressiveBlurView.js.map +1 -1
  18. package/lib/module/ReactNativeBlurSwitchNativeComponent.ts +2 -37
  19. package/lib/module/ReactNativeBlurViewNativeComponent.ts +2 -0
  20. package/lib/module/ReactNativeLiquidGlassContainerNativeComponent.ts +0 -5
  21. package/lib/module/ReactNativeLiquidGlassViewNativeComponent.ts +0 -35
  22. package/lib/module/ReactNativeProgressiveBlurViewNativeComponent.ts +2 -0
  23. package/lib/typescript/src/BlurSwitch.d.ts +10 -0
  24. package/lib/typescript/src/BlurSwitch.d.ts.map +1 -1
  25. package/lib/typescript/src/BlurView.d.ts +11 -2
  26. package/lib/typescript/src/BlurView.d.ts.map +1 -1
  27. package/lib/typescript/src/LiquidGlassContainer.d.ts +3 -2
  28. package/lib/typescript/src/LiquidGlassContainer.d.ts.map +1 -1
  29. package/lib/typescript/src/LiquidGlassView.d.ts +25 -15
  30. package/lib/typescript/src/LiquidGlassView.d.ts.map +1 -1
  31. package/lib/typescript/src/ProgressiveBlurView.d.ts +11 -2
  32. package/lib/typescript/src/ProgressiveBlurView.d.ts.map +1 -1
  33. package/lib/typescript/src/ReactNativeBlurSwitchNativeComponent.d.ts +2 -32
  34. package/lib/typescript/src/ReactNativeBlurSwitchNativeComponent.d.ts.map +1 -1
  35. package/lib/typescript/src/ReactNativeBlurViewNativeComponent.d.ts +2 -1
  36. package/lib/typescript/src/ReactNativeBlurViewNativeComponent.d.ts.map +1 -1
  37. package/lib/typescript/src/ReactNativeLiquidGlassContainerNativeComponent.d.ts +0 -5
  38. package/lib/typescript/src/ReactNativeLiquidGlassContainerNativeComponent.d.ts.map +1 -1
  39. package/lib/typescript/src/ReactNativeLiquidGlassViewNativeComponent.d.ts +0 -30
  40. package/lib/typescript/src/ReactNativeLiquidGlassViewNativeComponent.d.ts.map +1 -1
  41. package/lib/typescript/src/ReactNativeProgressiveBlurViewNativeComponent.d.ts +2 -1
  42. package/lib/typescript/src/ReactNativeProgressiveBlurViewNativeComponent.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/BlurSwitch.tsx +23 -3
  45. package/src/BlurView.tsx +14 -2
  46. package/src/LiquidGlassContainer.tsx +3 -2
  47. package/src/LiquidGlassView.tsx +25 -15
  48. package/src/ProgressiveBlurView.tsx +15 -2
  49. package/src/ReactNativeBlurSwitchNativeComponent.ts +2 -37
  50. package/src/ReactNativeBlurViewNativeComponent.ts +2 -0
  51. package/src/ReactNativeLiquidGlassContainerNativeComponent.ts +0 -5
  52. package/src/ReactNativeLiquidGlassViewNativeComponent.ts +0 -35
  53. package/src/ReactNativeProgressiveBlurViewNativeComponent.ts +2 -0
package/README.md CHANGED
@@ -626,6 +626,7 @@ All props are optional and have sensible defaults.
626
626
  | ---------------------------------- | ------------ | ----------- | ----------------------------------------------------------------------------- |
627
627
  | `blurType` | `BlurType` | `'xlight'` | The type of blur effect to apply |
628
628
  | `blurAmount` | `number` | `10.0` | The intensity of the blur effect (0-100) |
629
+ | `blurRounds` | `number` | `5` | The number of blur interactions must be an integer value (1-15) |
629
630
  | `ignoreSafeArea` | `boolean` | `true` | (iOS only) Controls whether the blur effect should ignore all safe area edges |
630
631
  | `reducedTransparencyFallbackColor` | `string` | `'#FFFFFF'` | Fallback color when reduced transparency is enabled |
631
632
  | `overlayColor` | `ColorValue` | `undefined` | The overlay color to apply on top of the blur effect |
@@ -647,16 +648,17 @@ All props are optional and have sensible defaults.
647
648
 
648
649
  All props are optional and have sensible defaults.
649
650
 
650
- | Prop | Type | Default | Description |
651
- | ---------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------- | ---------------------------------------------------- |
652
- | `blurType` | `BlurType` | `'regular'` | The type of blur effect to apply |
653
- | `blurAmount` | `number` | `20.0` | Maximum blur radius in pixels |
654
- | `direction` | `'blurredTopClearBottom' \| 'blurredBottomClearTop' \| 'blurredCenterClearTopAndBottom'` | `'blurredTopClearBottom'` | Direction of the blur gradient |
655
- | `startOffset` | `number` | `0.0` | Where the gradient starts (0.0 to 1.0) |
656
- | `reducedTransparencyFallbackColor` | `string` | `'#FFFFFF'` | Fallback color when reduced transparency is enabled |
657
- | `overlayColor` | `ColorValue` | `undefined` | The overlay color to apply on top of the blur effect |
658
- | `style` | `ViewStyle` | `undefined` | Style object for the blur view |
659
- | `children` | `ReactNode` | `undefined` | Child components to render inside the blur view |
651
+ | Prop | Type | Default | Description |
652
+ | ---------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------------------------- |
653
+ | `blurType` | `BlurType` | `'regular'` | The type of blur effect to apply |
654
+ | `blurAmount` | `number` | `20.0` | Maximum blur radius in pixels |
655
+ | `blurRounds` | `number` | `5` | The number of blur interactions must be an integer value (1-15) |
656
+ | `direction` | `'blurredTopClearBottom' \| 'blurredBottomClearTop' \| 'blurredCenterClearTopAndBottom'` | `'blurredTopClearBottom'` | Direction of the blur gradient |
657
+ | `startOffset` | `number` | `0.0` | Where the gradient starts (0.0 to 1.0) |
658
+ | `reducedTransparencyFallbackColor` | `string` | `'#FFFFFF'` | Fallback color when reduced transparency is enabled |
659
+ | `overlayColor` | `ColorValue` | `undefined` | The overlay color to apply on top of the blur effect |
660
+ | `style` | `ViewStyle` | `undefined` | Style object for the blur view |
661
+ | `children` | `ReactNode` | `undefined` | Child components to render inside the blur view |
660
662
 
661
663
  > **Platform Note**: `ProgressiveBlurView` works on both **iOS** and **Android**.
662
664
  >
@@ -696,7 +698,8 @@ All props are optional and have sensible defaults.
696
698
  | --------------- | ------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------- |
697
699
  | `value` | `boolean` | `false` | The current value of the switch |
698
700
  | `onValueChange` | `(value: boolean) => void` | `undefined` | Callback invoked when the switch value changes |
699
- | `blurAmount` | `number` | `10` | (Android only) The intensity of the blur effect (0-100) |
701
+ | `blurAmount` | `number` | `10.0` | (Android only) The intensity of the blur effect (0-100) |
702
+ | `blurRounds` | `number` | `5` | The number of blur interactions must be an integer value (1-15) |
700
703
  | `thumbColor` | `ColorValue` | `'#FFFFFF'` | (iOS only) The color of the switch thumb |
701
704
  | `trackColor` | `{ false?: ColorValue; true?: ColorValue }` | `{ false: '#E5E5EA', true: '#34C759' }` | Track colors. On Android, only `true` is used - QmBlurView auto-calculates on/off colors from base color |
702
705
  | `disabled` | `boolean` | `false` | Whether the switch is disabled (prevents interaction but maintains current value) |
@@ -845,6 +848,7 @@ interface MyGlassContainerProps {
845
848
  const blurProps: BlurViewProps = {
846
849
  blurType: 'systemMaterial',
847
850
  blurAmount: 50,
851
+ blurRounds: 10,
848
852
  reducedTransparencyFallbackColor: '#FFFFFF',
849
853
  overlayColor: '#FF000040',
850
854
  };
@@ -867,6 +871,7 @@ const blurSwitchProps: BlurSwitchProps = {
867
871
  value: true,
868
872
  onValueChange: (value) => console.log(value),
869
873
  blurAmount: 20,
874
+ blurRounds: 15,
870
875
  trackColor: { true: '#34C759' },
871
876
  disabled: false,
872
877
  };
@@ -1,5 +1,6 @@
1
1
  package com.sbaiahmed1.reactnativeblur
2
2
 
3
+ import android.content.res.Configuration
3
4
  import android.graphics.Color
4
5
 
5
6
  /**
@@ -9,51 +10,52 @@ import android.graphics.Color
9
10
  enum class BlurType(val overlayColor: Int) {
10
11
  XLIGHT(Color.argb(140, 240, 240, 240)),
11
12
  LIGHT(Color.argb(42, 255, 255, 255)),
12
- DARK(Color.argb(120, 25, 25, 25)),
13
+ DARK(Color.argb(120, 26, 22, 22)),
13
14
  EXTRA_DARK(Color.argb(160, 35, 35, 35)),
14
- REGULAR(Color.argb(35, 255, 255, 255)),
15
- PROMINENT(Color.argb(130, 240, 240, 240)),
16
- SYSTEM_ULTRA_THIN_MATERIAL(Color.argb(75, 240, 240, 240)),
17
- SYSTEM_ULTRA_THIN_MATERIAL_LIGHT(Color.argb(77, 240, 240, 240)),
15
+ REGULAR_LIGHT(Color.argb(35, 255, 255, 255)),
16
+ REGULAR_DARK(Color.argb(35, 28, 28, 30)),
17
+ PROMINENT_LIGHT(Color.argb(140, 240, 240, 240)),
18
+ PROMINENT_DARK(Color.argb(140, 28, 28, 30)),
19
+ SYSTEM_ULTRA_THIN_MATERIAL_LIGHT(Color.argb(75, 240, 240, 240)),
18
20
  SYSTEM_ULTRA_THIN_MATERIAL_DARK(Color.argb(65, 40, 40, 40)),
19
- SYSTEM_THIN_MATERIAL(Color.argb(102, 240, 240, 240)),
20
- SYSTEM_THIN_MATERIAL_LIGHT(Color.argb(105, 240, 240, 240)),
21
+ SYSTEM_THIN_MATERIAL_LIGHT(Color.argb(102, 240, 240, 240)),
21
22
  SYSTEM_THIN_MATERIAL_DARK(Color.argb(102, 35, 35, 35)),
22
- SYSTEM_MATERIAL(Color.argb(130, 242, 242, 242)),
23
- SYSTEM_MATERIAL_LIGHT(Color.argb(130, 245, 245, 245)),
23
+ SYSTEM_MATERIAL_LIGHT(Color.argb(140, 245, 245, 245)),
24
24
  SYSTEM_MATERIAL_DARK(Color.argb(215, 65, 60, 60)),
25
- SYSTEM_THICK_MATERIAL(Color.argb(160, 240, 240, 240)),
26
- SYSTEM_THICK_MATERIAL_LIGHT(Color.argb(160, 242, 242, 242)),
25
+ SYSTEM_THICK_MATERIAL_LIGHT(Color.argb(210, 248, 248, 248)),
27
26
  SYSTEM_THICK_MATERIAL_DARK(Color.argb(160, 35, 35, 35)),
28
- SYSTEM_CHROME_MATERIAL(Color.argb(135, 240, 240, 240)),
29
- SYSTEM_CHROME_MATERIAL_LIGHT(Color.argb(135, 242, 242, 242)),
30
- SYSTEM_CHROME_MATERIAL_DARK(Color.argb(90, 32, 32, 32));
27
+ SYSTEM_CHROME_MATERIAL_LIGHT(Color.argb(165, 248, 248, 248)),
28
+ SYSTEM_CHROME_MATERIAL_DARK(Color.argb(100, 32, 32, 32));
31
29
 
32
30
  companion object {
33
31
  /**
34
32
  * Get BlurType from string, with fallback to LIGHT for unknown types.
33
+ * Uses the provided configuration to determine if dark mode is active for
34
+ * appropriate defaults.
35
35
  */
36
- fun fromString(type: String): BlurType {
36
+ fun fromString(type: String, configuration: Configuration): BlurType {
37
+ val isDarkMode = (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
38
+
37
39
  return when (type.lowercase()) {
38
40
  "xlight" -> XLIGHT
39
41
  "light" -> LIGHT
40
42
  "dark" -> DARK
41
43
  "extradark" -> EXTRA_DARK
42
- "regular" -> REGULAR
43
- "prominent" -> PROMINENT
44
- "systemultrathinmaterial" -> SYSTEM_ULTRA_THIN_MATERIAL
44
+ "regular" -> if (isDarkMode) REGULAR_DARK else REGULAR_LIGHT
45
+ "prominent" -> if (isDarkMode) PROMINENT_DARK else PROMINENT_LIGHT
46
+ "systemultrathinmaterial" -> if (isDarkMode) SYSTEM_ULTRA_THIN_MATERIAL_DARK else SYSTEM_ULTRA_THIN_MATERIAL_LIGHT
45
47
  "systemultrathinmateriallight" -> SYSTEM_ULTRA_THIN_MATERIAL_LIGHT
46
48
  "systemultrathinmaterialdark" -> SYSTEM_ULTRA_THIN_MATERIAL_DARK
47
- "systemthinmaterial" -> SYSTEM_THIN_MATERIAL
49
+ "systemthinmaterial" -> if (isDarkMode) SYSTEM_THIN_MATERIAL_DARK else SYSTEM_THIN_MATERIAL_LIGHT
48
50
  "systemthinmateriallight" -> SYSTEM_THIN_MATERIAL_LIGHT
49
51
  "systemthinmaterialdark" -> SYSTEM_THIN_MATERIAL_DARK
50
- "systemmaterial" -> SYSTEM_MATERIAL
52
+ "systemmaterial" -> if (isDarkMode) SYSTEM_MATERIAL_DARK else SYSTEM_MATERIAL_LIGHT
51
53
  "systemmateriallight" -> SYSTEM_MATERIAL_LIGHT
52
54
  "systemmaterialdark" -> SYSTEM_MATERIAL_DARK
53
- "systemthickmaterial" -> SYSTEM_THICK_MATERIAL
55
+ "systemthickmaterial" -> if (isDarkMode) SYSTEM_THICK_MATERIAL_DARK else SYSTEM_THICK_MATERIAL_LIGHT
54
56
  "systemthickmateriallight" -> SYSTEM_THICK_MATERIAL_LIGHT
55
57
  "systemthickmaterialdark" -> SYSTEM_THICK_MATERIAL_DARK
56
- "systemchromematerial" -> SYSTEM_CHROME_MATERIAL
58
+ "systemchromematerial" -> if (isDarkMode) SYSTEM_CHROME_MATERIAL_DARK else SYSTEM_CHROME_MATERIAL_LIGHT
57
59
  "systemchromemateriallight" -> SYSTEM_CHROME_MATERIAL_LIGHT
58
60
  "systemchromematerialdark" -> SYSTEM_CHROME_MATERIAL_DARK
59
61
  else -> XLIGHT // default fallback
@@ -22,9 +22,11 @@ class ReactNativeBlurSwitch : BlurSwitchButtonView {
22
22
  private var onValueChangeListener: ((Boolean) -> Unit)? = null
23
23
  private var currentValue = false
24
24
  private var isDisabled = false
25
+ private var currentBlurRounds = DEFAULT_BLUR_ROUNDS
25
26
 
26
27
  companion object {
27
28
  private const val TAG = "ReactNativeBlurSwitch"
29
+ private const val DEFAULT_BLUR_ROUNDS = 5
28
30
  private const val DEFAULT_WIDTH_DP = 65f
29
31
  private const val DEFAULT_HEIGHT_DP = 36f
30
32
  private const val MIN_BLUR_AMOUNT = 0f
@@ -70,7 +72,7 @@ class ReactNativeBlurSwitch : BlurSwitchButtonView {
70
72
  */
71
73
  private fun initializeSwitch() {
72
74
  try {
73
- blurRounds = 5
75
+ blurRounds = currentBlurRounds
74
76
 
75
77
  setOnCheckedChangeListener { isChecked ->
76
78
  if (isDisabled) {
@@ -172,6 +174,22 @@ class ReactNativeBlurSwitch : BlurSwitchButtonView {
172
174
  }
173
175
  }
174
176
 
177
+ /**
178
+ * Set the number of blur rounds.
179
+ * @param rounds The number of blur rounds (1-15)
180
+ */
181
+ fun setRounds(rounds: Int) {
182
+ val blurRounds = rounds.coerceIn(1, 15)
183
+ currentBlurRounds = blurRounds
184
+ logDebug("setRounds: $rounds -> $blurRounds")
185
+
186
+ try {
187
+ super.setBlurRounds(blurRounds)
188
+ } catch (e: Exception) {
189
+ logError("Failed to set blur rounds: ${e.message}", e)
190
+ }
191
+ }
192
+
175
193
  /**
176
194
  * Set whether the switch is disabled.
177
195
  * @param disabled True to disable, false to enable
@@ -40,6 +40,11 @@ class ReactNativeBlurSwitchManager : SimpleViewManager<ReactNativeBlurSwitch>()
40
40
  view?.setBlurAmount(blurAmount.toFloat())
41
41
  }
42
42
 
43
+ @ReactProp(name = "blurRounds")
44
+ fun setBlurRounds(view: ReactNativeBlurSwitch?, blurRounds: Int) {
45
+ view?.setRounds(blurRounds)
46
+ }
47
+
43
48
  @ReactProp(name = "thumbColor")
44
49
  fun setThumbColor(view: ReactNativeBlurSwitch?, color: String?) {
45
50
  color?.let {
@@ -1,6 +1,7 @@
1
1
  package com.sbaiahmed1.reactnativeblur
2
2
 
3
3
  import android.content.Context
4
+ import android.content.res.Configuration
4
5
  import android.graphics.Color
5
6
  import android.graphics.Outline
6
7
  import android.util.AttributeSet
@@ -30,11 +31,13 @@ import android.view.View.MeasureSpec
30
31
  class ReactNativeBlurView : BlurViewGroup {
31
32
  private var currentBlurRadius = DEFAULT_BLUR_RADIUS
32
33
  private var currentOverlayColor = Color.TRANSPARENT
34
+ private var currentBlurRounds = DEFAULT_BLUR_ROUNDS
33
35
  private var currentCornerRadius = 0f
34
36
  private var glassTintColor: Int = Color.TRANSPARENT
35
37
  private var glassOpacity: Float = 1.0f
36
38
  private var viewType: String = "blur"
37
39
  private var glassType: String = "clear"
40
+ private var currentBlurType: String = "xlight"
38
41
  private var isBlurInitialized: Boolean = false
39
42
  private var initRunnable: Runnable? = null
40
43
 
@@ -42,6 +45,7 @@ class ReactNativeBlurView : BlurViewGroup {
42
45
  private const val TAG = "ReactNativeBlurView"
43
46
  private const val MAX_BLUR_RADIUS = 100f
44
47
  private const val DEFAULT_BLUR_RADIUS = 10f
48
+ private const val DEFAULT_BLUR_ROUNDS = 5
45
49
  private const val DEBUG = false
46
50
 
47
51
  // Cross-platform blur amount constants
@@ -91,7 +95,7 @@ class ReactNativeBlurView : BlurViewGroup {
91
95
  super.setBackgroundColor(currentOverlayColor)
92
96
  clipChildren = true
93
97
  clipToOutline = true
94
- blurRounds = 5
98
+ blurRounds = currentBlurRounds
95
99
  super.setDownsampleFactor(6.0F)
96
100
  }
97
101
 
@@ -187,16 +191,18 @@ class ReactNativeBlurView : BlurViewGroup {
187
191
  /**
188
192
  * Finds the optimal view to use as blur capture root.
189
193
  *
190
- * Returns the nearest react-native-screens Screen ancestor if found, which scopes
191
- * the blur to the current screen and prevents capturing navigation transitions.
192
- *
193
- * Returns null when no Screen ancestor exists (e.g. modals, standalone usage).
194
- * A null return means swapBlurRootToScreenAncestor() is a no-op and QmBlurView
195
- * keeps its default decor view as the blur root this is correct for modals
196
- * because they need to blur the content behind them (in the main activity window).
194
+ * Priority:
195
+ * 1. Nearest react-native-screens Screen ancestor scopes blur to the current
196
+ * screen and prevents capturing navigation transition artifacts.
197
+ * 2. Nearest ReactRootView ancestor scopes blur to the React Native root when
198
+ * the component is not inside a Screen (e.g. plain View hierarchies). Without
199
+ * this fallback, QmBlurView defaults to the activity decor view and blurs the
200
+ * entire screen instead of just the component area (issue #89).
201
+ * 3. null — returned for modals, which intentionally need to blur content from
202
+ * the main activity window (decor view is correct there).
197
203
  */
198
204
  private fun findOptimalBlurRoot(): ViewGroup? {
199
- return findNearestScreenAncestor()
205
+ return findNearestScreenAncestor() ?: findNearestReactRootView()
200
206
  }
201
207
 
202
208
  /**
@@ -214,6 +220,22 @@ class ReactNativeBlurView : BlurViewGroup {
214
220
  return null
215
221
  }
216
222
 
223
+ /**
224
+ * Walks up the view hierarchy looking for the React Native root view.
225
+ * Used as a fallback when no Screen ancestor exists, to scope the blur
226
+ * capture to the RN root rather than the full activity decor view.
227
+ */
228
+ private fun findNearestReactRootView(): ViewGroup? {
229
+ var currentParent = this.parent
230
+ while (currentParent != null) {
231
+ if (currentParent.javaClass.name == "com.facebook.react.ReactRootView") {
232
+ return currentParent as? ViewGroup
233
+ }
234
+ currentParent = currentParent.parent
235
+ }
236
+ return null
237
+ }
238
+
217
239
  /**
218
240
  * Initialize the blur view with current settings.
219
241
  * Called after the view is attached and the blur root has been swapped.
@@ -270,12 +292,29 @@ class ReactNativeBlurView : BlurViewGroup {
270
292
  }
271
293
  }
272
294
 
295
+ /**
296
+ * Set the number of blur rounds.
297
+ * @param rounds The number of blur rounds (1-15)
298
+ */
299
+ fun setRounds(rounds: Int) {
300
+ val blurRounds = rounds.coerceIn(1, 15)
301
+ currentBlurRounds = blurRounds
302
+ logDebug("setRounds: $rounds -> $blurRounds")
303
+
304
+ try {
305
+ super.setBlurRounds(blurRounds)
306
+ } catch (e: Exception) {
307
+ logError("Failed to set blur rounds: ${e.message}", e)
308
+ }
309
+ }
310
+
273
311
  /**
274
312
  * Set the blur type which determines the overlay color.
275
313
  * @param type The blur type string (case-insensitive)
276
314
  */
277
315
  fun setBlurType(type: String) {
278
- val blurType = BlurType.fromString(type)
316
+ currentBlurType = type
317
+ val blurType = BlurType.fromString(type, resources.configuration)
279
318
  currentOverlayColor = blurType.overlayColor
280
319
  logDebug("setBlurType: $type -> ${blurType.name}")
281
320
 
@@ -460,4 +499,18 @@ class ReactNativeBlurView : BlurViewGroup {
460
499
  // re-positioning children based on its own logic (e.g. gravity), which would
461
500
  // conflict with React Native's layout.
462
501
  }
502
+
503
+ /**
504
+ * Handle configuration changes, such as dark mode or orientation changes.
505
+ * This ensures the blur view updates its overlay color based on the new
506
+ * configuration.
507
+ */
508
+ override fun onConfigurationChanged(newConfig: Configuration) {
509
+ super.onConfigurationChanged(newConfig)
510
+
511
+ if (viewType == "blur") {
512
+ // Re-apply blur type to update overlay color based on new configuration
513
+ setBlurType(currentBlurType)
514
+ }
515
+ }
463
516
  }
@@ -39,6 +39,11 @@ class ReactNativeBlurViewManager : ViewGroupManager<ReactNativeBlurView>(),
39
39
  view?.setBlurAmount(blurAmount.toFloat())
40
40
  }
41
41
 
42
+ @ReactProp(name = "blurRounds")
43
+ override fun setBlurRounds(view: ReactNativeBlurView?, blurRounds: Int) {
44
+ view?.setRounds(blurRounds)
45
+ }
46
+
42
47
  @ReactProp(name = "borderRadius")
43
48
  override fun setBorderRadius(view: ReactNativeBlurView?, borderRadius: Float) {
44
49
  view?.setBorderRadius(borderRadius)
@@ -1,6 +1,7 @@
1
1
  package com.sbaiahmed1.reactnativeblur
2
2
 
3
3
  import android.content.Context
4
+ import android.content.res.Configuration
4
5
  import android.graphics.Canvas
5
6
  import android.graphics.Color
6
7
  import android.graphics.LinearGradient
@@ -30,7 +31,9 @@ class ReactNativeProgressiveBlurView : FrameLayout {
30
31
  private val gradientPaint = Paint(Paint.ANTI_ALIAS_FLAG)
31
32
 
32
33
  private var currentBlurRadius = DEFAULT_BLUR_RADIUS
34
+ private var currentBlurRounds = DEFAULT_BLUR_ROUNDS
33
35
  private var currentOverlayColor = Color.TRANSPARENT
36
+ private var currentBlurType = "xlight"
34
37
  private var currentDirection = "topToBottom"
35
38
  private var currentStartOffset = 0.0f
36
39
  private var hasExplicitBackground: Boolean = false
@@ -42,6 +45,7 @@ class ReactNativeProgressiveBlurView : FrameLayout {
42
45
  private const val TAG = "ReactNativeProgressiveBlur"
43
46
  private const val MAX_BLUR_RADIUS = 100f
44
47
  private const val DEFAULT_BLUR_RADIUS = 10f
48
+ private const val DEFAULT_BLUR_ROUNDS = 5
45
49
  private const val DEBUG = false
46
50
 
47
51
  // Cross-platform blur amount constants
@@ -129,7 +133,7 @@ class ReactNativeProgressiveBlurView : FrameLayout {
129
133
  blurView = BlurView(context, null).apply {
130
134
  layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
131
135
  setDownsampleFactor(6.0F)
132
- blurRounds = 5
136
+ blurRounds = currentBlurRounds
133
137
  }
134
138
  addView(blurView)
135
139
  }
@@ -209,16 +213,18 @@ class ReactNativeProgressiveBlurView : FrameLayout {
209
213
  /**
210
214
  * Finds the optimal view to use as blur capture root.
211
215
  *
212
- * Returns the nearest react-native-screens Screen ancestor if found, which scopes
213
- * the blur to the current screen and prevents capturing navigation transitions.
214
- *
215
- * Returns null when no Screen ancestor exists (e.g. modals, standalone usage).
216
- * A null return means swapBlurRootToScreenAncestor() is a no-op and QmBlurView
217
- * keeps its default decor view as the blur root this is correct for modals
218
- * because they need to blur the content behind them (in the main activity window).
216
+ * Priority:
217
+ * 1. Nearest react-native-screens Screen ancestor scopes blur to the current
218
+ * screen and prevents capturing navigation transition artifacts.
219
+ * 2. Nearest ReactRootView ancestor scopes blur to the React Native root when
220
+ * the component is not inside a Screen (e.g. plain View hierarchies). Without
221
+ * this fallback, QmBlurView defaults to the activity decor view and blurs the
222
+ * entire screen instead of just the component area (issue #89).
223
+ * 3. null — returned for modals, which intentionally need to blur content from
224
+ * the main activity window (decor view is correct there).
219
225
  */
220
226
  private fun findOptimalBlurRoot(): ViewGroup? {
221
- return findNearestScreenAncestor()
227
+ return findNearestScreenAncestor() ?: findNearestReactRootView()
222
228
  }
223
229
 
224
230
  /**
@@ -235,6 +241,22 @@ class ReactNativeProgressiveBlurView : FrameLayout {
235
241
  return null
236
242
  }
237
243
 
244
+ /**
245
+ * Walks up the view hierarchy looking for the React Native root view.
246
+ * Used as a fallback when no Screen ancestor exists, to scope the blur
247
+ * capture to the RN root rather than the full activity decor view.
248
+ */
249
+ private fun findNearestReactRootView(): ViewGroup? {
250
+ var currentParent = this.parent
251
+ while (currentParent != null) {
252
+ if (currentParent.javaClass.name == "com.facebook.react.ReactRootView") {
253
+ return currentParent as? ViewGroup
254
+ }
255
+ currentParent = currentParent.parent
256
+ }
257
+ return null
258
+ }
259
+
238
260
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
239
261
  val width = MeasureSpec.getSize(widthMeasureSpec)
240
262
  val height = MeasureSpec.getSize(heightMeasureSpec)
@@ -375,6 +397,17 @@ class ReactNativeProgressiveBlurView : FrameLayout {
375
397
  cleanup()
376
398
  }
377
399
 
400
+ /**
401
+ * Handle configuration changes, such as dark mode or orientation changes.
402
+ * This ensures the blur view updates its overlay color based on the new
403
+ * configuration.
404
+ */
405
+ override fun onConfigurationChanged(newConfig: Configuration) {
406
+ super.onConfigurationChanged(newConfig)
407
+
408
+ setBlurType(currentBlurType)
409
+ }
410
+
378
411
  /**
379
412
  * Cleanup method to prevent memory leaks.
380
413
  * Resets initialization state so blur is re-initialized on next attach.
@@ -429,6 +462,22 @@ class ReactNativeProgressiveBlurView : FrameLayout {
429
462
  }
430
463
  }
431
464
 
465
+ /**
466
+ * Set the number of blur rounds.
467
+ * @param rounds The number of blur rounds (1-15)
468
+ */
469
+ fun setRounds(rounds: Int) {
470
+ val blurRounds = rounds.coerceIn(1, 15)
471
+ currentBlurRounds = blurRounds
472
+ logDebug("setRounds: $rounds -> $blurRounds")
473
+
474
+ try {
475
+ blurView?.blurRounds = blurRounds
476
+ } catch (e: Exception) {
477
+ logError("Failed to set blur rounds: ${e.message}", e)
478
+ }
479
+ }
480
+
432
481
  /**
433
482
  * Set the direction of the progressive blur gradient.
434
483
  * @param direction The direction string: "blurredTopClearBottom" or "blurredBottomClearTop"
@@ -476,7 +525,8 @@ class ReactNativeProgressiveBlurView : FrameLayout {
476
525
  * @param type The blur type string (case-insensitive)
477
526
  */
478
527
  fun setBlurType(type: String) {
479
- val blurType = BlurType.fromString(type)
528
+ currentBlurType = type
529
+ val blurType = BlurType.fromString(type, resources.configuration)
480
530
  currentOverlayColor = blurType.overlayColor
481
531
  logDebug("setBlurType: $type -> ${blurType.name} -> ${Integer.toHexString(currentOverlayColor)}")
482
532
 
@@ -45,6 +45,11 @@ class ReactNativeProgressiveBlurViewManager : SimpleViewManager<ReactNativeProgr
45
45
  view?.setBlurAmount(blurAmount.toFloat())
46
46
  }
47
47
 
48
+ @ReactProp(name = "blurRounds")
49
+ override fun setBlurRounds(view: ReactNativeProgressiveBlurView?, blurRounds: Int) {
50
+ view?.setRounds(blurRounds)
51
+ }
52
+
48
53
  @ReactProp(name = "direction")
49
54
  override fun setDirection(view: ReactNativeProgressiveBlurView?, direction: String?) {
50
55
  // Provide default value if direction is null or empty
@@ -21,41 +21,54 @@ class BlurEffectView: UIVisualEffectView {
21
21
  }
22
22
 
23
23
  func updateBlur(style: UIBlurEffect.Style, intensity: Double) {
24
+ // Skip expensive animator recreation when nothing changed.
25
+ // During FlashList recycling, updateUIView fires on every layout pass
26
+ // even when props are identical, causing jank (issue #100).
27
+ guard style != self.blurStyle || intensity != self.intensity else { return }
24
28
  self.blurStyle = style
25
29
  self.intensity = intensity
26
30
  setupBlur()
27
31
  }
28
32
 
33
+ override func didMoveToWindow() {
34
+ super.didMoveToWindow()
35
+ guard window != nil else { return }
36
+ // UIKit resumes paused CAAnimations when a view re-joins a window
37
+ // (e.g. after modal dismiss + re-present). If the animation plays
38
+ // toward its end state the blur drifts to full intensity. Re-pause
39
+ // and re-set the fraction here to lock it back to our intended value.
40
+ // pausesOnCompletion = true (set in setupBlur) ensures the animator
41
+ // stays .active even if it reaches fraction 1.0, so this is always safe.
42
+ animator?.pauseAnimation()
43
+ animator?.fractionComplete = intensity
44
+ }
45
+
29
46
  private func setupBlur() {
30
- // Clean up existing animator
31
- if let animator = animator {
32
- animator.stopAnimation(true)
33
- animator.finishAnimation(at: .current)
47
+ if let existing = animator, existing.state == .active {
48
+ existing.stopAnimation(true)
34
49
  }
35
50
  animator = nil
36
51
 
37
- // Reset effect
38
52
  effect = nil
39
53
 
40
- // Create new animator
41
- animator = UIViewPropertyAnimator(duration: 1, curve: .linear)
42
- animator?.addAnimations { [weak self] in
54
+ let newAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
55
+ newAnimator.addAnimations { [weak self] in
43
56
  self?.effect = UIBlurEffect(style: self?.blurStyle ?? .systemMaterial)
44
57
  }
45
-
46
- // Set intensity
47
- animator?.fractionComplete = intensity
48
- // Stop the animation at the current state
49
- DispatchQueue.main.async { [weak self] in
50
- self?.animator?.stopAnimation(true)
51
- self?.animator?.finishAnimation(at: .current)
52
- }
58
+ // pausesOnCompletion: if UIKit ever resumes and runs this to the end,
59
+ // the animator stays .active (paused at 1.0) instead of going .inactive.
60
+ // This guarantees didMoveToWindow can always call pauseAnimation() safely.
61
+ newAnimator.pausesOnCompletion = true
62
+ newAnimator.startAnimation()
63
+ newAnimator.pauseAnimation()
64
+ newAnimator.fractionComplete = intensity
65
+ animator = newAnimator
53
66
  }
54
67
 
55
68
  deinit {
56
- guard let animator = animator, animator.state == .active else { return }
57
- animator.stopAnimation(true)
58
- animator.finishAnimation(at: .current)
69
+ if let animator = animator, animator.state == .active {
70
+ animator.stopAnimation(true)
71
+ }
59
72
  }
60
73
  }
61
74
 
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import React from 'react';
4
- import { Platform, Switch } from 'react-native';
4
+ import { Platform, StyleSheet, Switch } from 'react-native';
5
5
  import ReactNativeBlurSwitch from './ReactNativeBlurSwitchNativeComponent';
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
7
  const toColorString = (color, fallback) => {
@@ -30,8 +30,9 @@ const toColorString = (color, fallback) => {
30
30
  */
31
31
  export const BlurSwitch = ({
32
32
  value = false,
33
- onValueChange,
34
33
  blurAmount = 10,
34
+ blurRounds = 5,
35
+ onValueChange,
35
36
  thumbColor = '#FFFFFF',
36
37
  trackColor = {
37
38
  false: '#E5E5EA',
@@ -53,15 +54,13 @@ export const BlurSwitch = ({
53
54
  });
54
55
  }
55
56
  return /*#__PURE__*/_jsx(ReactNativeBlurSwitch, {
56
- style: [{
57
- width: 65,
58
- height: 36
59
- }, style],
57
+ style: [styles.switch, style],
60
58
  value: value,
61
59
  onValueChange: event => {
62
60
  onValueChange?.(event.nativeEvent.value);
63
61
  },
64
62
  blurAmount: blurAmount,
63
+ blurRounds: blurRounds,
65
64
  thumbColor: toColorString(thumbColor, '#FFFFFF'),
66
65
  trackColorOff: toColorString(trackColor?.false, '#E5E5EA'),
67
66
  trackColorOn: toColorString(trackColor?.true, '#34C759'),
@@ -69,5 +68,11 @@ export const BlurSwitch = ({
69
68
  ...props
70
69
  });
71
70
  };
71
+ const styles = StyleSheet.create({
72
+ switch: {
73
+ width: 65,
74
+ height: 36
75
+ }
76
+ });
72
77
  export default BlurSwitch;
73
78
  //# sourceMappingURL=BlurSwitch.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["React","Platform","Switch","ReactNativeBlurSwitch","jsx","_jsx","toColorString","color","fallback","BlurSwitch","value","onValueChange","blurAmount","thumbColor","trackColor","false","true","disabled","style","props","OS","width","height","event","nativeEvent","trackColorOff","trackColorOn"],"sourceRoot":"../../src","sources":["BlurSwitch.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,QAAQ,EAAEC,MAAM,QAAQ,cAAc;AAE/C,OAAOC,qBAAqB,MAAM,wCAAwC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE3E,MAAMC,aAAa,GAAGA,CACpBC,KAA6B,EAC7BC,QAAgB,KACL;EACX,IAAI,OAAOD,KAAK,KAAK,QAAQ,EAAE,OAAOA,KAAK;EAC3C,OAAOC,QAAQ;AACjB,CAAC;AA8DD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,UAAqC,GAAGA,CAAC;EACpDC,KAAK,GAAG,KAAK;EACbC,aAAa;EACbC,UAAU,GAAG,EAAE;EACfC,UAAU,GAAG,SAAS;EACtBC,UAAU,GAAG;IAAEC,KAAK,EAAE,SAAS;IAAEC,IAAI,EAAE;EAAU,CAAC;EAClDC,QAAQ,GAAG,KAAK;EAChBC,KAAK;EACL,GAAGC;AACL,CAAC,KAAK;EACJ,IAAIlB,QAAQ,CAACmB,EAAE,KAAK,KAAK,EAAE;IACzB,oBACEf,IAAA,CAACH,MAAM;MACLQ,KAAK,EAAEA,KAAM;MACbC,aAAa,EAAEA,aAAc;MAC7BE,UAAU,EAAEA,UAAW;MACvBC,UAAU,EAAEA,UAAW;MACvBG,QAAQ,EAAEA,QAAS;MACnBC,KAAK,EAAEA,KAAM;MAAA,GACTC;IAAK,CACV,CAAC;EAEN;EAEA,oBACEd,IAAA,CAACF,qBAAqB;IACpBe,KAAK,EAAE,CAAC;MAAEG,KAAK,EAAE,EAAE;MAAEC,MAAM,EAAE;IAAG,CAAC,EAAEJ,KAAK,CAAE;IAC1CR,KAAK,EAAEA,KAAM;IACbC,aAAa,EAAGY,KAAK,IAAK;MACxBZ,aAAa,GAAGY,KAAK,CAACC,WAAW,CAACd,KAAK,CAAC;IAC1C,CAAE;IACFE,UAAU,EAAEA,UAAW;IACvBC,UAAU,EAAEP,aAAa,CAACO,UAAU,EAAE,SAAS,CAAE;IACjDY,aAAa,EAAEnB,aAAa,CAACQ,UAAU,EAAEC,KAAK,EAAE,SAAS,CAAE;IAC3DW,YAAY,EAAEpB,aAAa,CAACQ,UAAU,EAAEE,IAAI,EAAE,SAAS,CAAE;IACzDC,QAAQ,EAAEA,QAAS;IAAA,GACfE;EAAK,CACV,CAAC;AAEN,CAAC;AAED,eAAeV,UAAU","ignoreList":[]}
1
+ {"version":3,"names":["React","Platform","StyleSheet","Switch","ReactNativeBlurSwitch","jsx","_jsx","toColorString","color","fallback","BlurSwitch","value","blurAmount","blurRounds","onValueChange","thumbColor","trackColor","false","true","disabled","style","props","OS","styles","switch","event","nativeEvent","trackColorOff","trackColorOn","create","width","height"],"sourceRoot":"../../src","sources":["BlurSwitch.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,QAAQ,EAAEC,UAAU,EAAEC,MAAM,QAAQ,cAAc;AAE3D,OAAOC,qBAAqB,MAAM,wCAAwC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE3E,MAAMC,aAAa,GAAGA,CACpBC,KAA6B,EAC7BC,QAAgB,KACL;EACX,IAAI,OAAOD,KAAK,KAAK,QAAQ,EAAE,OAAOA,KAAK;EAC3C,OAAOC,QAAQ;AACjB,CAAC;AAyED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,UAAqC,GAAGA,CAAC;EACpDC,KAAK,GAAG,KAAK;EACbC,UAAU,GAAG,EAAE;EACfC,UAAU,GAAG,CAAC;EACdC,aAAa;EACbC,UAAU,GAAG,SAAS;EACtBC,UAAU,GAAG;IAAEC,KAAK,EAAE,SAAS;IAAEC,IAAI,EAAE;EAAU,CAAC;EAClDC,QAAQ,GAAG,KAAK;EAChBC,KAAK;EACL,GAAGC;AACL,CAAC,KAAK;EACJ,IAAIpB,QAAQ,CAACqB,EAAE,KAAK,KAAK,EAAE;IACzB,oBACEhB,IAAA,CAACH,MAAM;MACLQ,KAAK,EAAEA,KAAM;MACbG,aAAa,EAAEA,aAAc;MAC7BC,UAAU,EAAEA,UAAW;MACvBC,UAAU,EAAEA,UAAW;MACvBG,QAAQ,EAAEA,QAAS;MACnBC,KAAK,EAAEA,KAAM;MAAA,GACTC;IAAK,CACV,CAAC;EAEN;EAEA,oBACEf,IAAA,CAACF,qBAAqB;IACpBgB,KAAK,EAAE,CAACG,MAAM,CAACC,MAAM,EAAEJ,KAAK,CAAE;IAC9BT,KAAK,EAAEA,KAAM;IACbG,aAAa,EAAGW,KAAK,IAAK;MACxBX,aAAa,GAAGW,KAAK,CAACC,WAAW,CAACf,KAAK,CAAC;IAC1C,CAAE;IACFC,UAAU,EAAEA,UAAW;IACvBC,UAAU,EAAEA,UAAW;IACvBE,UAAU,EAAER,aAAa,CAACQ,UAAU,EAAE,SAAS,CAAE;IACjDY,aAAa,EAAEpB,aAAa,CAACS,UAAU,EAAEC,KAAK,EAAE,SAAS,CAAE;IAC3DW,YAAY,EAAErB,aAAa,CAACS,UAAU,EAAEE,IAAI,EAAE,SAAS,CAAE;IACzDC,QAAQ,EAAEA,QAAS;IAAA,GACfE;EAAK,CACV,CAAC;AAEN,CAAC;AAED,MAAME,MAAM,GAAGrB,UAAU,CAAC2B,MAAM,CAAC;EAC/BL,MAAM,EAAE;IACNM,KAAK,EAAE,EAAE;IACTC,MAAM,EAAE;EACV;AACF,CAAC,CAAC;AAEF,eAAerB,UAAU","ignoreList":[]}