@mobileai/react-native 0.9.27 → 0.9.28

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 (61) hide show
  1. package/README.md +24 -11
  2. package/android/build.gradle +17 -0
  3. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayDialogRootViewGroup.kt +243 -0
  4. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +281 -87
  5. package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +52 -17
  6. package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +49 -2
  7. package/bin/generate-map.cjs +45 -6
  8. package/ios/Podfile +63 -0
  9. package/ios/Podfile.lock +2290 -0
  10. package/ios/Podfile.properties.json +4 -0
  11. package/ios/mobileaireactnative/AppDelegate.swift +69 -0
  12. package/ios/mobileaireactnative/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  13. package/ios/mobileaireactnative/Images.xcassets/Contents.json +6 -0
  14. package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +21 -0
  15. package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/SplashScreenLegacy.png +0 -0
  16. package/ios/mobileaireactnative/Info.plist +55 -0
  17. package/ios/mobileaireactnative/PrivacyInfo.xcprivacy +48 -0
  18. package/ios/mobileaireactnative/SplashScreen.storyboard +47 -0
  19. package/ios/mobileaireactnative/Supporting/Expo.plist +6 -0
  20. package/ios/mobileaireactnative/mobileaireactnative-Bridging-Header.h +3 -0
  21. package/ios/mobileaireactnative.xcodeproj/project.pbxproj +547 -0
  22. package/ios/mobileaireactnative.xcodeproj/xcshareddata/xcschemes/mobileaireactnative.xcscheme +88 -0
  23. package/ios/mobileaireactnative.xcworkspace/contents.xcworkspacedata +10 -0
  24. package/lib/module/components/AIAgent.js +405 -168
  25. package/lib/module/components/AgentChatBar.js +250 -59
  26. package/lib/module/components/FloatingOverlayWrapper.js +68 -32
  27. package/lib/module/config/endpoints.js +22 -1
  28. package/lib/module/core/AgentRuntime.js +103 -1
  29. package/lib/module/core/FiberTreeWalker.js +98 -0
  30. package/lib/module/core/OutcomeVerifier.js +149 -0
  31. package/lib/module/core/systemPrompt.js +96 -25
  32. package/lib/module/providers/GeminiProvider.js +9 -3
  33. package/lib/module/services/telemetry/TelemetryService.js +21 -2
  34. package/lib/module/services/telemetry/TouchAutoCapture.js +45 -35
  35. package/lib/module/specs/FloatingOverlayNativeComponent.ts +7 -1
  36. package/lib/module/support/supportPrompt.js +22 -7
  37. package/lib/module/support/supportStyle.js +55 -0
  38. package/lib/module/support/types.js +2 -0
  39. package/lib/module/tools/typeTool.js +20 -0
  40. package/lib/module/utils/humanizeScreenName.js +49 -0
  41. package/lib/typescript/src/components/AIAgent.d.ts +6 -2
  42. package/lib/typescript/src/components/AgentChatBar.d.ts +15 -1
  43. package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +22 -10
  44. package/lib/typescript/src/config/endpoints.d.ts +4 -0
  45. package/lib/typescript/src/core/AgentRuntime.d.ts +9 -0
  46. package/lib/typescript/src/core/FiberTreeWalker.d.ts +12 -1
  47. package/lib/typescript/src/core/OutcomeVerifier.d.ts +46 -0
  48. package/lib/typescript/src/core/systemPrompt.d.ts +3 -10
  49. package/lib/typescript/src/core/types.d.ts +35 -0
  50. package/lib/typescript/src/index.d.ts +1 -0
  51. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +7 -1
  52. package/lib/typescript/src/services/telemetry/types.d.ts +1 -1
  53. package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +5 -0
  54. package/lib/typescript/src/support/index.d.ts +1 -0
  55. package/lib/typescript/src/support/supportStyle.d.ts +9 -0
  56. package/lib/typescript/src/support/types.d.ts +3 -0
  57. package/lib/typescript/src/utils/humanizeScreenName.d.ts +6 -0
  58. package/package.json +5 -2
  59. package/src/specs/FloatingOverlayNativeComponent.ts +7 -1
  60. package/ios/MobileAIFloatingOverlayComponentView.mm +0 -73
  61. 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 — A ViewGroup that renders its children in an elevated
