@sbaiahmed1/react-native-blur 4.6.2-beta.0 → 4.6.3-beta.0

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 (82) hide show
  1. package/README.md +16 -11
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/BlurType.kt +12 -19
  4. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurSwitch.kt +19 -1
  5. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurSwitchManager.kt +5 -0
  6. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt +98 -135
  7. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurViewManager.kt +25 -0
  8. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt +21 -1
  9. package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurViewManager.kt +5 -0
  10. package/ios/Helpers/BlurStyleHelpers.swift +17 -0
  11. package/ios/Views/AdvancedBlurView.swift +8 -5
  12. package/ios/Views/BasicColoredView.swift +1 -19
  13. package/ios/Views/BlurEffectView.swift +19 -19
  14. package/ios/Views/LiquidGlassContainerView.swift +4 -7
  15. package/ios/Views/ProgressiveBlurView.swift +8 -3
  16. package/ios/Views/VariableBlurView.swift +0 -11
  17. package/ios/Views/VibrancyEffectView.swift +3 -12
  18. package/lib/module/BlurSwitch.js +11 -6
  19. package/lib/module/BlurSwitch.js.map +1 -1
  20. package/lib/module/BlurView.js +2 -0
  21. package/lib/module/BlurView.js.map +1 -1
  22. package/lib/module/LiquidGlassContainer.js.map +1 -1
  23. package/lib/module/LiquidGlassView.js.map +1 -1
  24. package/lib/module/ProgressiveBlurView.js +3 -0
  25. package/lib/module/ProgressiveBlurView.js.map +1 -1
  26. package/lib/module/ReactNativeBlurSwitchNativeComponent.ts +2 -37
  27. package/lib/module/ReactNativeBlurViewNativeComponent.ts +2 -0
  28. package/lib/module/ReactNativeLiquidGlassContainerNativeComponent.ts +0 -5
  29. package/lib/module/ReactNativeLiquidGlassViewNativeComponent.ts +0 -35
  30. package/lib/module/ReactNativeProgressiveBlurViewNativeComponent.ts +2 -0
  31. package/lib/typescript/src/BlurSwitch.d.ts +10 -0
  32. package/lib/typescript/src/BlurSwitch.d.ts.map +1 -1
  33. package/lib/typescript/src/BlurView.d.ts +11 -2
  34. package/lib/typescript/src/BlurView.d.ts.map +1 -1
  35. package/lib/typescript/src/LiquidGlassContainer.d.ts +3 -2
  36. package/lib/typescript/src/LiquidGlassContainer.d.ts.map +1 -1
  37. package/lib/typescript/src/LiquidGlassView.d.ts +25 -15
  38. package/lib/typescript/src/LiquidGlassView.d.ts.map +1 -1
  39. package/lib/typescript/src/ProgressiveBlurView.d.ts +11 -2
  40. package/lib/typescript/src/ProgressiveBlurView.d.ts.map +1 -1
  41. package/lib/typescript/src/ReactNativeBlurSwitchNativeComponent.d.ts +2 -32
  42. package/lib/typescript/src/ReactNativeBlurSwitchNativeComponent.d.ts.map +1 -1
  43. package/lib/typescript/src/ReactNativeBlurViewNativeComponent.d.ts +2 -1
  44. package/lib/typescript/src/ReactNativeBlurViewNativeComponent.d.ts.map +1 -1
  45. package/lib/typescript/src/ReactNativeLiquidGlassContainerNativeComponent.d.ts +0 -5
  46. package/lib/typescript/src/ReactNativeLiquidGlassContainerNativeComponent.d.ts.map +1 -1
  47. package/lib/typescript/src/ReactNativeLiquidGlassViewNativeComponent.d.ts +0 -30
  48. package/lib/typescript/src/ReactNativeLiquidGlassViewNativeComponent.d.ts.map +1 -1
  49. package/lib/typescript/src/ReactNativeProgressiveBlurViewNativeComponent.d.ts +2 -1
  50. package/lib/typescript/src/ReactNativeProgressiveBlurViewNativeComponent.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/src/BlurSwitch.tsx +23 -3
  53. package/src/BlurView.tsx +14 -2
  54. package/src/LiquidGlassContainer.tsx +3 -2
  55. package/src/LiquidGlassView.tsx +25 -15
  56. package/src/ProgressiveBlurView.tsx +15 -2
  57. package/src/ReactNativeBlurSwitchNativeComponent.ts +2 -37
  58. package/src/ReactNativeBlurViewNativeComponent.ts +2 -0
  59. package/src/ReactNativeLiquidGlassContainerNativeComponent.ts +0 -5
  60. package/src/ReactNativeLiquidGlassViewNativeComponent.ts +0 -35
  61. package/src/ReactNativeProgressiveBlurViewNativeComponent.ts +2 -0
  62. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/ReactNativeBlurViewManagerDelegate.java +0 -53
  63. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/ReactNativeBlurViewManagerInterface.java +0 -25
  64. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/ReactNativeGlassEffectContainerManagerDelegate.java +0 -53
  65. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/ReactNativeGlassEffectContainerManagerInterface.java +0 -25
  66. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/ReactNativeGlassViewManagerDelegate.java +0 -38
  67. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/ReactNativeGlassViewManagerInterface.java +0 -20
  68. package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +0 -36
  69. package/android/app/build/generated/source/codegen/jni/ReactNativeBlurViewSpec-generated.cpp +0 -22
  70. package/android/app/build/generated/source/codegen/jni/ReactNativeBlurViewSpec.h +0 -24
  71. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/ComponentDescriptors.cpp +0 -23
  72. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/ComponentDescriptors.h +0 -25
  73. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/EventEmitters.cpp +0 -17
  74. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/EventEmitters.h +0 -30
  75. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/Props.cpp +0 -46
  76. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/Props.h +0 -131
  77. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/ReactNativeBlurViewSpecJSI-generated.cpp +0 -17
  78. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/ReactNativeBlurViewSpecJSI.h +0 -19
  79. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/ShadowNodes.cpp +0 -18
  80. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/ShadowNodes.h +0 -43
  81. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/States.cpp +0 -16
  82. package/android/app/build/generated/source/codegen/jni/react/renderer/components/ReactNativeBlurViewSpec/States.h +0 -41
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
  };
