golia-expo-utils 1.0.2 → 1.0.4

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.
@@ -0,0 +1,31 @@
1
+ package expo.modules.goliaexpoutils
2
+
3
+ import android.content.Context
4
+ import android.content.ContextWrapper
5
+ import android.view.View
6
+ import androidx.appcompat.app.AppCompatActivity
7
+ import androidx.lifecycle.findViewTreeLifecycleOwner
8
+ import androidx.lifecycle.setViewTreeLifecycleOwner
9
+ import androidx.savedstate.findViewTreeSavedStateRegistryOwner
10
+ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
11
+
12
+ fun View.enforceLifecycle(context: Context) {
13
+ val activity = context.findActivity() ?: return
14
+ if (this.findViewTreeLifecycleOwner() == null) {
15
+ this.setViewTreeLifecycleOwner(activity)
16
+ }
17
+ if (this.findViewTreeSavedStateRegistryOwner() == null) {
18
+ this.setViewTreeSavedStateRegistryOwner(activity)
19
+ }
20
+ }
21
+
22
+ private fun Context.findActivity(): AppCompatActivity? {
23
+ var ctx = this
24
+ while (ctx is ContextWrapper) {
25
+ if (ctx is AppCompatActivity) {
26
+ return ctx
27
+ }
28
+ ctx = ctx.baseContext
29
+ }
30
+ return null
31
+ }
@@ -2,19 +2,15 @@ package expo.modules.goliaexpoutils.segmentedControl
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
- import android.content.ContextWrapper
6
- import androidx.appcompat.app.AppCompatActivity
5
+ import android.view.ViewGroup
7
6
  import androidx.compose.foundation.layout.fillMaxSize
8
7
  import androidx.compose.runtime.mutableIntStateOf
9
8
  import androidx.compose.runtime.mutableStateOf
10
9
  import androidx.compose.ui.Modifier
11
10
  import androidx.compose.ui.platform.ComposeView
12
11
  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
17
12
  import expo.modules.goliaexpoutils.Config
13
+ import expo.modules.goliaexpoutils.enforceLifecycle
18
14
  import expo.modules.kotlin.AppContext
19
15
  import expo.modules.kotlin.viewevent.EventDispatcher
20
16
  import expo.modules.kotlin.views.ExpoView
@@ -37,30 +33,18 @@ class ReactSegmentedControl(context: Context, appContext: AppContext) :
37
33
  private var isContentSet = false
38
34
 
39
35
  private val composeView = ComposeView(context).apply {
40
- layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
41
- // 确保 View 卸载时清理 Compose 资源
36
+ layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
42
37
  setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
43
38
  }
44
39
 
45
40
  init {
46
- clipChildren = false
47
- clipToPadding = false
48
41
  addView(composeView)
49
42
  }
50
43
 
51
44
  override fun onAttachedToWindow() {
52
45
  super.onAttachedToWindow()
53
46
 
54
- // ✅ 核心修复:查找 Activity 并强行注入 Lifecycle
55
- val activity = context.lookupActivity()
56
- if (activity != null) {
57
- if (composeView.findViewTreeLifecycleOwner() == null) {
58
- composeView.setViewTreeLifecycleOwner(activity)
59
- }
60
- if (composeView.findViewTreeSavedStateRegistryOwner() == null) {
61
- composeView.setViewTreeSavedStateRegistryOwner(activity)
62
- }
63
- }
47
+ composeView.enforceLifecycle(context)
64
48
 
65
49
  if (!isContentSet) {
66
50
  composeView.setContent {
@@ -70,7 +54,12 @@ class ReactSegmentedControl(context: Context, appContext: AppContext) :
70
54
  onValueChange = { index ->
71
55
  selectedIndexState.intValue = index
72
56
  val title = itemsState.value.getOrNull(index)?.title ?: ""
73
- onValueChange(mapOf("value" to title, "index" to index))
57
+ if (appContext.reactContext != null) {
58
+ try {
59
+ onValueChange(mapOf("value" to title, "index" to index))
60
+ } catch (_: Throwable) {
61
+ }
62
+ }
74
63
  },
75
64
  modifier = Modifier.fillMaxSize(),
76
65
  activeColor = activeColorState.value,
@@ -85,17 +74,4 @@ class ReactSegmentedControl(context: Context, appContext: AppContext) :
85
74
  isContentSet = true
86
75
  }
87
76
  }
88
- }
89
-
90
- // ✅ 辅助扩展函数:从 ContextWrapper 中剥离出 Activity
91
- // (虽然代码重复了,但这样解耦,避免跨包依赖问题)
92
- private fun Context.lookupActivity(): AppCompatActivity? {
93
- var ctx = this
94
- while (ctx is ContextWrapper) {
95
- if (ctx is AppCompatActivity) {
96
- return ctx
97
- }
98
- ctx = ctx.baseContext
99
- }
100
- return null
101
77
  }
@@ -2,11 +2,9 @@ package expo.modules.goliaexpoutils.waterView
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
- import android.content.ContextWrapper
6
5
  import android.view.View
7
6
  import android.view.ViewGroup
8
7
  import android.widget.FrameLayout
9
- import androidx.appcompat.app.AppCompatActivity
10
8
  import androidx.compose.foundation.layout.Box