16
- * Dialog window using WindowManager.LayoutParams.TYPE_APPLICATION_PANEL.
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
- // All RN children are placed in this container inside the Dialog
32
- private val contentContainer = FrameLayout(context)
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
- // ─── Lifecycle ──────────────────────────────────────────────
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
- showOverlay()
76
+ scheduleShowPopup()
39
77
  }
40
78
 
41
79
  override fun onDetachedFromWindow() {
42
- dismissOverlay()
80
+ dismissPopup()
43
81
  super.onDetachedFromWindow()
44
82
  }
45
83
 
46
- // ─── Overlay management ──────────────────────────────────────
47
-
48
- private fun showOverlay() {
49
- val activity = getActivity() ?: return
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
- window.addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
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
- dialog.setContentView(contentContainer)
84
- dialog.setCanceledOnTouchOutside(false)
85
- dialog.show()
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
- private fun dismissOverlay() {
90
- try {
91
- if (overlayDialog?.isShowing == true) {
92
- overlayDialog?.dismiss()
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
- // ─── Child view forwarding ───────────────────────────────────
102
- // React Native's layout engine sees THIS ViewGroup, but actual rendering
103
- // happens in the Dialog's contentContainer.
104
- // We forward all child management calls to maintain RN's expectations.
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
- contentContainer.addView(child)
118
+ dialogRootViewGroup.addView(child)
108
119
  }
109
120
 
110
121
  override fun addView(child: View, index: Int) {
111
- contentContainer.addView(child, index)
122
+ dialogRootViewGroup.addView(child, index)
112
123
  }
113
124
 
114
125
  override fun addView(child: View, params: ViewGroup.LayoutParams) {
115
- contentContainer.addView(child, params)
126
+ dialogRootViewGroup.addView(child, params)
116
127
  }
117
128
 
118
129
  override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
119
- contentContainer.addView(child, index, params)
130
+ dialogRootViewGroup.addView(child, index, params)
120
131
  }
121
132
 
122
133
  override fun removeView(view: View) {
123
- contentContainer.removeView(view)
134
+ dialogRootViewGroup.removeView(view)
124
135
  }
125
136
 
126
137
  override fun removeViewAt(index: Int) {
127
- contentContainer.removeViewAt(index)
138
+ dialogRootViewGroup.removeView(getChildAt(index))
128
139
  }
129
140
 
130
- override fun getChildCount(): Int = contentContainer.childCount
131
-
132
- override fun getChildAt(index: Int): View? = contentContainer.getChildAt(index)
141
+ override fun getChildCount(): Int = dialogRootViewGroup.childCount
133
142
 
134
- // ─── Layout ──────────────────────────────────────────────────
143
+ override fun getChildAt(index: Int): View? = dialogRootViewGroup.getChildAt(index)
135
144
 
136
- // The Dialog manages sizing (MATCH_PARENT) this View itself is invisible
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
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
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
- // ─── Helpers ─────────────────────────────────────────────────
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
- // ReactContext knows the current Activity
148
- return (context as? ReactContext)?.currentActivity
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.SimpleViewManager
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.annotations.ReactProp
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
- * Codegen generates FloatingOverlayNativeComponentSpec from src/specs/FloatingOverlayNativeComponent.ts.
16
- * This class implements the generated spec interface for Fabric rendering.
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
- ) : SimpleViewManager<FloatingOverlayView>() {
25
+ ) : ViewGroupManager<FloatingOverlayView>(),
26
+ MobileAIFloatingOverlayManagerInterface<FloatingOverlayView> {
21
27
 
22
- // Fabric: delegate is generated by Codegen. Null until Codegen runs.
23
- // The interop layer uses this delegate to bridge JS<->Native in New Arch.
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>? = delegate
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.SimpleViewManager
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
- ) : SimpleViewManager<FloatingOverlayView>() {
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
  }