golia-expo-utils 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,10 +57,15 @@ android {
57
57
  }
58
58
 
59
59
  dependencies {
60
- def composeBom = platform('androidx.compose:compose-bom:2025.12.01')
60
+ def composeBom = platform('androidx.compose:compose-bom:2026.01.00')
61
61
  implementation composeBom
62
62
  implementation 'androidx.compose.ui:ui'
63
63
  implementation 'androidx.compose.ui:ui-tooling-preview'
64
64
  implementation 'androidx.compose.material3:material3'
65
65
  implementation 'io.github.kyant0:backdrop:1.0.0'
66
+ implementation "androidx.appcompat:appcompat:1.7.1"
67
+ implementation "androidx.activity:activity-ktx:1.12.2"
68
+ implementation "androidx.savedstate:savedstate-ktx:1.4.0"
69
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.10.0"
70
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0"
66
71
  }
@@ -2,12 +2,18 @@ package expo.modules.goliaexpoutils.segmentedControl
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.util.Log
6
+ import android.view.ViewGroup
5
7
  import androidx.compose.foundation.layout.fillMaxSize
6
8
  import androidx.compose.runtime.mutableIntStateOf
7
9
  import androidx.compose.runtime.mutableStateOf
8
10
  import androidx.compose.ui.Modifier
9
11
  import androidx.compose.ui.platform.ComposeView
10
12
  import androidx.compose.ui.platform.ViewCompositionStrategy
13
+ import androidx.lifecycle.findViewTreeLifecycleOwner
14
+ import androidx.lifecycle.setViewTreeLifecycleOwner
15
+ import androidx.savedstate.findViewTreeSavedStateRegistryOwner
16
+ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
11
17
  import expo.modules.goliaexpoutils.Config
12
18
  import expo.modules.kotlin.AppContext
13
19
  import expo.modules.kotlin.viewevent.EventDispatcher
@@ -28,42 +34,73 @@ class ReactSegmentedControl(context: Context, appContext: AppContext) :
28
34
  val hapticEnabledState = mutableStateOf(true)
29
35
  val enabledState = mutableStateOf(true)
30
36
 
31
- private var isContentSet = false
32
-
33
37
  private val composeView = ComposeView(context).apply {
34
- layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
38
+ layoutParams = ViewGroup.LayoutParams(
39
+ LayoutParams.MATCH_PARENT,
40
+ LayoutParams.MATCH_PARENT
41
+ )
35
42
  setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
36
43
  }
37
44
 
38
45
  init {
39
- clipChildren = false
40
- clipToPadding = false
41
- addView(composeView)
46
+ // 不要在这里 addView(composeView)
42
47
  }
43
48
 
