@mobileai/react-native 0.9.26 → 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.
- package/README.md +28 -15
- 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 +556 -126
- 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 +407 -148
- package/lib/module/components/AgentChatBar.js +253 -62
- package/lib/module/components/FloatingOverlayWrapper.js +68 -32
- package/lib/module/config/endpoints.js +22 -1
- package/lib/module/core/AgentRuntime.js +192 -24
- package/lib/module/core/FiberTreeWalker.js +410 -34
- package/lib/module/core/OutcomeVerifier.js +149 -0
- package/lib/module/core/systemPrompt.js +126 -44
- package/lib/module/providers/GeminiProvider.js +9 -3
- package/lib/module/services/MobileAIKnowledgeRetriever.js +1 -1
- package/lib/module/services/telemetry/MobileAI.js +1 -1
- package/lib/module/services/telemetry/TelemetryService.js +21 -2
- package/lib/module/services/telemetry/TouchAutoCapture.js +45 -35
- 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/tapTool.js +77 -6
- 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 +17 -1
- 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 +37 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +1 -1
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +7 -1
- 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/tools/tapTool.d.ts +3 -2
- package/lib/typescript/src/utils/humanizeScreenName.d.ts +6 -0
- package/lib/typescript/test-tree.d.ts +2 -0
- package/package.json +5 -2
- package/src/specs/FloatingOverlayNativeComponent.ts +7 -1
- package/ios/MobileAIFloatingOverlayComponentView.mm +0 -73
- package/ios/MobileAIPilotIntents.swift +0 -51
package/README.md
CHANGED
|
@@ -256,7 +256,7 @@ Your app becomes MCP-compatible with one prop. Connect any AI — Antigravity, C
|
|
|
256
256
|
<AIAgent
|
|
257
257
|
showChatBar={false}
|
|
258
258
|
mcpServerUrl="ws://localhost:3101"
|
|
259
|
-
|
|
259
|
+
analyticsKey="mobileai_pub_xxx"
|
|
260
260
|
navRef={navRef}
|
|
261
261
|
>
|
|
262
262
|
<App />
|
|
@@ -391,6 +391,8 @@ npx @mobileai/react-native generate-map
|
|
|
391
391
|
|
|
392
392
|
### 2. Wrap Your App
|
|
393
393
|
|
|
394
|
+
If you use a MobileAI publishable key, the SDK now defaults to the hosted MobileAI text and voice proxies automatically. You only need to pass `proxyUrl` and `voiceProxyUrl` when you want to override them with your own backend.
|
|
395
|
+
|
|
394
396
|
#### React Navigation
|
|
395
397
|
|
|
396
398
|
```tsx
|
|
@@ -403,12 +405,9 @@ export default function App() {
|
|
|
403
405
|
|
|
404
406
|
return (
|
|
405
407
|
<AIAgent
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// ✅ Production: route through your secure backend proxy
|
|
410
|
-
// proxyUrl="https://api.yourdomain.com/ai-proxy"
|
|
411
|
-
// proxyHeaders={{ Authorization: `Bearer ${userToken}` }}
|
|
408
|
+
// Your MobileAI Dashboard ID
|
|
409
|
+
// This now auto-configures the hosted MobileAI text + voice proxies too.
|
|
410
|
+
analyticsKey="mobileai_pub_xxxxxxxx"
|
|
412
411
|
|
|
413
412
|
navRef={navRef}
|
|
414
413
|
screenMap={screenMap} // optional but recommended
|
|
@@ -435,7 +434,8 @@ export default function RootLayout() {
|
|
|
435
434
|
|
|
436
435
|
return (
|
|
437
436
|
<AIAgent
|
|
438
|
-
|
|
437
|
+
// Hosted MobileAI proxies are inferred automatically from analyticsKey
|
|
438
|
+
analyticsKey="mobileai_pub_xxxxxxxx"
|
|
439
439
|
navRef={navRef}
|
|
440
440
|
screenMap={screenMap}
|
|
441
441
|
>
|
|
@@ -462,6 +462,19 @@ The examples above use **Gemini** (default). To use **OpenAI** for text mode, ad
|
|
|
462
462
|
|
|
463
463
|
A floating chat bar appears automatically. Ask the AI to navigate, tap buttons, fill forms, answer questions.
|
|
464
464
|
|
|
465
|
+
### Hosted MobileAI Defaults
|
|
466
|
+
|
|
467
|
+
For the standard MobileAI Cloud setup, this is enough:
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
<AIAgent analyticsKey="mobileai_pub_xxxxxxxx" navRef={navRef} />
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Only pass explicit proxy props when:
|
|
474
|
+
- you want to use your own backend proxy
|
|
475
|
+
- you want a dedicated voice proxy
|
|
476
|
+
- you are self-hosting the MobileAI backend
|
|
477
|
+
|
|
465
478
|
### Knowledge-Only Mode — AI Assistant Without UI Automation
|
|
466
479
|
|
|
467
480
|
Set `enableUIControl={false}` for a lightweight FAQ / support assistant. Single LLM call, ~70% fewer tokens:
|
|
@@ -485,7 +498,7 @@ The agent operates in **copilot mode** by default. It navigates, scrolls, types,
|
|
|
485
498
|
|
|
486
499
|
```tsx
|
|
487
500
|
// Default — copilot mode, zero extra config:
|
|
488
|
-
<AIAgent
|
|
501
|
+
<AIAgent analyticsKey="mobileai_pub_xxx" navRef={navRef}>
|
|
489
502
|
<App />
|
|
490
503
|
</AIAgent>
|
|
491
504
|
```
|
|
@@ -551,7 +564,6 @@ Transform the AI agent into a production-grade support system. The AI resolves i
|
|
|
551
564
|
import { SupportGreeting, buildSupportPrompt, createEscalateTool } from '@mobileai/react-native';
|
|
552
565
|
|
|
553
566
|
<AIAgent
|
|
554
|
-
apiKey="..."
|
|
555
567
|
analyticsKey="mobileai_pub_xxx" // required for MobileAI escalation
|
|
556
568
|
instructions={{
|
|
557
569
|
system: buildSupportPrompt({
|
|
@@ -870,9 +882,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
870
882
|
|------|------|---------|-------------|
|
|
871
883
|
| `apiKey` | `string` | — | API key for your provider (prototyping only — use `proxyUrl` in production). |
|
|
872
884
|
| `provider` | `'gemini' \| 'openai'` | `'gemini'` | LLM provider for text mode. |
|
|
873
|
-
| `proxyUrl` | `string` |
|
|
885
|
+
| `proxyUrl` | `string` | Hosted MobileAI text proxy when `analyticsKey` is set | Backend proxy URL (production). Routes all LLM traffic through your server. |
|
|
874
886
|
| `proxyHeaders` | `Record<string, string>` | — | Auth headers for proxy (e.g., `Authorization: Bearer ${token}`). |
|
|
875
|
-
| `voiceProxyUrl` | `string` |
|
|
887
|
+
| `voiceProxyUrl` | `string` | Hosted MobileAI voice proxy when `analyticsKey` is set; otherwise falls back to `proxyUrl` | Dedicated proxy for Voice Mode WebSockets. |
|
|
876
888
|
| `voiceProxyHeaders` | `Record<string, string>` | — | Auth headers for voice proxy. |
|
|
877
889
|
| `model` | `string` | Provider default | Model name (e.g. `gemini-2.5-flash`, `gpt-4.1-mini`). |
|
|
878
890
|
| `navRef` | `NavigationContainerRef` | — | Navigation ref for auto-navigation. |
|
|
@@ -952,7 +964,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
952
964
|
|
|
953
965
|
| Prop | Type | Default | Description |
|
|
954
966
|
|------|------|---------|-------------|
|
|
955
|
-
| `analyticsKey` | `string` | — | Publishable key (`mobileai_pub_xxx`) — enables auto-analytics. |
|
|
967
|
+
| `analyticsKey` | `string` | — | Publishable key (`mobileai_pub_xxx`) — enables auto-analytics and, by default, the hosted MobileAI text/voice proxies. |
|
|
956
968
|
| `analyticsProxyUrl` | `string` | — | Enterprise: route events through your backend. |
|
|
957
969
|
| `analyticsProxyHeaders` | `Record<string, string>` | — | Auth headers for analytics proxy. |
|
|
958
970
|
|
|
@@ -1064,11 +1076,10 @@ const { send } = useAI({
|
|
|
1064
1076
|
|
|
1065
1077
|
## 📊 Zero-Config Analytics — Auto-Capture Every Tap
|
|
1066
1078
|
|
|
1067
|
-
Just add `analyticsKey` — every button tap, screen navigation, and session is tracked automatically. **Zero code changes** to your app components.
|
|
1079
|
+
Just add `analyticsKey` — every button tap, screen navigation, and session is tracked automatically. It also enables the default hosted MobileAI AI proxies unless you override them. **Zero code changes** to your app components.
|
|
1068
1080
|
|
|
1069
1081
|
```tsx
|
|
1070
1082
|
<AIAgent
|
|
1071
|
-
apiKey="YOUR_KEY"
|
|
1072
1083
|
analyticsKey="mobileai_pub_abc123" // ← enables full auto-capture
|
|
1073
1084
|
navRef={navRef}
|
|
1074
1085
|
>
|
|
@@ -1116,6 +1127,8 @@ MobileAI.identify('user_123', { plan: 'pro' });
|
|
|
1116
1127
|
|
|
1117
1128
|
### Backend Proxy — Keep API Keys Secure
|
|
1118
1129
|
|
|
1130
|
+
Use this only when you want to override the default hosted MobileAI proxy behavior or route traffic through your own backend.
|
|
1131
|
+
|
|
1119
1132
|
```tsx
|
|
1120
1133
|
<AIAgent
|
|
1121
1134
|
proxyUrl="https://myapp.vercel.app/api/gemini"
|
package/android/build.gradle
CHANGED
|
@@ -10,6 +10,15 @@ buildscript {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
def isNewArchitectureEnabled() {
|
|
14
|
+
// Libraries opt into Fabric/TurboModule codegen only when the consuming app enables it.
|
|
15
|
+
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isNewArchitectureEnabled()) {
|
|
19
|
+
apply plugin: "com.facebook.react"
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
apply plugin: "com.android.library"
|
|
14
23
|
apply plugin: "kotlin-android"
|
|
15
24
|
|
|
@@ -59,3 +68,11 @@ repositories {
|
|
|
59
68
|
dependencies {
|
|
60
69
|
implementation "com.facebook.react:react-android"
|
|
61
70
|
}
|
|
71
|
+
|
|
72
|
+
if (isNewArchitectureEnabled()) {
|
|
73
|
+
react {
|
|
74
|
+
jsRootDir = file("../")
|
|
75
|
+
libraryName = "RNMobileAIOverlaySpec"
|
|
76
|
+
codegenJavaPackageName = "com.mobileai.overlay"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
package com.mobileai.overlay
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.view.MotionEvent
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewConfiguration
|
|
8
|
+
import com.facebook.react.bridge.GuardedRunnable
|
|
9
|
+
import com.facebook.react.bridge.WritableMap
|
|
10
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
11
|
+
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
12
|
+
import com.facebook.react.common.build.ReactBuildConfig
|
|
13
|
+
import com.facebook.react.config.ReactFeatureFlags
|
|
14
|
+
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
15
|
+
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
16
|
+
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
|
17
|
+
import com.facebook.react.uimanager.RootView
|
|
18
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
19
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
20
|
+
import com.facebook.react.uimanager.UIManagerModule
|
|
21
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
22
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
23
|
+
import kotlin.math.abs
|
|
24
|
+
import kotlin.math.roundToInt
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Root view rendered inside the Android floating panel dialog.
|
|
28
|
+
*
|
|
29
|
+
* This mirrors the touch and layout behavior of React Native's modal dialog
|
|
30
|
+
* host so JS children remain fully interactive even though they render inside
|
|
31
|
+
* a separate native window.
|
|
32
|
+
*/
|
|
33
|
+
internal class FloatingOverlayDialogRootViewGroup(context: Context) :
|
|
34
|
+
ReactViewGroup(context),
|
|
35
|
+
RootView {
|
|
36
|
+
|
|
37
|
+
var stateWrapper: StateWrapper? = null
|
|
38
|
+
var eventDispatcher: EventDispatcher? = null
|
|
39
|
+
var overlayHost: FloatingOverlayView? = null
|
|
40
|
+
|
|
41
|
+
private var viewWidth: Int = 0
|
|
42
|
+
private var viewHeight: Int = 0
|
|
43
|
+
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
44
|
+
private var jsPointerDispatcher: JSPointerDispatcher? = null
|
|
45
|
+
private val touchSlop: Float = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
|
|
46
|
+
private var dragCandidate: Boolean = false
|
|
47
|
+
private var isDraggingWindow: Boolean = false
|
|
48
|
+
private var dragStartRawX: Float = 0f
|
|
49
|
+
private var dragStartRawY: Float = 0f
|
|
50
|
+
private var dragStartWindowX: Int = 0
|
|
51
|
+
private var dragStartWindowY: Int = 0
|
|
52
|
+
|
|
53
|
+
private val reactContext: ThemedReactContext
|
|
54
|
+
get() = context as ThemedReactContext
|
|
55
|
+
|
|
56
|
+
init {
|
|
57
|
+
if (ReactFeatureFlags.dispatchPointerEvents) {
|
|
58
|
+
jsPointerDispatcher = JSPointerDispatcher(this)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
63
|
+
super.onSizeChanged(w, h, oldw, oldh)
|
|
64
|
+
viewWidth = w
|
|
65
|
+
viewHeight = h
|
|
66
|
+
updateState(viewWidth, viewHeight)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun updateState(width: Int, height: Int) {
|
|
70
|
+
val realWidth = width.toFloat().pxToDp()
|
|
71
|
+
val realHeight = height.toFloat().pxToDp()
|
|
72
|
+
|
|
73
|
+
val wrapper = stateWrapper
|
|
74
|
+
if (wrapper != null) {
|
|
75
|
+
val newStateData: WritableMap = WritableNativeMap()
|
|
76
|
+
newStateData.putDouble("screenWidth", realWidth.toDouble())
|
|
77
|
+
newStateData.putDouble("screenHeight", realHeight.toDouble())
|
|
78
|
+
wrapper.updateState(newStateData)
|
|
79
|
+
} else if (
|
|
80
|
+
!ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE &&
|
|
81
|
+
!reactContext.isBridgeless
|
|
82
|
+
) {
|
|
83
|
+
@Suppress("DEPRECATION")
|
|
84
|
+
reactContext.runOnNativeModulesQueueThread(
|
|
85
|
+
object : GuardedRunnable(reactContext) {
|
|
86
|
+
override fun runGuarded() {
|
|
87
|
+
reactContext.reactApplicationContext
|
|
88
|
+
.getNativeModule(UIManagerModule::class.java)
|
|
89
|
+
?.updateNodeSize(id, viewWidth, viewHeight)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun handleException(t: Throwable) {
|
|
97
|
+
reactContext.reactApplicationContext.handleException(RuntimeException(t))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
101
|
+
when (event.actionMasked) {
|
|
102
|
+
MotionEvent.ACTION_DOWN -> {
|
|
103
|
+
dragCandidate = isInNativeDragRegion(event.x, event.y)
|
|
104
|
+
isDraggingWindow = false
|
|
105
|
+
if (dragCandidate) {
|
|
106
|
+
dragStartRawX = event.rawX
|
|
107
|
+
dragStartRawY = event.rawY
|
|
108
|
+
dragStartWindowX = overlayHost?.getWindowXPx() ?: 0
|
|
109
|
+
dragStartWindowY = overlayHost?.getWindowYPx() ?: 0
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
MotionEvent.ACTION_MOVE -> {
|
|
114
|
+
if (dragCandidate && !isDraggingWindow && hasExceededTouchSlop(event)) {
|
|
115
|
+
isDraggingWindow = true
|
|
116
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
117
|
+
updateNativeWindowDrag(event)
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
MotionEvent.ACTION_UP,
|
|
123
|
+
MotionEvent.ACTION_CANCEL -> resetNativeDrag()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (isDraggingWindow) return true
|
|
127
|
+
|
|
128
|
+
eventDispatcher?.let { dispatcher ->
|
|
129
|
+
jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
130
|
+
jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
|
|
131
|
+
}
|
|
132
|
+
return super.onInterceptTouchEvent(event)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
136
|
+
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
137
|
+
if (isDraggingWindow || dragCandidate) {
|
|
138
|
+
when (event.actionMasked) {
|
|
139
|
+
MotionEvent.ACTION_MOVE -> {
|
|
140
|
+
if (!isDraggingWindow && hasExceededTouchSlop(event)) {
|
|
141
|
+
isDraggingWindow = true
|
|
142
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (isDraggingWindow) {
|
|
146
|
+
updateNativeWindowDrag(event)
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
MotionEvent.ACTION_UP -> {
|
|
152
|
+
if (isDraggingWindow) {
|
|
153
|
+
updateNativeWindowDrag(event)
|
|
154
|
+
overlayHost?.emitWindowDragEnd()
|
|
155
|
+
resetNativeDrag()
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
resetNativeDrag()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
MotionEvent.ACTION_CANCEL -> {
|
|
162
|
+
if (isDraggingWindow) {
|
|
163
|
+
overlayHost?.emitWindowDragEnd()
|
|
164
|
+
resetNativeDrag()
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
resetNativeDrag()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
eventDispatcher?.let { dispatcher ->
|
|
173
|
+
jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
174
|
+
jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
|
|
175
|
+
}
|
|
176
|
+
super.onTouchEvent(event)
|
|
177
|
+
return true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
|
|
181
|
+
eventDispatcher?.let { dispatcher ->
|
|
182
|
+
jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
|
|
183
|
+
}
|
|
184
|
+
return super.onInterceptHoverEvent(event)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
override fun onHoverEvent(event: MotionEvent): Boolean {
|
|
188
|
+
eventDispatcher?.let { dispatcher ->
|
|
189
|
+
jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
|
|
190
|
+
}
|
|
191
|
+
return super.onHoverEvent(event)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
195
|
+
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
|
196
|
+
eventDispatcher?.let { dispatcher ->
|
|
197
|
+
jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher, reactContext)
|
|
198
|
+
jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, dispatcher)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
|
|
203
|
+
eventDispatcher?.let { dispatcher ->
|
|
204
|
+
jsTouchDispatcher.onChildEndedNativeGesture(ev, dispatcher)
|
|
205
|
+
}
|
|
206
|
+
jsPointerDispatcher?.onChildEndedNativeGesture()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
|
210
|
+
// No-op so the root can continue receiving events for JS dispatch.
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private fun hasExceededTouchSlop(event: MotionEvent): Boolean {
|
|
214
|
+
return abs(event.rawX - dragStartRawX) > touchSlop || abs(event.rawY - dragStartRawY) > touchSlop
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private fun updateNativeWindowDrag(event: MotionEvent) {
|
|
218
|
+
val targetX = dragStartWindowX + (event.rawX - dragStartRawX).roundToInt()
|
|
219
|
+
val targetY = dragStartWindowY + (event.rawY - dragStartRawY).roundToInt()
|
|
220
|
+
overlayHost?.updateWindowPositionFromNative(targetX, targetY)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private fun resetNativeDrag() {
|
|
224
|
+
dragCandidate = false
|
|
225
|
+
isDraggingWindow = false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private fun isInNativeDragRegion(x: Float, y: Float): Boolean {
|
|
229
|
+
if (viewWidth <= dpToPx(80) && viewHeight <= dpToPx(80)) {
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
val handleTop = dpToPx(44)
|
|
234
|
+
val handleHalfWidth = dpToPx(72)
|
|
235
|
+
val centerX = viewWidth / 2f
|
|
236
|
+
|
|
237
|
+
return y <= handleTop && x in (centerX - handleHalfWidth)..(centerX + handleHalfWidth)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private fun dpToPx(value: Int): Float {
|
|
241
|
+
return value * resources.displayMetrics.density
|
|
242
|
+
}
|
|
243
|
+
}
|