11
9
  import androidx.compose.foundation.layout.fillMaxSize
12
10
  import androidx.compose.runtime.Composable
@@ -19,13 +17,10 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
19
17
  import androidx.compose.ui.unit.dp
20
18
  import androidx.compose.ui.viewinterop.AndroidView
21
19
  import androidx.core.view.isNotEmpty
22
- import androidx.lifecycle.findViewTreeLifecycleOwner
23
- import androidx.lifecycle.setViewTreeLifecycleOwner
24
- import androidx.savedstate.findViewTreeSavedStateRegistryOwner
25
- import androidx.savedstate.setViewTreeSavedStateRegistryOwner
26
20
  import com.kyant.backdrop.backdrops.layerBackdrop
27
21
  import com.kyant.backdrop.backdrops.rememberLayerBackdrop
28
22
  import expo.modules.goliaexpoutils.Config
23
+ import expo.modules.goliaexpoutils.enforceLifecycle
29
24
  import expo.modules.kotlin.AppContext
30
25
  import expo.modules.kotlin.viewevent.EventDispatcher
31
26
  import expo.modules.kotlin.views.ExpoView
@@ -58,12 +53,13 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
58
53
  private val onPressIn by EventDispatcher()
59
54
  private val onPressOut by EventDispatcher()
60
55
  private val commandChannel = Channel<WaterCommand>(Channel.BUFFERED)
56
+
57
+ private var isContentSet = false
58
+
61
59
  private val childContainer = FrameLayout(context).apply {
62
60
  layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
63
61
  }
64
62
 
65
- private var isContentSet = false
66
-
67
63
  private val composeView = ComposeView(context).apply {
68
64
  layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
69
65
  setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
@@ -72,21 +68,14 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
72
68
  init {
73
69
  clipChildren = false
74
70
  clipToPadding = false
71
+
75
72
  addView(composeView)
76
73
  }
77
74
 
78
75
  override fun onAttachedToWindow() {
79
76
  super.onAttachedToWindow()
80
77
 
81
- val activity = context.lookupActivity()
82
- if (activity != null) {
83
- if (composeView.findViewTreeLifecycleOwner() == null) {
84
- composeView.setViewTreeLifecycleOwner(activity)
85
- }
86
- if (composeView.findViewTreeSavedStateRegistryOwner() == null) {
87
- composeView.setViewTreeSavedStateRegistryOwner(activity)
88
- }
89
- }
78
+ composeView.enforceLifecycle(context)
90
79
 
91
80
  if (!isContentSet) {
92
81
  composeView.setContent {
@@ -94,7 +83,14 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
94
83
  childContainer = childContainer,
95
84
  props = props,
96
85
  commandFlow = commandChannel.receiveAsFlow(),
97
- onMoveDispatch = { x, y -> onMove(mapOf("x" to x, "y" to y)) },
86
+ onMoveDispatch = { x, y ->
87
+ if (appContext.reactContext != null) {
88
+ try {
89
+ onMove(mapOf("x" to x, "y" to y))
90
+ } catch (_: Throwable) {
91
+ }
92
+ }
93
+ },
98
94
  onPressInDispatch = { onPressIn(mapOf()) },
99
95
  onPressOutDispatch = { onPressOut(mapOf()) }
100
96
  )
@@ -104,13 +100,22 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
104
100
  }
105
101
 
106
102
  override fun addView(child: View?, index: Int) {
107
- if (child == composeView) super.addView(child, index)
108
- else childContainer.addView(child)
103
+ if (child == composeView) {
104
+ super.addView(child, index)
105
+ } else {
106
+ if (child != null) {
107
+ (child.parent as? ViewGroup)?.removeView(child)
108
+ childContainer.addView(child)
109
+ }
110
+ }
109
111
  }
110
112
 
111
113
  override fun removeView(view: View?) {
112
- if (view == composeView) super.removeView(view)
113
- else childContainer.removeView(view)
114
+ if (view == composeView) {
115
+ super.removeView(view)
116
+ } else {
117
+ childContainer.removeView(view)
118
+ }
114
119
  }
115
120
 
116
121
  override fun removeViewAt(index: Int) {
@@ -118,10 +123,7 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
118
123
  try {
119
124
  childContainer.removeViewAt(0)
120
125
  } catch (_: Exception) {
121
- super.removeViewAt(index)
122
126
  }
123
- } else {
124
- super.removeViewAt(index)
125
127
  }
126
128
  }
127
129
 
@@ -130,18 +132,6 @@ class ReactWaterView(context: Context, appContext: AppContext) : ExpoView(contex
130
132
  }
131
133
  }
132
134
 
133
- // ✅ 辅助扩展函数:从 ContextWrapper 中剥离出 Activity
134
- private fun Context.lookupActivity(): AppCompatActivity? {
135
- var ctx = this
136
- while (ctx is ContextWrapper) {
137
- if (ctx is AppCompatActivity) {
138
- return ctx
139
- }
140
- ctx = ctx.baseContext
141
- }
142
- return null
143
- }
144
-
145
135
  @Composable
146
136
  private fun WaterViewEntry(
147
137
  childContainer: FrameLayout,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "golia-expo-utils",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",