@@ -74,7 +74,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
74
74
  dependencies {
75
75
  implementation "com.facebook.react:react-android"
76
76
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
- implementation 'com.qmdeve.blurview:core:1.1.4'
77
+ implementation 'com.github.qmdeve:qmblurview:v1.1.5'
78
78
  }
79
79
 
80
80
  react {
@@ -2,37 +2,30 @@ package com.sbaiahmed1.reactnativeblur
2
2
 
3
3
  import android.graphics.Color
4
4
 
5
- /**
6
- * Enum representing different blur types with their corresponding overlay colors.
7
- * Maps iOS blur types to Android overlay colors to approximate the visual appearance.
8
- */
9
5
  enum class BlurType(val overlayColor: Int) {
10
6
  XLIGHT(Color.argb(140, 240, 240, 240)),
11
7
  LIGHT(Color.argb(42, 255, 255, 255)),
12
- DARK(Color.argb(120, 25, 25, 25)),
8
+ DARK(Color.argb(120, 26, 22, 22)),
13
9
  EXTRA_DARK(Color.argb(160, 35, 35, 35)),
14
10
  REGULAR(Color.argb(35, 255, 255, 255)),
15
- PROMINENT(Color.argb(130, 240, 240, 240)),
11
+ PROMINENT(Color.argb(140, 240, 240, 240)),
16
12
  SYSTEM_ULTRA_THIN_MATERIAL(Color.argb(75, 240, 240, 240)),
17
- SYSTEM_ULTRA_THIN_MATERIAL_LIGHT(Color.argb(77, 240, 240, 240)),
13
+ SYSTEM_ULTRA_THIN_MATERIAL_LIGHT(Color.argb(75, 240, 240, 240)),
18
14
  SYSTEM_ULTRA_THIN_MATERIAL_DARK(Color.argb(65, 40, 40, 40)),
19
15
  SYSTEM_THIN_MATERIAL(Color.argb(102, 240, 240, 240)),
20
- SYSTEM_THIN_MATERIAL_LIGHT(Color.argb(105, 240, 240, 240)),
16
+ SYSTEM_THIN_MATERIAL_LIGHT(Color.argb(102, 240, 240, 240)),
21
17
  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)),
18
+ SYSTEM_MATERIAL(Color.argb(140, 245, 245, 245)),
19
+ SYSTEM_MATERIAL_LIGHT(Color.argb(140, 245, 245, 245)),
24
20
  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)),