44
49
  override fun onAttachedToWindow() {
45
50
  super.onAttachedToWindow()
46
51
 
47
- if (!isContentSet) {
48
- composeView.setContent {
49
- SegmentedControl(
50
- items = itemsState.value,
51
- selectedIndex = selectedIndexState.intValue,
52
- onValueChange = { index ->
53
- selectedIndexState.intValue = index
54
- val title = itemsState.value.getOrNull(index)?.title ?: ""
55
- onValueChange(mapOf("value" to title, "index" to index))
56
- },
57
- modifier = Modifier.fillMaxSize(),
58
- activeColor = activeColorState.value,
59
- ctrlBackgroundColor = ctrlBgColorState.value,
60
- textColor = textColorState.value,
61
- autoWidth = autoWidthState.value,
62
- hapticEnabled = hapticEnabledState.value,
63
- enabled = enabledState.value,
64
- glassExpansion = Config.Common.Glass.DEFAULT_EXPANSION,
65
- )
52
+ // 1. 保底注入 Lifecycle
53
+ val activity = appContext.currentActivity
54
+ if (activity != null) {
55
+ try {
56
+ if (composeView.findViewTreeLifecycleOwner() == null && activity is androidx.lifecycle.LifecycleOwner) {
57
+ composeView.setViewTreeLifecycleOwner(activity)
58
+ }
59
+ if (composeView.findViewTreeSavedStateRegistryOwner() == null && activity is androidx.savedstate.SavedStateRegistryOwner) {
60
+ composeView.setViewTreeSavedStateRegistryOwner(activity)
61
+ }
62
+ } catch (e: Exception) {
63
+ Log.e("ReactSegmentedControl", "Lifecycle injection failed", e)
66
64
  }
67
65
  }
66
+
67
+ // 2. 动态挂载
68
+ if (composeView.parent == null) {
69
+ addView(composeView)
70
+ }
71
+
72
+ // 3. 设置内容
73
+ composeView.setContent {
74
+ SegmentedControl(
75
+ items = itemsState.value,
76
+ selectedIndex = selectedIndexState.intValue,
77
+ onValueChange = { index ->
78
+ selectedIndexState.intValue = index
79
+ val title = itemsState.value.getOrNull(index)?.title ?: ""
80
+ if (appContext.reactContext !== null) {
81
+ try {
82
+ onValueChange(mapOf("value" to title, "index" to index))
83
+ } catch (_: Throwable) {
84
+ }
85
+ }
86
+ },
87
+ modifier = Modifier.fillMaxSize(),
88
+ activeColor = activeColorState.value,
89
+ ctrlBackgroundColor = ctrlBgColorState.value,
90
+ textColor = textColorState.value,
91
+ autoWidth = autoWidthState.value,
92
+ hapticEnabled = hapticEnabledState.value,
93
+ enabled = enabledState.value,
94
+ glassExpansion = Config.Common.Glass.DEFAULT_EXPANSION,
95
+ )
96
+ }
97
+ }
98
+
99
+ override fun onDetachedFromWindow() {
100
+ // 4. 动态卸载
101
+ if (composeView.parent == this) {
102
+ removeView(composeView)
103
+ }
104
+ super.onDetachedFromWindow()
68
105
  }
69
- }
106
+ }
@@ -2,6 +2,7 @@ package expo.modules.goliaexpoutils.waterView
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.util.Log
5
6
  import android.view.View
6
7
  import android.view.ViewGroup
7
8
  import android.widget.FrameLayout
@@ -17,6 +18,10 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
17
18
  import androidx.compose.ui.unit.dp
18
19
  import androidx.compose.ui.viewinterop.AndroidView
19
20
  import androidx.core.view.isNotEmpty
21
+ import androidx.lifecycle.findViewTreeLifecycleOwner
22
+ import androidx.lifecycle.setViewTreeLifecycleOwner
23
+ import androidx.savedstate.findViewTreeSavedStateRegistryOwner
24
+ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
20
25
  import com.kyant.backdrop.backdrops.layerBackdrop
21
26
  import com.kyant.backdrop.backdrops.rememberLayerBackdrop
22
27
  import expo.modules.goliaexpoutils.Config
@@ -52,59 +57,117 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
52
57
  private val onPressIn by EventDispatcher()
53
58
  private val onPressOut by EventDispatcher()
54
59
  private val commandChannel = Channel<WaterCommand>(Channel.BUFFERED)
60
+ private val TAG = "ReactWaterView"
61
+
62
+ // 仅仅作为容器,初始化时不添加 ComposeView
55
63
  private val childContainer = FrameLayout(context).apply {
56
64
  layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
57
65
  }
58
66
 
59
- private var isContentSet = false
60
-
67
+ // ComposeView 懒加载,并且只在 Attached 状态下存在于 ViewTree 中
61
68
  private val composeView = ComposeView(context).apply {
62
69
  layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
70
+ // 关键策略:Detach 时立即销毁 Composition,防止内存泄漏和重绘崩溃
63
71
  setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
64
72
  }
65
73
 
66
74
  init {
67
75
  clipChildren = false
68
76
  clipToPadding = false
69
- addView(composeView)
77
+ // ❌ 以前这里有 addView(composeView),现在删掉!
78
+ // 保持 View 树干净,Fabric 测量时不会触发 Compose 逻辑
70
79
  }
71
80
 
