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.
- package/android/src/main/java/expo/modules/goliaexpoutils/ViewExt.kt +31 -0
- package/android/src/main/java/expo/modules/goliaexpoutils/segmentedControl/ReactSegmentedControl.kt +10 -34
- package/android/src/main/java/expo/modules/goliaexpoutils/waterView/ReactWaterView.kt +27 -37
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/android/src/main/java/expo/modules/goliaexpoutils/segmentedControl/ReactSegmentedControl.kt
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ->
|
|
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)
|
|
108
|
-
|
|
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)
|
|
113
|
-
|
|
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,
|