21
+ SYSTEM_THICK_MATERIAL(Color.argb(210, 248, 248, 248)),
22
+ SYSTEM_THICK_MATERIAL_LIGHT(Color.argb(210, 248, 248, 248)),
27
23
  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));
24
+ SYSTEM_CHROME_MATERIAL(Color.argb(165, 248, 248, 248)),
25
+ SYSTEM_CHROME_MATERIAL_LIGHT(Color.argb(165, 248, 248, 248)),
26
+ SYSTEM_CHROME_MATERIAL_DARK(Color.argb(100, 32, 32, 32));
31
27
 
32
28
  companion object {
33
- /**
34
- * Get BlurType from string, with fallback to LIGHT for unknown types.
35
- */
36
29
  fun fromString(type: String): BlurType {
37
30
  return when (type.lowercase()) {
38
31
  "xlight" -> XLIGHT
@@ -56,7 +49,7 @@ enum class BlurType(val overlayColor: Int) {
56
49
  "systemchromematerial" -> SYSTEM_CHROME_MATERIAL
57
50
  "systemchromemateriallight" -> SYSTEM_CHROME_MATERIAL_LIGHT
58
51
  "systemchromematerialdark" -> SYSTEM_CHROME_MATERIAL_DARK
59
- else -> XLIGHT // default fallback
52
+ else -> XLIGHT
60
53
  }
61
54
  }
62
55
  }
@@ -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 {
@@ -3,6 +3,8 @@ 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.graphics.Path
7
+ import android.os.Build
6
8
  import android.util.AttributeSet
7
9
  import android.util.Log
8
10
  import android.util.TypedValue
@@ -30,11 +32,17 @@ import android.view.View.MeasureSpec
30
32
  class ReactNativeBlurView : BlurViewGroup {
31
33
  private var currentBlurRadius = DEFAULT_BLUR_RADIUS
32
34
  private var currentOverlayColor = Color.TRANSPARENT
33
- private var currentCornerRadius = 0f
35
+ private var currentBlurRounds = DEFAULT_BLUR_ROUNDS
36
+ private var borderRadius = 0f
37
+ private var borderTopLeftRadius = -1f
38
+ private var borderTopRightRadius = -1f
39
+ private var borderBottomLeftRadius = -1f
40
+ private var borderBottomRightRadius = -1f
34
41
  private var glassTintColor: Int = Color.TRANSPARENT
35
42
  private var glassOpacity: Float = 1.0f
36
43
  private var viewType: String = "blur"
37
44
  private var glassType: String = "clear"
45
+ private var currentBlurType: String = "xlight"
38
46
  private var isBlurInitialized: Boolean = false
39
47
  private var initRunnable: Runnable? = null
40
48
 
@@ -42,9 +50,9 @@ class ReactNativeBlurView : BlurViewGroup {
42
50
  private const val TAG = "ReactNativeBlurView"
43
51
  private const val MAX_BLUR_RADIUS = 100f
44
52
  private const val DEFAULT_BLUR_RADIUS = 10f
53
+ private const val DEFAULT_BLUR_ROUNDS = 5
45
54
  private const val DEBUG = false
46
55
 
47
- // Cross-platform blur amount constants
48
56
  private const val MIN_BLUR_AMOUNT = 0f
49
57
  private const val MAX_BLUR_AMOUNT = 100f
50
58
 
@@ -62,12 +70,6 @@ class ReactNativeBlurView : BlurViewGroup {
62
70
  Log.e(TAG, message, throwable)
63
71
  }
64
72
 
65
- /**
66
- * Maps blur amount (0-100) to Android blur radius (0-25).
67
- * This ensures cross-platform consistency while respecting Android's limitations.
68
- * @param amount The blur amount from 0-100
69
- * @return The corresponding blur radius from 0-25
70
- */
71
73
  private fun mapBlurAmountToRadius(amount: Float): Float {
72
74
  val clampedAmount = amount.coerceIn(MIN_BLUR_AMOUNT, MAX_BLUR_AMOUNT)
73
75
  return (clampedAmount / MAX_BLUR_AMOUNT) * MAX_BLUR_RADIUS
@@ -82,58 +84,32 @@ class ReactNativeBlurView : BlurViewGroup {
82
84
  setupView()
83
85
  }
84
86
 
85
- /**
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.
89
- */
90
87
  private fun setupView() {
91
88
  super.setBackgroundColor(currentOverlayColor)
92
89
  clipChildren = true
93
90
  clipToOutline = true
94
- blurRounds = 5
91
+ blurRounds = currentBlurRounds
95
92
  super.setDownsampleFactor(6.0F)
96
93
  }
97
94
 
98
- /**
99
- * Called when the view is attached to a window.
100
- * After QmBlurView's onAttachedToWindow sets the decor view as blur root,
101
- * we use reflection to redirect it to the nearest Screen ancestor.
102
- * This scopes the blur capture to just the current screen, preventing
103
- * navigation transition artifacts.
104
- */
105
95
  override fun onAttachedToWindow() {
106
96
  super.onAttachedToWindow()
107
97
 
108
98
  if (isBlurInitialized) return
109
99
 
110
- // Immediately try to swap blur root and initialize.
111
- // We avoid posting a runnable to prevent the 1-second delay artifact.
112
- // If the parent hierarchy is not ready yet (unlikely in onAttachedToWindow),
113
- // we could fall back to post, but for now we prioritize immediate execution.
114
100
  swapBlurRootToScreenAncestor()
115
101
  initializeBlur()
116
102
  }
117
103
 
118
- /**
119
- * Uses reflection to redirect QmBlurView's internal blur capture root
120
- * from the activity decor view to the nearest react-native-screens Screen ancestor.
121
- *
122
- * Reflection path: BlurViewGroup.mBaseBlurViewGroup -> BaseBlurViewGroup.mDecorView
123
- * Also moves the OnPreDrawListener from the old root to the new one.
124
- */
125
104
  private fun swapBlurRootToScreenAncestor() {
126
- // Pinned to QmBlurView 1.1.4 – depends on: mBaseBlurViewGroup, mDecorView, preDrawListener, mDifferentRoot, mForceRedraw
127
105
  val newRoot = findOptimalBlurRoot() ?: return
128
106
 
129
107
  try {
130
- // Step 1: Get BlurViewGroup's private mBaseBlurViewGroup field
131
108
  val blurViewGroupClass = BlurViewGroup::class.java
132
109
  val baseField = blurViewGroupClass.getDeclaredField("mBaseBlurViewGroup")
133
110
  baseField.isAccessible = true
134
111
  val baseBlurViewGroup = baseField.get(this) ?: return
135
112
 
136
- // Step 2: Get BaseBlurViewGroup's private fields
137
113
  val baseClass = BaseBlurViewGroup::class.java
138
114
 
139
115
  val decorViewField = baseClass.getDeclaredField("mDecorView")
@@ -152,25 +128,20 @@ class ReactNativeBlurView : BlurViewGroup {
152
128
  }
153
129
 
154
130
  if (preDrawListener != null && oldDecorView != null) {
155
- // Step 3: Remove listener from old root's ViewTreeObserver
156
131
  try {
157
132
  oldDecorView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
158
133
  } catch (e: Exception) {
159
134
  logDebug("Could not remove old pre-draw listener: ${e.message}")
160
135
  }
161
136
 
162
- // Step 4: Set new root as mDecorView
163
137
  decorViewField.set(baseBlurViewGroup, newRoot)
164
138
 
165
- // Step 5: Add listener to new root's ViewTreeObserver
166
139
  newRoot.viewTreeObserver.addOnPreDrawListener(preDrawListener)
167
140
 
168
- // Step 6: Update mDifferentRoot flag
169
141
  val differentRootField = baseClass.getDeclaredField("mDifferentRoot")
170
142
  differentRootField.isAccessible = true
171
143
  differentRootField.setBoolean(baseBlurViewGroup, newRoot.rootView != this.rootView)
172
144
 
173
- // Step 7: Force a redraw
174
145
  val forceRedrawField = baseClass.getDeclaredField("mForceRedraw")
175
146
  forceRedrawField.isAccessible = true
176
147
  forceRedrawField.setBoolean(baseBlurViewGroup, true)
@@ -184,27 +155,10 @@ class ReactNativeBlurView : BlurViewGroup {
184
155
  }
185
156
  }
186
157
 
187
- /**
188
- * Finds the optimal view to use as blur capture root.
189
- *
190
- * Priority:
191
- * 1. Nearest react-native-screens Screen ancestor — scopes blur to the current
192
- * screen and prevents capturing navigation transition artifacts.
193
- * 2. Nearest ReactRootView ancestor — scopes blur to the React Native root when
194
- * the component is not inside a Screen (e.g. plain View hierarchies). Without
195
- * this fallback, QmBlurView defaults to the activity decor view and blurs the
196
- * entire screen instead of just the component area (issue #89).
197
- * 3. null — returned for modals, which intentionally need to blur content from
198
- * the main activity window (decor view is correct there).
199
- */
200
158
  private fun findOptimalBlurRoot(): ViewGroup? {
201
159
  return findNearestScreenAncestor() ?: findNearestReactRootView()
202
160
  }
203
161
 
204
- /**
205
- * Walks up the view hierarchy looking for react-native-screens Screen components
206
- * using class name detection to avoid hard dependencies on react-native-screens.
207
- */
208
162
  private fun findNearestScreenAncestor(): ViewGroup? {
209
163
  var currentParent = this.parent
210
164
  while (currentParent != null) {
@@ -216,11 +170,6 @@ class ReactNativeBlurView : BlurViewGroup {
216
170
  return null
217
171
  }
218
172
 
219
- /**
220
- * Walks up the view hierarchy looking for the React Native root view.
221
- * Used as a fallback when no Screen ancestor exists, to scope the blur
222
- * capture to the RN root rather than the full activity decor view.
223
- */
224
173
  private fun findNearestReactRootView(): ViewGroup? {
225
174
  var currentParent = this.parent
226
175
  while (currentParent != null) {
@@ -232,11 +181,6 @@ class ReactNativeBlurView : BlurViewGroup {
232
181
  return null
233
182
  }
234
183
 
235
- /**
236
- * Initialize the blur view with current settings.
237
- * Called after the view is attached and the blur root has been swapped.
238
- * Guarded by isBlurInitialized to prevent duplicate setup.
239
- */
240
184
  private fun initializeBlur() {
241
185
  if (isBlurInitialized) return
242
186
 
@@ -252,20 +196,11 @@ class ReactNativeBlurView : BlurViewGroup {
252
196
  }
253
197
  }
254
198
 
255
- /**
256
- * Called when the view is detached from a window.
257
- * Performs cleanup to prevent memory leaks and resets initialization state
258
- * so blur is re-initialized on next attach (e.g. navigation transitions).
259
- */
260
199
  override fun onDetachedFromWindow() {
261
200
  super.onDetachedFromWindow()
262
201
  cleanup()
263
202
  }
264
203
 
265
- /**
266
- * Cleanup method to reset state.
267
- * Helps prevent memory leaks and ensures clean state.
268
- */
269
204
  fun cleanup() {
270
205
  isBlurInitialized = false
271
206
  initRunnable?.let { removeCallbacks(it) }
@@ -273,10 +208,6 @@ class ReactNativeBlurView : BlurViewGroup {
273
208
  logDebug("View cleaned up")
274
209
  }
275
210
 
276
- /**
277
- * Set the blur amount with cross-platform mapping.
278
- * @param amount The blur amount value (0-100), will be mapped to Android's 0-25 radius range
279
- */
280
211
  fun setBlurAmount(amount: Float) {
281
212
  currentBlurRadius = mapBlurAmountToRadius(amount)
282
213
  logDebug("setBlurAmount: $amount -> $currentBlurRadius (mapped from 0-100 to 0-25 range)")
@@ -288,11 +219,24 @@ class ReactNativeBlurView : BlurViewGroup {
288
219
  }
289
220
  }
290
221
 
222
+ fun setRounds(rounds: Int) {
223
+ val blurRounds = rounds.coerceIn(1, 15)
224
+ currentBlurRounds = blurRounds
225
+ logDebug("setRounds: $rounds -> $blurRounds")
226
+
227
+ try {
228
+ super.setBlurRounds(blurRounds)
229
+ } catch (e: Exception) {
230
+ logError("Failed to set blur rounds: ${e.message}", e)
231
+ }
232
+ }
233
+
291
234
  /**
292
235
  * Set the blur type which determines the overlay color.
293
236
  * @param type The blur type string (case-insensitive)
294
237
  */
295
238
  fun setBlurType(type: String) {
239
+ currentBlurType = type
296
240
  val blurType = BlurType.fromString(type)
297
241
  currentOverlayColor = blurType.overlayColor
298
242
  logDebug("setBlurType: $type -> ${blurType.name}")
@@ -307,9 +251,6 @@ class ReactNativeBlurView : BlurViewGroup {
307
251
 
308
252
  /**
309
253
  * Set the glass tint color for liquid glass effect.
310
- * @param color The color string in hex format (e.g., "#FF0000") or null to clear
311
- */
312
- fun setGlassTintColor(color: String?) {
313
254
  color?.let {
314
255
  try {
315
256
  glassTintColor = it.toColorInt()
@@ -326,58 +267,37 @@ class ReactNativeBlurView : BlurViewGroup {
326
267
  }
327
268
  }
328
269
 
329
- /**
330
- * Set the glass opacity for liquid glass effect.
331
- * @param opacity The opacity value (0.0 to 1.0)
332
- */
333
270
  fun setGlassOpacity(opacity: Float) {
334
271
  glassOpacity = opacity.coerceIn(0.0f, 1.0f)
335
272
  logDebug("setGlassOpacity: $opacity")
336
273
  updateGlassEffect()
337
274
  }
338
275
 
339
- /**
340
- * Set the view type (blur or liquidGlass).
341
- * @param type The view type string
342
- */
343
276
  fun setType(type: String) {
344
277
  viewType = type
345
278
  logDebug("setType: $type")
346
279
  updateViewType()
347
280
  }
348
281
 
349
- /**
350
- * Set the view type (blur or liquidGlass).
351
- * @param isInteractive The view type string
352
- */
353
282
  fun setIsInteractive(isInteractive: Boolean) {
354
283
  logDebug("setType: $isInteractive")
355
284
  }
356
285
 
357
- /**
358
- * Set the glass type for liquid glass effect.
359
- * @param type The glass type string
360
- */
361
286
  fun setGlassType(type: String) {
362
287
  glassType = type
363
288
  logDebug("setGlassType: $type")
364
289
  updateGlassEffect()
365
290
  }
366
291
 
367
- /**
368
- * Update the glass effect based on current glass properties.
369
- */
370
292
  private fun updateGlassEffect() {
371
293
  if (viewType == "liquidGlass") {
372
294
  try {
373
- // Apply glass tint with opacity
374
295
  val glassColor = Color.argb(
375
296
  (glassOpacity * 255).toInt(),
376
297
  Color.red(glassTintColor),
377
298
  Color.green(glassTintColor),
378
299
  Color.blue(glassTintColor)
379
300
  )
380
- // Use QmBlurView's setOverlayColor method
381
301
  super.setOverlayColor(glassColor)
382
302
  logDebug("Applied glass effect: color=$glassColor, opacity=$glassOpacity")
383
303
  } catch (e: Exception) {
@@ -386,16 +306,12 @@ class ReactNativeBlurView : BlurViewGroup {
386
306
  }
387
307
  }
388
308
 
389
- /**
390
- * Update the view type and apply appropriate effects.
391
- */
392
309
  private fun updateViewType() {
393
310
  when (viewType) {
394
311
  "liquidGlass" -> {
395
312
  updateGlassEffect()
396
313
  }
397
314
  "blur" -> {
398
- // Restore original blur overlay color
399
315
  try {
400
316
  super.setBackgroundColor(currentOverlayColor)
401
317
  super.setOverlayColor(currentOverlayColor)
@@ -406,40 +322,90 @@ class ReactNativeBlurView : BlurViewGroup {
406
322
  }
407
323
  }
408
324
 
409
- /**
410
- * Set the border radius from React Native StyleSheet.
411
- * React Native provides values in logical pixels (dp), which we convert for the native view.
412
- * @param radius The border radius value in dp
413
- */
414
325
  fun setBorderRadius(radius: Float) {
415
- currentCornerRadius = radius
326
+ borderRadius = radius
416
327
  logDebug("setBorderRadius: $radius dp")
417
328
  updateCornerRadius()
418
329
  }
419
330
 
420
- /**
421
- * Convert pixels to density-independent pixels and update the corner radius.
422
- * QmBlurView's setCornerRadius expects values in pixels, and React Native already
423
- * provides values in dp, so we need to convert from dp to pixels.
424
- */
331
+ fun setBorderTopLeftRadius(radius: Float) {
332
+ borderTopLeftRadius = radius
333
+ logDebug("setBorderTopLeftRadius: $radius dp")
334
+ updateCornerRadius()
335
+ }
336
+
337
+ fun setBorderTopRightRadius(radius: Float) {
338
+ borderTopRightRadius = radius
339
+ logDebug("setBorderTopRightRadius: $radius dp")
340
+ updateCornerRadius()
341
+ }
342
+
343
+ fun setBorderBottomLeftRadius(radius: Float) {
344
+ borderBottomLeftRadius = radius
345
+ logDebug("setBorderBottomLeftRadius: $radius dp")
346
+ updateCornerRadius()
347
+ }
348
+
349
+ fun setBorderBottomRightRadius(radius: Float) {
350
+ borderBottomRightRadius = radius
351
+ logDebug("setBorderBottomRightRadius: $radius dp")
352
+ updateCornerRadius()
353
+ }
354
+
355
+ private fun convertDpToPx(dp: Float): Float {
356
+ val displayMetrics = context.resources.displayMetrics
357
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics)
358
+ }
359
+
425
360
  private fun updateCornerRadius() {
426
361
  try {
427
- // Convert from dp (React Native) to pixels (Android)
428
- val radiusInPixels = TypedValue.applyDimension(
429
- TypedValue.COMPLEX_UNIT_DIP,
430
- currentCornerRadius,
431
- context.resources.displayMetrics
432
- )
433
-
434
- outlineProvider = object : ViewOutlineProvider() {
435
- override fun getOutline(view: View, outline: Outline?) {
436
- outline?.setRoundRect(0, 0, view.width, view.height, radiusInPixels)
362
+ val baseRadius = convertDpToPx(borderRadius)
363
+ val topLeft = if (borderTopLeftRadius > 0) convertDpToPx(borderTopLeftRadius) else baseRadius
364
+ val topRight = if (borderTopRightRadius > 0) convertDpToPx(borderTopRightRadius) else baseRadius
365
+ val bottomLeft = if (borderBottomLeftRadius > 0) convertDpToPx(borderBottomLeftRadius) else baseRadius
366
+ val bottomRight = if (borderBottomRightRadius > 0) convertDpToPx(borderBottomRightRadius) else baseRadius
367
+
368
+ super.setTopLeftCornerRadius(topLeft)
369
+ super.setTopRightCornerRadius(topRight)
370
+ super.setBottomLeftCornerRadius(bottomLeft)
371
+ super.setBottomRightCornerRadius(bottomRight)
372
+ super.setCornerRadius(baseRadius)
373
+
374
+ val isUniform = topLeft == topRight && topRight == bottomLeft && bottomLeft == bottomRight
375
+
376
+ if (isUniform) {
377
+ outlineProvider = object : ViewOutlineProvider() {
378
+ override fun getOutline(view: View, outline: Outline?) {
379
+ outline?.setRoundRect(0, 0, view.width, view.height, baseRadius)
380
+ }
381
+ }
382
+ } else {
383
+ outlineProvider = object : ViewOutlineProvider() {
384
+ override fun getOutline(view: View, outline: Outline?) {
385
+ val path = Path()
386
+ val radii = floatArrayOf(
387
+ topLeft,
388
+ topLeft,
389
+ topRight,
390
+ topRight,
391
+ bottomRight,
392
+ bottomRight,
393
+ bottomLeft,
394
+ bottomLeft
395
+ )
396
+ path.addRoundRect(0f, 0f, view.width.toFloat(), view.height.toFloat(), radii, Path.Direction.CW)
397
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
398
+ outline?.setPath(path)
399
+ } else {
400
+ @Suppress("DEPRECATION")
401
+ outline?.setConvexPath(path)
402
+ }
403
+ }
437
404
  }
438
405
  }
439
- clipToOutline = true
440
406
 
441
- super.setCornerRadius(radiusInPixels)
442
- logDebug("Updated corner radius: ${currentCornerRadius}dp -> ${radiusInPixels}px")
407
+ clipToOutline = true
408
+ logDebug("Updated corner radius: topLeft=$topLeft, topRight=$topRight, bottomLeft=$bottomLeft, bottomRight=$bottomRight (px)")
443
409
  } catch (e: Exception) {
444
410
  logError("Failed to update corner radius: ${e.message}", e)
445
411
  }
@@ -474,8 +440,5 @@ class ReactNativeBlurView : BlurViewGroup {
474
440
  */
475
441
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
476
442
  // No-op: Layout is handled by React Native's UIManager.
477
- // We override this to prevent the superclass (BlurViewGroup/FrameLayout) from
478
- // re-positioning children based on its own logic (e.g. gravity), which would
479
- // conflict with React Native's layout.
480
443
  }
481
444
  }