72
81
  override fun onAttachedToWindow() {
73
82
  super.onAttachedToWindow()
74
83
 
75
- if (!isContentSet) {
76
- composeView.setContent {
77
- WaterViewEntry(
78
- childContainer = childContainer,
79
- props = props,
80
- commandFlow = commandChannel.receiveAsFlow(),
81
- onMoveDispatch = { x, y -> onMove(mapOf("x" to x, "y" to y)) },
82
- onPressInDispatch = { onPressIn(mapOf()) },
83
- onPressOutDispatch = { onPressOut(mapOf()) }
84
- )
84
+ // 1. 再次尝试获取 Activity (为了保底)
85
+ val activity = appContext.currentActivity
86
+ if (activity != null) {
87
+ // 2. 注入 Lifecycle (如果 ReactActivity 还没给它的话)
88
+ try {
89
+ if (composeView.findViewTreeLifecycleOwner() == null && activity is androidx.lifecycle.LifecycleOwner) {
90
+ composeView.setViewTreeLifecycleOwner(activity)
91
+ }
92
+ if (composeView.findViewTreeSavedStateRegistryOwner() == null && activity is androidx.savedstate.SavedStateRegistryOwner) {
93
+ composeView.setViewTreeSavedStateRegistryOwner(activity)
94
+ }
95
+ } catch (e: Exception) {
96
+ Log.e(TAG, "Lifecycle injection failed", e)
85
97
  }
86
98
  }
99
+
100
+ // 3. ✅ 核心修复:只有在真正 Attach 到窗口时,才把 ComposeView 加进去
101
+ // 这样 Fabric 在后台测量这个 View 时,它只是一个空的 ViewGroup,不会触发 Compose
102
+ if (composeView.parent == null) {
103
+ addView(composeView)
104
+ }
105
+
106
+ // 4. 设置内容 (Compose 会自己判断是否需要重新 Composition)
107
+ composeView.setContent {
108
+ WaterViewEntry(
109
+ childContainer = childContainer,
110
+ props = props,
111
+ commandFlow = commandChannel.receiveAsFlow(),
112
+ onMoveDispatch = { x, y ->
113
+ if (appContext.reactContext !== null) {
114
+ try {
115
+ onMove(mapOf("x" to x, "y" to y))
116
+ } catch (_: Throwable) {
117
+ }
118
+ }
119
+ },
120
+ onPressInDispatch = { onPressIn(mapOf()) },
121
+ onPressOutDispatch = { onPressOut(mapOf()) }
122
+ )
123
+ }
87
124
  }
88
125
 
126
+ override fun onDetachedFromWindow() {
127
+ // 5. ✅ 核心修复:Detach 时立即移除 ComposeView
128
+ // 确保 View 被回收或离屏测量时,ComposeView 不在 ViewTree 里
129
+ if (composeView.parent == this) {
130
+ removeView(composeView)
131
+ }
132
+ super.onDetachedFromWindow()
133
+ }
134
+
135
+ // 下面处理 RN 子 View 的挂载 (Keep standard logic)
89
136
  override fun addView(child: View?, index: Int) {
90
- if (child == composeView) super.addView(child, index)
91
- else childContainer.addView(child)
137
+ if (child == composeView) {
138
+ super.addView(child, index)
139
+ } else {
140
+ // RN 的子 View (比如 Text/Image) 放到 childContainer 里
141
+ // 注意:childContainer 需要被 NativeChildrenContainer (AndroidView) 渲染
142
+ // 这一步逻辑通过 WaterViewEntry -> NativeChildrenContainer 实现
143
+ if (child != null) {
144
+ // 如果 child 已经有父节点,先移除 (Fabric 有时会重用 View)
145
+ (child.parent as? ViewGroup)?.removeView(child)
146
+ childContainer.addView(child)
147
+ }
148
+ }
92
149
  }
93
150
 
94
151
  override fun removeView(view: View?) {
95
- if (view == composeView) super.removeView(view)
96
- else childContainer.removeView(view)
152
+ if (view == composeView) {
153
+ super.removeView(view)
154
+ } else {
155
+ childContainer.removeView(view)
156
+ }
97
157
  }
98
158
 
99
159
  override fun removeViewAt(index: Int) {
160
+ // 这是一个 tricky 的 override,因为 RN 有时候通过 index 删除
161
+ // 我们要确保删的是 childContainer 里的东西,而不是把 composeView 删了
100
162
  if (childContainer.isNotEmpty()) {
101
163
  try {
102
- childContainer.removeViewAt(0)
164
+ childContainer.removeViewAt(0) // 通常 RN 只是顺序删除
103
165
  } catch (_: Exception) {
104
- super.removeViewAt(index)
166
+ // 忽略
105
167
  }
106
168
  } else {
107
- super.removeViewAt(index)
169
+ // 如果 childContainer 空了,那可能是在操作 ComposeView,虽然 RN 不应该直接操作它
170
+ // 保持 safe
108
171
  }
109
172
  }
110
173
 
@@ -113,6 +176,7 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
113
176
  }
114
177
  }
