@mobileai/react-native 0.9.27 → 0.9.29
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/README.md +28 -16
- package/android/build.gradle +17 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayDialogRootViewGroup.kt +243 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +281 -87
- package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +52 -17
- package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +49 -2
- package/bin/generate-map.cjs +45 -6
- package/ios/MobileAIFloatingOverlayComponentView.h +8 -0
- package/ios/MobileAIFloatingOverlayComponentView.mm +12 -41
- package/ios/Podfile +63 -0
- package/ios/Podfile.lock +2290 -0
- package/ios/Podfile.properties.json +4 -0
- package/ios/mobileaireactnative/AppDelegate.swift +69 -0
- package/ios/mobileaireactnative/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/ios/mobileaireactnative/Images.xcassets/Contents.json +6 -0
- package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +21 -0
- package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/SplashScreenLegacy.png +0 -0
- package/ios/mobileaireactnative/Info.plist +55 -0
- package/ios/mobileaireactnative/PrivacyInfo.xcprivacy +48 -0
- package/ios/mobileaireactnative/SplashScreen.storyboard +47 -0
- package/ios/mobileaireactnative/Supporting/Expo.plist +6 -0
- package/ios/mobileaireactnative/mobileaireactnative-Bridging-Header.h +3 -0
- package/ios/mobileaireactnative.xcodeproj/project.pbxproj +547 -0
- package/ios/mobileaireactnative.xcodeproj/xcshareddata/xcschemes/mobileaireactnative.xcscheme +88 -0
- package/ios/mobileaireactnative.xcworkspace/contents.xcworkspacedata +10 -0
- package/lib/module/components/AIAgent.js +501 -191
- package/lib/module/components/AgentChatBar.js +250 -59
- package/lib/module/components/FloatingOverlayWrapper.js +68 -32
- package/lib/module/config/endpoints.js +22 -1
- package/lib/module/core/AgentRuntime.js +110 -8
- package/lib/module/core/FiberTreeWalker.js +211 -10
- package/lib/module/core/OutcomeVerifier.js +149 -0
- package/lib/module/core/systemPrompt.js +96 -25
- package/lib/module/providers/GeminiProvider.js +9 -3
- package/lib/module/services/telemetry/TelemetryService.js +21 -2
- package/lib/module/services/telemetry/TouchAutoCapture.js +235 -38
- package/lib/module/services/telemetry/analyticsLabeling.js +187 -0
- package/lib/module/specs/FloatingOverlayNativeComponent.ts +7 -1
- package/lib/module/support/supportPrompt.js +22 -7
- package/lib/module/support/supportStyle.js +55 -0
- package/lib/module/support/types.js +2 -0
- package/lib/module/tools/typeTool.js +20 -0
- package/lib/module/utils/humanizeScreenName.js +49 -0
- package/lib/typescript/src/components/AIAgent.d.ts +6 -2
- package/lib/typescript/src/components/AgentChatBar.d.ts +15 -1
- package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +22 -10
- package/lib/typescript/src/config/endpoints.d.ts +4 -0
- package/lib/typescript/src/core/AgentRuntime.d.ts +12 -3
- package/lib/typescript/src/core/FiberTreeWalker.d.ts +12 -1
- package/lib/typescript/src/core/OutcomeVerifier.d.ts +46 -0
- package/lib/typescript/src/core/systemPrompt.d.ts +3 -10
- package/lib/typescript/src/core/types.d.ts +63 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +7 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +6 -1
- package/lib/typescript/src/services/telemetry/analyticsLabeling.d.ts +20 -0
- package/lib/typescript/src/services/telemetry/types.d.ts +1 -1
- package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +5 -0
- package/lib/typescript/src/support/index.d.ts +1 -0
- package/lib/typescript/src/support/supportStyle.d.ts +9 -0
- package/lib/typescript/src/support/types.d.ts +3 -0
- package/lib/typescript/src/utils/humanizeScreenName.d.ts +6 -0
- package/package.json +10 -10
- package/src/specs/FloatingOverlayNativeComponent.ts +7 -1
- package/ios/MobileAIPilotIntents.swift +0 -51
|
@@ -5,147 +5,341 @@ import android.app.Dialog
|
|
|
5
5
|
import android.content.Context
|
|
6
6
|
import android.graphics.Color
|
|
7
7
|
import android.graphics.drawable.ColorDrawable
|
|
8
|
+
import android.view.Gravity
|
|
9
|
+
import android.view.MotionEvent
|
|
8
10
|
import android.view.View
|
|
9
11
|
import android.view.ViewGroup
|
|
12
|
+
import android.view.Window
|
|
10
13
|
import android.view.WindowManager
|
|
11
14
|
import android.widget.FrameLayout
|
|
15
|
+
import com.facebook.react.bridge.Arguments
|
|
16
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
17
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
18
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
19
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
12
20
|
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import kotlin.math.roundToInt
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
|
-
* FloatingOverlayView —
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Architecture:
|
|
19
|
-
* TYPE_APPLICATION_PANEL (z=1000) sits above normal app windows (TYPE_APPLICATION, z=2)
|
|
20
|
-
* AND above React Native's <Modal> Dialog windows (also TYPE_APPLICATION), because
|
|
21
|
-
* TYPE_APPLICATION_PANEL explicitly describes a panel that floats above its parent
|
|
22
|
-
* application window. No SYSTEM_ALERT_WINDOW permission required — the panel is
|
|
23
|
-
* scoped to the consuming app's own window hierarchy.
|
|
24
|
-
*
|
|
25
|
-
* Used by both Old Arch (FloatingOverlayViewManager) and New Arch Fabric implementations.
|
|
24
|
+
* FloatingOverlayView — hosts React children inside an Android panel dialog so
|
|
25
|
+
* the agent can float above app content and native modal surfaces without
|
|
26
|
+
* taking ownership of the full React root view.
|
|
26
27
|
*/
|
|
27
28
|
class FloatingOverlayView(context: Context) : ViewGroup(context) {
|
|
28
|
-
|
|
29
|
+
private var showPopupPosted: Boolean = false
|
|
29
30
|
private var overlayDialog: Dialog? = null
|
|
31
|
+
private var windowX: Int = 0
|
|
32
|
+
private var windowY: Int = 0
|
|
33
|
+
private var windowWidth: Int = 0
|
|
34
|
+
private var windowHeight: Int = 0
|
|
35
|
+
|
|
36
|
+
var eventDispatcher: EventDispatcher?
|
|
37
|
+
get() = dialogRootViewGroup.eventDispatcher
|
|
38
|
+
set(value) {
|
|
39
|
+
dialogRootViewGroup.eventDispatcher = value
|
|
40
|
+
}
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
var stateWrapper: StateWrapper?
|
|
43
|
+
get() = dialogRootViewGroup.stateWrapper
|
|
44
|
+
set(value) {
|
|
45
|
+
dialogRootViewGroup.stateWrapper = value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
init {
|
|
49
|
+
isClickable = false
|
|
50
|
+
isFocusable = false
|
|
51
|
+
isFocusableInTouchMode = false
|
|
52
|
+
visibility = View.GONE
|
|
53
|
+
setBackgroundColor(Color.TRANSPARENT)
|
|
54
|
+
}
|
|
33
55
|
|
|
34
|
-
|
|
56
|
+
private val dialogRootViewGroup = FloatingOverlayDialogRootViewGroup(context)
|
|
57
|
+
|
|
58
|
+
private val contentView = FrameLayout(context).apply {
|
|
59
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
60
|
+
setBackgroundColor(Color.TRANSPARENT)
|
|
61
|
+
addView(
|
|
62
|
+
dialogRootViewGroup,
|
|
63
|
+
FrameLayout.LayoutParams(
|
|
64
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
65
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
init {
|
|
71
|
+
dialogRootViewGroup.overlayHost = this
|
|
72
|
+
}
|
|
35
73
|
|
|
36
74
|
override fun onAttachedToWindow() {
|
|
37
75
|
super.onAttachedToWindow()
|
|
38
|
-
|
|
76
|
+
scheduleShowPopup()
|
|
39
77
|
}
|
|
40
78
|
|
|
41
79
|
override fun onDetachedFromWindow() {
|
|
42
|
-
|
|
80
|
+
dismissPopup()
|
|
43
81
|
super.onDetachedFromWindow()
|
|
44
82
|
}
|
|
45
83
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
overlayDialog = Dialog(activity, android.R.style.Theme_Translucent_NoTitleBar).also { dialog ->
|
|
52
|
-
dialog.window?.let { window ->
|
|
53
|
-
// TYPE_APPLICATION_PANEL: sub-window panel above its application window.
|
|
54
|
-
// Requires the parent window's token — no SYSTEM_ALERT_WINDOW needed.
|
|
55
|
-
window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL)
|
|
56
|
-
|
|
57
|
-
// Anchor to the Activity's root decor view token.
|
|
58
|
-
// This makes our panel a child of the main window,
|
|
59
|
-
// floating above it and above any Dialogs (which are peers of the main window).
|
|
60
|
-
val attrs = window.attributes
|
|
61
|
-
attrs.token = activity.window.decorView.windowToken
|
|
62
|
-
window.attributes = attrs
|
|
63
|
-
|
|
64
|
-
window.setLayout(
|
|
65
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
66
|
-
ViewGroup.LayoutParams.MATCH_PARENT
|
|
67
|
-
)
|
|
68
|
-
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
|
69
|
-
|
|
70
|
-
// FLAG_NOT_FOCUSABLE: don't steal keyboard focus from the underlying app.
|
|
71
|
-
// Keyboard inputs (TextInput in the chat bar) still work because
|
|
72
|
-
// the chat bar's own TextInput will request focus explicitly when tapped.
|
|
73
|
-
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
|
|
74
|
-
|
|
75
|
-
// FLAG_NOT_TOUCH_MODAL: allow touches outside our visible UI to reach the
|
|
76
|
-
// underlying app / dialog windows. Without this, any touch on the transparent
|
|
77
|
-
// areas of our overlay would be consumed and swallowed.
|
|
78
|
-
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
|
84
|
+
override fun setId(id: Int) {
|
|
85
|
+
super.setId(id)
|
|
86
|
+
dialogRootViewGroup.id = id
|
|
87
|
+
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
fun setWindowX(value: Int) {
|
|
90
|
+
val pxValue = dpToPx(value)
|
|
91
|
+
if (windowX == pxValue) return
|
|
92
|
+
windowX = pxValue
|
|
93
|
+
updatePopupPosition()
|
|
94
|
+
}
|
|
82
95
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
fun setWindowY(value: Int) {
|
|
97
|
+
val pxValue = dpToPx(value)
|
|
98
|
+
if (windowY == pxValue) return
|
|
99
|
+
windowY = pxValue
|
|
100
|
+
updatePopupPosition()
|
|
87
101
|
}
|
|
88
102
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
} catch (e: Exception) {
|
|
95
|
-
// Activity may already be finishing — safe to ignore crash
|
|
96
|
-
} finally {
|
|
97
|
-
overlayDialog = null
|
|
98
|
-
}
|
|
103
|
+
fun setWindowWidth(value: Int) {
|
|
104
|
+
val normalized = dpToPx(value.coerceAtLeast(1))
|
|
105
|
+
if (windowWidth == normalized) return
|
|
106
|
+
windowWidth = normalized
|
|
107
|
+
updatePopupLayout()
|
|
99
108
|
}
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
fun setWindowHeight(value: Int) {
|
|
111
|
+
val normalized = dpToPx(value.coerceAtLeast(1))
|
|
112
|
+
if (windowHeight == normalized) return
|
|
113
|
+
windowHeight = normalized
|
|
114
|
+
updatePopupLayout()
|
|
115
|
+
}
|
|
105
116
|
|
|
106
117
|
override fun addView(child: View) {
|
|
107
|
-
|
|
118
|
+
dialogRootViewGroup.addView(child)
|
|
108
119
|
}
|
|
109
120
|
|
|
110
121
|
override fun addView(child: View, index: Int) {
|
|
111
|
-
|
|
122
|
+
dialogRootViewGroup.addView(child, index)
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
override fun addView(child: View, params: ViewGroup.LayoutParams) {
|
|
115
|
-
|
|
126
|
+
dialogRootViewGroup.addView(child, params)
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
|
|
119
|
-
|
|
130
|
+
dialogRootViewGroup.addView(child, index, params)
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
override fun removeView(view: View) {
|
|
123
|
-
|
|
134
|
+
dialogRootViewGroup.removeView(view)
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
override fun removeViewAt(index: Int) {
|
|
127
|
-
|
|
138
|
+
dialogRootViewGroup.removeView(getChildAt(index))
|
|
128
139
|
}
|
|
129
140
|
|
|
130
|
-
override fun getChildCount(): Int =
|
|
131
|
-
|
|
132
|
-
override fun getChildAt(index: Int): View? = contentContainer.getChildAt(index)
|
|
141
|
+
override fun getChildCount(): Int = dialogRootViewGroup.childCount
|
|
133
142
|
|
|
134
|
-
|
|
143
|
+
override fun getChildAt(index: Int): View? = dialogRootViewGroup.getChildAt(index)
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
// in the main window and only serves as an anchor.
|
|
138
|
-
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { /* no-op */ }
|
|
145
|
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { /* anchor only */ }
|
|
139
146
|
|
|
140
147
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
141
|
-
|
|
148
|
+
setMeasuredDimension(0, 0)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
override fun dispatchTouchEvent(event: MotionEvent?): Boolean = false
|
|
152
|
+
|
|
153
|
+
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean = false
|
|
154
|
+
|
|
155
|
+
override fun onTouchEvent(event: MotionEvent?): Boolean = false
|
|
156
|
+
|
|
157
|
+
fun onDropInstance() {
|
|
158
|
+
dismissPopup()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
internal fun getWindowXPx(): Int = windowX
|
|
162
|
+
|
|
163
|
+
internal fun getWindowYPx(): Int = windowY
|
|
164
|
+
|
|
165
|
+
internal fun updateWindowPositionFromNative(x: Int, y: Int) {
|
|
166
|
+
val (clampedX, clampedY) = clampPopupPosition(x, y)
|
|
167
|
+
if (windowX == clampedX && windowY == clampedY) return
|
|
168
|
+
windowX = clampedX
|
|
169
|
+
windowY = clampedY
|
|
170
|
+
updatePopupPosition()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
internal fun emitWindowDragEnd() {
|
|
174
|
+
val reactContext = context as? ReactContext ?: return
|
|
175
|
+
val payload = Arguments.createMap().apply {
|
|
176
|
+
putInt("viewId", id)
|
|
177
|
+
putInt("x", pxToDp(windowX))
|
|
178
|
+
putInt("y", pxToDp(windowY))
|
|
179
|
+
putInt("width", pxToDp(resolvePopupWidth()))
|
|
180
|
+
putInt("height", pxToDp(resolvePopupHeight()))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
reactContext
|
|
184
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
185
|
+
.emit(WINDOW_DRAG_END_EVENT, payload)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private fun showPopup() {
|
|
189
|
+
showPopupPosted = false
|
|
190
|
+
|
|
191
|
+
val activity = getActivity()
|
|
192
|
+
if (activity == null) {
|
|
193
|
+
scheduleShowPopup()
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
val decorView = activity.window?.decorView
|
|
198
|
+
if (decorView == null) {
|
|
199
|
+
scheduleShowPopup()
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!decorView.isAttachedToWindow) {
|
|
204
|
+
scheduleShowPopup()
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (overlayDialog == null || overlayDialog?.context !== activity) {
|
|
209
|
+
dismissPopup()
|
|
210
|
+
(contentView.parent as? ViewGroup)?.removeView(contentView)
|
|
211
|
+
|
|
212
|
+
overlayDialog = Dialog(activity).apply {
|
|
213
|
+
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
214
|
+
setCancelable(false)
|
|
215
|
+
setCanceledOnTouchOutside(false)
|
|
216
|
+
setContentView(contentView)
|
|
217
|
+
|
|
218
|
+
window?.apply {
|
|
219
|
+
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
|
220
|
+
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
221
|
+
addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
|
222
|
+
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
|
223
|
+
val params = attributes
|
|
224
|
+
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
|
|
225
|
+
params.token = decorView.applicationWindowToken
|
|
226
|
+
attributes = params
|
|
227
|
+
setGravity(Gravity.TOP or Gravity.START)
|
|
228
|
+
setWindowAnimations(0)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
updatePopupLayout()
|
|
234
|
+
|
|
235
|
+
val dialog = overlayDialog ?: return
|
|
236
|
+
if (!dialog.isShowing && activity.isFinishing.not()) {
|
|
237
|
+
dialog.show()
|
|
238
|
+
updatePopupLayout()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private fun updatePopupLayout() {
|
|
243
|
+
val dialog = overlayDialog
|
|
244
|
+
if (dialog == null) {
|
|
245
|
+
scheduleShowPopup()
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
val width = resolvePopupWidth()
|
|
250
|
+
val height = resolvePopupHeight()
|
|
251
|
+
|
|
252
|
+
contentView.layoutParams = FrameLayout.LayoutParams(width, height)
|
|
253
|
+
dialogRootViewGroup.layoutParams = FrameLayout.LayoutParams(width, height)
|
|
254
|
+
val widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
|
|
255
|
+
val heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
|
256
|
+
contentView.measure(widthSpec, heightSpec)
|
|
257
|
+
contentView.layout(0, 0, width, height)
|
|
258
|
+
dialogRootViewGroup.measure(widthSpec, heightSpec)
|
|
259
|
+
dialogRootViewGroup.layout(0, 0, width, height)
|
|
260
|
+
dialogRootViewGroup.updateState(width, height)
|
|
261
|
+
contentView.requestLayout()
|
|
262
|
+
|
|
263
|
+
updatePopupPosition()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private fun updatePopupPosition() {
|
|
267
|
+
val dialog = overlayDialog
|
|
268
|
+
if (dialog == null) {
|
|
269
|
+
scheduleShowPopup()
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
val width = resolvePopupWidth()
|
|
274
|
+
val height = resolvePopupHeight()
|
|
275
|
+
val (clampedX, clampedY) = clampPopupPosition(windowX, windowY)
|
|
276
|
+
windowX = clampedX
|
|
277
|
+
windowY = clampedY
|
|
278
|
+
|
|
279
|
+
dialog.window?.let { window ->
|
|
280
|
+
window.setLayout(width, height)
|
|
281
|
+
val params = window.attributes
|
|
282
|
+
params.width = width
|
|
283
|
+
params.height = height
|
|
284
|
+
params.x = clampedX
|
|
285
|
+
params.y = clampedY
|
|
286
|
+
params.gravity = Gravity.TOP or Gravity.START
|
|
287
|
+
window.attributes = params
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun dismissPopup() {
|
|
292
|
+
try {
|
|
293
|
+
overlayDialog?.dismiss()
|
|
294
|
+
} catch (_: Exception) {
|
|
295
|
+
// The host activity may already be shutting down.
|
|
296
|
+
} finally {
|
|
297
|
+
showPopupPosted = false
|
|
298
|
+
overlayDialog = null
|
|
299
|
+
(contentView.parent as? ViewGroup)?.removeView(contentView)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private fun scheduleShowPopup() {
|
|
304
|
+
if (showPopupPosted) return
|
|
305
|
+
showPopupPosted = true
|
|
306
|
+
post { showPopup() }
|
|
142
307
|
}
|
|
143
308
|
|
|
144
|
-
|
|
309
|
+
private fun resolvePopupWidth(): Int = windowWidth.coerceAtLeast(1)
|
|
310
|
+
|
|
311
|
+
private fun resolvePopupHeight(): Int = windowHeight.coerceAtLeast(1)
|
|
312
|
+
|
|
313
|
+
private fun clampPopupPosition(x: Int, y: Int): Pair<Int, Int> {
|
|
314
|
+
val screenInset = dpToPx(10)
|
|
315
|
+
val bottomInset = dpToPx(24)
|
|
316
|
+
val decorView = getActivity()?.window?.decorView
|
|
317
|
+
val screenWidth = decorView?.width?.takeIf { it > 0 } ?: resources.displayMetrics.widthPixels
|
|
318
|
+
val screenHeight = decorView?.height?.takeIf { it > 0 } ?: resources.displayMetrics.heightPixels
|
|
319
|
+
val maxX = maxOf(screenInset, screenWidth - resolvePopupWidth() - screenInset)
|
|
320
|
+
val maxY = maxOf(screenInset, screenHeight - resolvePopupHeight() - bottomInset)
|
|
321
|
+
|
|
322
|
+
return Pair(
|
|
323
|
+
x.coerceIn(screenInset, maxX),
|
|
324
|
+
y.coerceIn(screenInset, maxY),
|
|
325
|
+
)
|
|
326
|
+
}
|
|
145
327
|
|
|
146
328
|
private fun getActivity(): Activity? {
|
|
147
|
-
|
|
148
|
-
|
|
329
|
+
return (context as? ThemedReactContext)?.currentActivity
|
|
330
|
+
?: (context as? ReactContext)?.currentActivity
|
|
149
331
|
?: (context as? Activity)
|
|
150
332
|
}
|
|
333
|
+
|
|
334
|
+
private fun dpToPx(value: Int): Int {
|
|
335
|
+
return (value * resources.displayMetrics.density).roundToInt()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private fun pxToDp(value: Int): Int {
|
|
339
|
+
return (value / resources.displayMetrics.density).roundToInt()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
companion object {
|
|
343
|
+
const val WINDOW_DRAG_END_EVENT = "mobileaiFloatingOverlayDragEnd"
|
|
344
|
+
}
|
|
151
345
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
package com.mobileai.overlay
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
-
import com.facebook.react.uimanager.
|
|
4
|
+
import com.facebook.react.uimanager.ReactStylesDiffMap
|
|
5
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
6
7
|
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
7
|
-
import com.facebook.react.uimanager.
|
|
8
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
9
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
10
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
11
|
+
import com.facebook.react.viewmanagers.MobileAIFloatingOverlayManagerDelegate
|
|
12
|
+
import com.facebook.react.viewmanagers.MobileAIFloatingOverlayManagerInterface
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* New Architecture (Fabric) ViewManager for MobileAIFloatingOverlay.
|
|
@@ -12,26 +17,18 @@ import com.facebook.react.uimanager.annotations.ReactProp
|
|
|
12
17
|
* Used when newArchEnabled=true in the consuming app's gradle.properties.
|
|
13
18
|
* Loaded via the newarch/ sourceSet in build.gradle.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
20
|
+
* Fabric mounts this component as a ViewGroup, so the manager must expose the
|
|
21
|
+
* ViewGroup contract rather than SimpleViewManager.
|
|
17
22
|
*/
|
|
18
23
|
class FloatingOverlayViewManager(
|
|
19
24
|
private val reactApplicationContext: ReactApplicationContext
|
|
20
|
-
) :
|
|
25
|
+
) : ViewGroupManager<FloatingOverlayView>(),
|
|
26
|
+
MobileAIFloatingOverlayManagerInterface<FloatingOverlayView> {
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
private val delegate: ViewManagerDelegate<FloatingOverlayView>? = try {
|
|
25
|
-
// Codegen generates this class during the build. It may not exist yet in dev setups.
|
|
26
|
-
Class.forName("com.mobileai.overlay.FloatingOverlayNativeComponentSpecDelegate")
|
|
27
|
-
.getConstructor(SimpleViewManager::class.java)
|
|
28
|
-
.newInstance(this) as? ViewManagerDelegate<FloatingOverlayView>
|
|
29
|
-
} catch (e: Exception) {
|
|
30
|
-
// Codegen hasn't run yet or interop is disabled — proceed without delegate
|
|
31
|
-
null
|
|
32
|
-
}
|
|
28
|
+
private val delegate: ViewManagerDelegate<FloatingOverlayView> =
|
|
29
|
+
MobileAIFloatingOverlayManagerDelegate(this)
|
|
33
30
|
|
|
34
|
-
override fun getDelegate(): ViewManagerDelegate<FloatingOverlayView
|
|
31
|
+
override fun getDelegate(): ViewManagerDelegate<FloatingOverlayView> = delegate
|
|
35
32
|
|
|
36
33
|
override fun getName(): String = NAME
|
|
37
34
|
|
|
@@ -39,6 +36,44 @@ class FloatingOverlayViewManager(
|
|
|
39
36
|
return FloatingOverlayView(context)
|
|
40
37
|
}
|
|
41
38
|
|
|
39
|
+
override fun onDropViewInstance(view: FloatingOverlayView) {
|
|
40
|
+
super.onDropViewInstance(view)
|
|
41
|
+
view.onDropInstance()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: FloatingOverlayView) {
|
|
45
|
+
val dispatcher: EventDispatcher? =
|
|
46
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
|
|
47
|
+
if (dispatcher != null) {
|
|
48
|
+
view.eventDispatcher = dispatcher
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun updateState(
|
|
53
|
+
view: FloatingOverlayView,
|
|
54
|
+
props: ReactStylesDiffMap,
|
|
55
|
+
stateWrapper: StateWrapper,
|
|
56
|
+
): Any? {
|
|
57
|
+
view.stateWrapper = stateWrapper
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override fun setWindowX(view: FloatingOverlayView, value: Int) {
|
|
62
|
+
view.setWindowX(value)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun setWindowY(view: FloatingOverlayView, value: Int) {
|
|
66
|
+
view.setWindowY(value)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun setWindowWidth(view: FloatingOverlayView, value: Int) {
|
|
70
|
+
view.setWindowWidth(value)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override fun setWindowHeight(view: FloatingOverlayView, value: Int) {
|
|
74
|
+
view.setWindowHeight(value)
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
companion object {
|
|
43
78
|
const val NAME = "MobileAIFloatingOverlay"
|
|
44
79
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
package com.mobileai.overlay
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
-
import com.facebook.react.uimanager.
|
|
4
|
+
import com.facebook.react.uimanager.ReactStylesDiffMap
|
|
5
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
5
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
8
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
9
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
10
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Old Architecture (Paper) ViewManager for MobileAIFloatingOverlay.
|
|
@@ -15,7 +20,7 @@ import com.facebook.react.uimanager.ThemedReactContext
|
|
|
15
20
|
*/
|
|
16
21
|
class FloatingOverlayViewManager(
|
|
17
22
|
private val reactApplicationContext: ReactApplicationContext
|
|
18
|
-
) :
|
|
23
|
+
) : ViewGroupManager<FloatingOverlayView>() {
|
|
19
24
|
|
|
20
25
|
override fun getName(): String = NAME
|
|
21
26
|
|
|
@@ -23,6 +28,48 @@ class FloatingOverlayViewManager(
|
|
|
23
28
|
return FloatingOverlayView(context)
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
override fun onDropViewInstance(view: FloatingOverlayView) {
|
|
32
|
+
super.onDropViewInstance(view)
|
|
33
|
+
view.onDropInstance()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: FloatingOverlayView) {
|
|
37
|
+
val dispatcher: EventDispatcher? =
|
|
38
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
|
|
39
|
+
if (dispatcher != null) {
|
|
40
|
+
view.eventDispatcher = dispatcher
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun updateState(
|
|
45
|
+
view: FloatingOverlayView,
|
|
46
|
+
props: ReactStylesDiffMap,
|
|
47
|
+
stateWrapper: StateWrapper,
|
|
48
|
+
): Any? {
|
|
49
|
+
view.stateWrapper = stateWrapper
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@ReactProp(name = "windowX", defaultInt = 0)
|
|
54
|
+
fun setWindowX(view: FloatingOverlayView, value: Int) {
|
|
55
|
+
view.setWindowX(value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@ReactProp(name = "windowY", defaultInt = 0)
|
|
59
|
+
fun setWindowY(view: FloatingOverlayView, value: Int) {
|
|
60
|
+
view.setWindowY(value)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@ReactProp(name = "windowWidth", defaultInt = 0)
|
|
64
|
+
fun setWindowWidth(view: FloatingOverlayView, value: Int) {
|
|
65
|
+
view.setWindowWidth(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@ReactProp(name = "windowHeight", defaultInt = 0)
|
|
69
|
+
fun setWindowHeight(view: FloatingOverlayView, value: Int) {
|
|
70
|
+
view.setWindowHeight(value)
|
|
71
|
+
}
|
|
72
|
+
|
|
26
73
|
companion object {
|
|
27
74
|
const val NAME = "MobileAIFloatingOverlay"
|
|
28
75
|
}
|