115
178
 
179
+ // Composable 部分保持不变 (WaterViewEntry, NativeChildrenContainer)
116
180
  @Composable
117
181
  private fun WaterViewEntry(
118
182
  childContainer: FrameLayout,
@@ -53,7 +53,7 @@ declare const WaterViewEffect: React.ForwardRefExoticComponent<ViewProps & {
53
53
  onPressIn?: (event: NativeSyntheticEvent<WaterViewEventData>) => void;
54
54
  onPressOut?: (event: NativeSyntheticEvent<WaterViewEventData>) => void;
55
55
  } & React.RefAttributes<WaterViewRef>>;
56
- export declare const WaterView: React.FC<WaterViewProps> & {
56
+ export declare const ReactWaterView: React.FC<WaterViewProps> & {
57
57
  Effect: typeof WaterViewEffect;
58
58
  };
59
59
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"ReactWaterView.d.ts","sourceRoot":"","sources":["../src/ReactWaterView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAmD,MAAM,OAAO,CAAA;AACvE,OAAO,EAAE,oBAAoB,EAAgC,SAAS,EAAE,MAAM,cAAc,CAAA;AAM5F,MAAM,MAAM,kBAAkB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACzD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE5C,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrE,CAAA;AAGD,KAAK,WAAW,GAAG,SAAS,GAAG;IAC7B,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;IAClE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;IACrE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;CACvE,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAA;AAE9C,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAAA;AAOvE,QAAA,MAAM,eAAe;WA1BZ;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;cAC9B,MAAM;uBACG,OAAO;mBACX,YAAY;aAClB,OAAO;aACP,OAAO;WACT,MAAM;WACN,MAAM;WACN,MAAM;WACN,MAAM;qBACI,MAAM;qBACN,MAAM;aACd,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI;gBACtD,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI;iBACxD,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI;sCA6BvE,CAAA;AAqDD,eAAO,MAAM,SAAS,EAAoB,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC,GAAG;IACnE,MAAM,EAAE,OAAO,eAAe,CAAA;CAC/B,CAAA"}
1
+ {"version":3,"file":"ReactWaterView.d.ts","sourceRoot":"","sources":["../src/ReactWaterView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAmD,MAAM,OAAO,CAAA;AACvE,OAAO,EAAE,oBAAoB,EAAgC,SAAS,EAAE,MAAM,cAAc,CAAA;AAM5F,MAAM,MAAM,kBAAkB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACzD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE5C,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrE,CAAA;AAGD,KAAK,WAAW,GAAG,SAAS,GAAG;IAC7B,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;IAClE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;IACrE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;CACvE,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAA;AAE9C,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAAA;AAOvE,QAAA,MAAM,eAAe;WA1BZ;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;cAC9B,MAAM;uBACG,OAAO;mBACX,YAAY;aAClB,OAAO;aACP,OAAO;WACT,MAAM;WACN,MAAM;WACN,MAAM;WACN,MAAM;qBACI,MAAM;qBACN,MAAM;aACd,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI;gBACtD,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI;iBACxD,CAAC,KAAK,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,KAAK,IAAI;sCA6BvE,CAAA;AAqDD,eAAO,MAAM,cAAc,EAAoB,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC,GAAG;IACxE,MAAM,EAAE,OAAO,eAAe,CAAA;CAC/B,CAAA"}
@@ -47,5 +47,5 @@ const WaterViewRoot = ({ children, style, ...props }) => {
47
47
  WaterViewRoot.displayName = 'WaterView';
48
48
  WaterViewRoot.Effect = WaterViewEffect;
49
49
  // 导出
50
- export const WaterView = WaterViewRoot;
50
+ export const ReactWaterView = WaterViewRoot;
51
51
  //# sourceMappingURL=ReactWaterView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactWaterView.js","sourceRoot":"","sources":["../src/ReactWaterView.tsx"],"names":[],"mappings":"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","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core'\nimport React, { isValidElement, cloneElement, forwardRef } from 'react'\nimport { NativeSyntheticEvent, Platform, processColor, View, ViewProps } from 'react-native'\n\n// =============================================================================\n// 1. 类型定义\n// =============================================================================\n\nexport type WaterViewEventData = { x: number; y: number }\nexport type RestrictMode = 'edge' | 'center'\n\nexport type WaterViewRef = {\n moveTo: (x: number, y: number, duration?: number) => Promise<void>\n moveBy: (dx: number, dy: number, duration?: number) => Promise<void>\n}\n\n// Native Props\ntype NativeProps = ViewProps & {\n size?: { width: number; height: number }\n bgColor?: string\n shouldSpringBack?: boolean\n restrictMode?: RestrictMode\n allowX?: boolean\n allowY?: boolean\n minX?: number\n maxX?: number\n minY?: number\n maxY?: number\n initialOffsetX?: number\n initialOffsetY?: number\n onMove?: (event: NativeSyntheticEvent<WaterViewEventData>) => void\n onPressIn?: (event: NativeSyntheticEvent<WaterViewEventData>) => void\n onPressOut?: (event: NativeSyntheticEvent<WaterViewEventData>) => void\n}\n\nexport type WaterViewEffectProps = NativeProps\n\nexport type WaterViewProps = ViewProps & { children?: React.ReactNode }\n\nconst NativeComponent = requireNativeViewManager('WaterViewModule', 'ReactWaterView')\n\n// =============================================================================\n// 2. 子组件: WaterView.Effect\n// =============================================================================\nconst WaterViewEffect = forwardRef<WaterViewRef, WaterViewEffectProps>(\n ({ bgColor, children, ...props }, ref) => {\n const nativeRef = React.useRef<any>(null)\n\n React.useImperativeHandle(ref, () => ({\n moveBy: async (dx, dy, duration) => nativeRef.current?.moveBy(dx, dy, duration),\n moveTo: async (x, y, duration) => nativeRef.current?.moveTo(x, y, duration),\n }))\n\n const nativeProps = { ...props, bgColor: processColor(bgColor), collapsable: false }\n\n return (\n <NativeComponent ref={nativeRef} {...nativeProps} collapsable={false}>\n {children}\n </NativeComponent>\n )\n }\n)\nWaterViewEffect.displayName = 'WaterView.Effect'\n\n// =============================================================================\n// 3. 父组件: WaterView (容器)\n// =============================================================================\n\nconst WaterViewRoot = ({ children, style, ...props }: WaterViewProps) => {\n // [修复]: 使用 React.Children.toArray + find,而不是 forEach 回调\n // 这样 TypeScript 可以正确推断 effectNode 不为 null/never\n const childrenArray = React.Children.toArray(children)\n\n const effectNode = childrenArray.find(\n (child): child is React.ReactElement<WaterViewEffectProps> =>\n isValidElement(child) && child.type === WaterViewEffect\n )\n\n const contentChildren = childrenArray.filter((child) => child !== effectNode)\n\n if (!effectNode) {\n console.warn('<WaterView> requires a <WaterView.Effect> child to work correctly.')\n return (\n <View style={style} {...props} collapsable={false}>\n {contentChildren}\n </View>\n )\n }\n\n // Android 渲染逻辑\n if (Platform.OS === 'android') {\n return cloneElement(\n effectNode,\n { ...effectNode.props },\n <View style={style} {...props} collapsable={false}>\n {contentChildren}\n </View>\n )\n }\n\n // iOS 渲染逻辑\n return (\n <View style={style} {...props} collapsable={false}>\n {contentChildren}\n {cloneElement(effectNode, { style: effectNode.props.style })}\n </View>\n )\n}\n\n// 挂载静态属性\n;(WaterViewRoot as any).displayName = 'WaterView'\n;(WaterViewRoot as any).Effect = WaterViewEffect\n\n// 导出\nexport const WaterView = WaterViewRoot as React.FC<WaterViewProps> & {\n Effect: typeof WaterViewEffect\n}\n"]}
1
+ {"version":3,"file":"ReactWaterView.js","sourceRoot":"","sources":["../src/ReactWaterView.tsx"],"names":[],"mappings":"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","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core'\nimport React, { isValidElement, cloneElement, forwardRef } from 'react'\nimport { NativeSyntheticEvent, Platform, processColor, View, ViewProps } from 'react-native'\n\n// =============================================================================\n// 1. 类型定义\n// =============================================================================\n\nexport type WaterViewEventData = { x: number; y: number }\nexport type RestrictMode = 'edge' | 'center'\n\nexport type WaterViewRef = {\n moveTo: (x: number, y: number, duration?: number) => Promise<void>\n moveBy: (dx: number, dy: number, duration?: number) => Promise<void>\n}\n\n// Native Props\ntype NativeProps = ViewProps & {\n size?: { width: number; height: number }\n bgColor?: string\n shouldSpringBack?: boolean\n restrictMode?: RestrictMode\n allowX?: boolean\n allowY?: boolean\n minX?: number\n maxX?: number\n minY?: number\n maxY?: number\n initialOffsetX?: number\n initialOffsetY?: number\n onMove?: (event: NativeSyntheticEvent<WaterViewEventData>) => void\n onPressIn?: (event: NativeSyntheticEvent<WaterViewEventData>) => void\n onPressOut?: (event: NativeSyntheticEvent<WaterViewEventData>) => void\n}\n\nexport type WaterViewEffectProps = NativeProps\n\nexport type WaterViewProps = ViewProps & { children?: React.ReactNode }\n\nconst NativeComponent = requireNativeViewManager('WaterViewModule', 'ReactWaterView')\n\n// =============================================================================\n// 2. 子组件: WaterView.Effect\n// =============================================================================\nconst WaterViewEffect = forwardRef<WaterViewRef, WaterViewEffectProps>(\n ({ bgColor, children, ...props }, ref) => {\n const nativeRef = React.useRef<any>(null)\n\n React.useImperativeHandle(ref, () => ({\n moveBy: async (dx, dy, duration) => nativeRef.current?.moveBy(dx, dy, duration),\n moveTo: async (x, y, duration) => nativeRef.current?.moveTo(x, y, duration),\n }))\n\n const nativeProps = { ...props, bgColor: processColor(bgColor), collapsable: false }\n\n return (\n <NativeComponent ref={nativeRef} {...nativeProps} collapsable={false}>\n {children}\n </NativeComponent>\n )\n }\n)\nWaterViewEffect.displayName = 'WaterView.Effect'\n\n// =============================================================================\n// 3. 父组件: WaterView (容器)\n// =============================================================================\n\nconst WaterViewRoot = ({ children, style, ...props }: WaterViewProps) => {\n // [修复]: 使用 React.Children.toArray + find,而不是 forEach 回调\n // 这样 TypeScript 可以正确推断 effectNode 不为 null/never\n const childrenArray = React.Children.toArray(children)\n\n const effectNode = childrenArray.find(\n (child): child is React.ReactElement<WaterViewEffectProps> =>\n isValidElement(child) && child.type === WaterViewEffect\n )\n\n const contentChildren = childrenArray.filter((child) => child !== effectNode)\n\n if (!effectNode) {\n console.warn('<WaterView> requires a <WaterView.Effect> child to work correctly.')\n return (\n <View style={style} {...props} collapsable={false}>\n {contentChildren}\n </View>\n )\n }\n\n // Android 渲染逻辑\n if (Platform.OS === 'android') {\n return cloneElement(\n effectNode,\n { ...effectNode.props },\n <View style={style} {...props} collapsable={false}>\n {contentChildren}\n </View>\n )\n }\n\n // iOS 渲染逻辑\n return (\n <View style={style} {...props} collapsable={false}>\n {contentChildren}\n {cloneElement(effectNode, { style: effectNode.props.style })}\n </View>\n )\n}\n\n// 挂载静态属性\n;(WaterViewRoot as any).displayName = 'WaterView'\n;(WaterViewRoot as any).Effect = WaterViewEffect\n\n// 导出\nexport const ReactWaterView = WaterViewRoot as React.FC<WaterViewProps> & {\n Effect: typeof WaterViewEffect\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "golia-expo-utils",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "expo utils provided by golia.jp",
5
5
  "main": "build/index.js",
6
6
  "homepage": "https://expo.golia.jp/golia-expo-utils",
@@ -19,23 +19,32 @@
19
19
  },
20
20
  "keywords": ["react-native", "expo", "golia-expo-utils", "GoliaExpoUtils"],
21
21
  "repository": "https://github.com/goliajp/golia-expo-utils",
22
- "bugs": { "url": "https://github.com/goliajp/golia-expo-utils/issues" },
23
- "author": "doracawl <lihao@golia.jp> (doracawl)",
24
- "license": "MIT",
25
- "dependencies": { "fs-extra": "^11.3.3", "svg2vectordrawable": "^2.9.1" },
26
- "app.plugin": "./plugin/build/index.js",
27
- "devDependencies": {
28
- "@types/fs-extra": "^11.0.4",
29
- "@types/node": "^25.0.3",
30
- "@types/react": "~19.1.0",
31
- "eslint-config-expo": "^10.0.0",
32
- "eslint-plugin-perfectionist": "^5.3.1",
33
- "eslint-plugin-react": "^7.37.5",
34
- "eslint-plugin-unused-imports": "^4.3.0",
35
- "expo": "^54.0.31",
36
- "expo-module-scripts": "^5.0.8",
37
- "prettier": "^3.7.4",
38
- "react-native": "0.81.5"
39
- },
40
- "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" }
22
+ "bugs": {
23
+ "url": "https://github.com/goliajp/golia-expo-utils/issues"
24
+ },
25
+ "author": "doracawl <lihao@golia.jp> (doracawl)",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "fs-extra": "^11.3.3",
29
+ "svg2vectordrawable": "^2.9.1"
30
+ },
31
+ "app.plugin": "./plugin/build/index.js",
32
+ "devDependencies": {
33
+ "@types/fs-extra": "^11.0.4",
34
+ "@types/node": "^25.0.3",
35
+ "@types/react": "~19.1.0",
36
+ "eslint-config-expo": "^10.0.0",
37
+ "eslint-plugin-perfectionist": "^5.3.1",
38
+ "eslint-plugin-react": "^7.37.5",
39
+ "eslint-plugin-unused-imports": "^4.3.0",
40
+ "expo": "^54.0.31",
41
+ "expo-module-scripts": "^5.0.8",
42
+ "prettier": "^3.7.4",
43
+ "react-native": "0.81.5"
44
+ },
45
+ "peerDependencies": {
46
+ "expo": "*",
47
+ "react": "*",
48
+ "react-native": "*"
49
+ }
41
50
  }
@@ -112,6 +112,6 @@ const WaterViewRoot = ({ children, style, ...props }: WaterViewProps) => {
112
112
  ;(WaterViewRoot as any).Effect = WaterViewEffect
113
113
 
114
114
  // 导出
115
- export const WaterView = WaterViewRoot as React.FC<WaterViewProps> & {
115
+ export const ReactWaterView = WaterViewRoot as React.FC<WaterViewProps> & {
116
116
  Effect: typeof WaterViewEffect
117
117
  }