@mobileai/react-native 0.9.17 → 0.9.19
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/LICENSE +28 -20
- package/MobileAIFloatingOverlay.podspec +25 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +151 -0
- package/android/src/main/java/com/mobileai/overlay/MobileAIOverlayPackage.kt +23 -0
- package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +45 -0
- package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +29 -0
- package/ios/MobileAIFloatingOverlayComponentView.mm +73 -0
- package/lib/module/components/AIAgent.js +902 -136
- package/lib/module/components/AIConsentDialog.js +439 -0
- package/lib/module/components/AgentChatBar.js +828 -134
- package/lib/module/components/AgentOverlay.js +2 -1
- package/lib/module/components/DiscoveryTooltip.js +21 -9
- package/lib/module/components/FloatingOverlayWrapper.js +108 -0
- package/lib/module/components/Icons.js +123 -0
- package/lib/module/config/endpoints.js +12 -2
- package/lib/module/core/AgentRuntime.js +373 -27
- package/lib/module/core/FiberAdapter.js +56 -0
- package/lib/module/core/FiberTreeWalker.js +186 -80
- package/lib/module/core/IdleDetector.js +19 -0
- package/lib/module/core/NativeAlertInterceptor.js +191 -0
- package/lib/module/core/systemPrompt.js +203 -45
- package/lib/module/index.js +3 -0
- package/lib/module/providers/GeminiProvider.js +72 -56
- package/lib/module/providers/ProviderFactory.js +6 -2
- package/lib/module/services/AudioInputService.js +3 -12
- package/lib/module/services/AudioOutputService.js +1 -13
- package/lib/module/services/ConversationService.js +166 -0
- package/lib/module/services/MobileAIKnowledgeRetriever.js +41 -0
- package/lib/module/services/VoiceService.js +29 -8
- package/lib/module/services/telemetry/MobileAI.js +44 -0
- package/lib/module/services/telemetry/TelemetryService.js +13 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js +44 -18
- package/lib/module/specs/FloatingOverlayNativeComponent.ts +19 -0
- package/lib/module/support/CSATSurvey.js +95 -12
- package/lib/module/support/EscalationSocket.js +70 -1
- package/lib/module/support/ReportedIssueEventSource.js +148 -0
- package/lib/module/support/escalateTool.js +4 -2
- package/lib/module/support/index.js +1 -0
- package/lib/module/support/reportIssueTool.js +127 -0
- package/lib/module/support/supportPrompt.js +77 -9
- package/lib/module/tools/guideTool.js +2 -1
- package/lib/module/tools/longPressTool.js +4 -3
- package/lib/module/tools/pickerTool.js +6 -4
- package/lib/module/tools/tapTool.js +12 -3
- package/lib/module/tools/typeTool.js +19 -10
- package/lib/module/utils/logger.js +175 -6
- package/lib/typescript/react-native.config.d.ts +11 -0
- package/lib/typescript/src/components/AIAgent.d.ts +28 -2
- package/lib/typescript/src/components/AIConsentDialog.d.ts +153 -0
- package/lib/typescript/src/components/AgentChatBar.d.ts +15 -2
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts +3 -1
- package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +51 -0
- package/lib/typescript/src/components/Icons.d.ts +8 -0
- package/lib/typescript/src/config/endpoints.d.ts +5 -3
- package/lib/typescript/src/core/AgentRuntime.d.ts +4 -0
- package/lib/typescript/src/core/FiberAdapter.d.ts +25 -0
- package/lib/typescript/src/core/FiberTreeWalker.d.ts +2 -0
- package/lib/typescript/src/core/IdleDetector.d.ts +11 -0
- package/lib/typescript/src/core/NativeAlertInterceptor.d.ts +55 -0
- package/lib/typescript/src/core/types.d.ts +106 -1
- package/lib/typescript/src/index.d.ts +9 -4
- package/lib/typescript/src/providers/GeminiProvider.d.ts +6 -5
- package/lib/typescript/src/services/ConversationService.d.ts +55 -0
- package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +9 -0
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts +7 -0
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +1 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +9 -6
- package/lib/typescript/src/services/telemetry/types.d.ts +3 -1
- package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +17 -0
- package/lib/typescript/src/support/EscalationSocket.d.ts +17 -0
- package/lib/typescript/src/support/ReportedIssueEventSource.d.ts +24 -0
- package/lib/typescript/src/support/escalateTool.d.ts +5 -0
- package/lib/typescript/src/support/index.d.ts +2 -1
- package/lib/typescript/src/support/reportIssueTool.d.ts +20 -0
- package/lib/typescript/src/support/types.d.ts +56 -1
- package/lib/typescript/src/utils/logger.d.ts +15 -0
- package/package.json +20 -9
- package/react-native.config.js +12 -0
- package/lib/module/__cli_tmp__.js.map +0 -1
- package/lib/module/components/AIAgent.js.map +0 -1
- package/lib/module/components/AIZone.js.map +0 -1
- package/lib/module/components/AgentChatBar.js.map +0 -1
- package/lib/module/components/AgentErrorBoundary.js.map +0 -1
- package/lib/module/components/AgentOverlay.js.map +0 -1
- package/lib/module/components/DiscoveryTooltip.js.map +0 -1
- package/lib/module/components/HighlightOverlay.js.map +0 -1
- package/lib/module/components/Icons.js.map +0 -1
- package/lib/module/components/ProactiveHint.js.map +0 -1
- package/lib/module/components/cards/InfoCard.js.map +0 -1
- package/lib/module/components/cards/ReviewSummary.js.map +0 -1
- package/lib/module/config/endpoints.js.map +0 -1
- package/lib/module/core/ActionRegistry.js.map +0 -1
- package/lib/module/core/AgentRuntime.js.map +0 -1
- package/lib/module/core/FiberTreeWalker.js.map +0 -1
- package/lib/module/core/IdleDetector.js.map +0 -1
- package/lib/module/core/MCPBridge.js.map +0 -1
- package/lib/module/core/ScreenDehydrator.js.map +0 -1
- package/lib/module/core/ZoneRegistry.js.map +0 -1
- package/lib/module/core/systemPrompt.js.map +0 -1
- package/lib/module/core/types.js.map +0 -1
- package/lib/module/hooks/useAction.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/plugin/withAppIntents.js.map +0 -1
- package/lib/module/providers/GeminiProvider.js.map +0 -1
- package/lib/module/providers/OpenAIProvider.js.map +0 -1
- package/lib/module/providers/ProviderFactory.js.map +0 -1
- package/lib/module/services/AudioInputService.js.map +0 -1
- package/lib/module/services/AudioOutputService.js.map +0 -1
- package/lib/module/services/KnowledgeBaseService.js.map +0 -1
- package/lib/module/services/VoiceService.js.map +0 -1
- package/lib/module/services/flags/FlagService.js.map +0 -1
- package/lib/module/services/telemetry/MobileAI.js.map +0 -1
- package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
- package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
- package/lib/module/services/telemetry/device.js.map +0 -1
- package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
- package/lib/module/services/telemetry/index.js.map +0 -1
- package/lib/module/services/telemetry/types.js.map +0 -1
- package/lib/module/support/CSATSurvey.js.map +0 -1
- package/lib/module/support/EscalationEventSource.js.map +0 -1
- package/lib/module/support/EscalationSocket.js.map +0 -1
- package/lib/module/support/SupportChatModal.js.map +0 -1
- package/lib/module/support/SupportGreeting.js.map +0 -1
- package/lib/module/support/TicketStore.js.map +0 -1
- package/lib/module/support/escalateTool.js.map +0 -1
- package/lib/module/support/index.js.map +0 -1
- package/lib/module/support/supportPrompt.js.map +0 -1
- package/lib/module/support/types.js.map +0 -1
- package/lib/module/tools/datePickerTool.js.map +0 -1
- package/lib/module/tools/guideTool.js.map +0 -1
- package/lib/module/tools/index.js.map +0 -1
- package/lib/module/tools/keyboardTool.js.map +0 -1
- package/lib/module/tools/longPressTool.js.map +0 -1
- package/lib/module/tools/pickerTool.js.map +0 -1
- package/lib/module/tools/restoreTool.js.map +0 -1
- package/lib/module/tools/scrollTool.js.map +0 -1
- package/lib/module/tools/simplifyTool.js.map +0 -1
- package/lib/module/tools/sliderTool.js.map +0 -1
- package/lib/module/tools/tapTool.js.map +0 -1
- package/lib/module/tools/typeTool.js.map +0 -1
- package/lib/module/tools/types.js.map +0 -1
- package/lib/module/types/jsx.d.js.map +0 -1
- package/lib/module/utils/audioUtils.js.map +0 -1
- package/lib/module/utils/logger.js.map +0 -1
- package/lib/typescript/babel.config.d.ts.map +0 -1
- package/lib/typescript/bin/generate-map.d.cts.map +0 -1
- package/lib/typescript/eslint.config.d.mts.map +0 -1
- package/lib/typescript/generate-map.d.ts.map +0 -1
- package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
- package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
- package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
- package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/Icons.d.ts.map +0 -1
- package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
- package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
- package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
- package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
- package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
- package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
- package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
- package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/src/core/types.d.ts.map +0 -1
- package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
- package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
- package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
- package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
- package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
- package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
- package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
- package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
- package/lib/typescript/src/support/index.d.ts.map +0 -1
- package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
- package/lib/typescript/src/support/types.d.ts.map +0 -1
- package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/index.d.ts.map +0 -1
- package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/types.d.ts.map +0 -1
- package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
- package/lib/typescript/src/utils/logger.d.ts.map +0 -1
- package/src/__cli_tmp__.tsx +0 -9
- package/src/cli/analyzers/chain-analyzer.ts +0 -183
- package/src/cli/extractors/ai-extractor.ts +0 -6
- package/src/cli/extractors/ast-extractor.ts +0 -551
- package/src/cli/generate-intents.ts +0 -140
- package/src/cli/generate-map.ts +0 -121
- package/src/cli/generate-swift.ts +0 -116
- package/src/cli/scanners/expo-scanner.ts +0 -203
- package/src/cli/scanners/rn-scanner.ts +0 -445
- package/src/components/AIAgent.tsx +0 -1716
- package/src/components/AIZone.tsx +0 -147
- package/src/components/AgentChatBar.tsx +0 -1143
- package/src/components/AgentErrorBoundary.tsx +0 -78
- package/src/components/AgentOverlay.tsx +0 -73
- package/src/components/DiscoveryTooltip.tsx +0 -148
- package/src/components/HighlightOverlay.tsx +0 -136
- package/src/components/Icons.tsx +0 -253
- package/src/components/ProactiveHint.tsx +0 -145
- package/src/components/cards/InfoCard.tsx +0 -58
- package/src/components/cards/ReviewSummary.tsx +0 -76
- package/src/config/endpoints.ts +0 -22
- package/src/core/ActionRegistry.ts +0 -105
- package/src/core/AgentRuntime.ts +0 -1471
- package/src/core/FiberTreeWalker.ts +0 -930
- package/src/core/IdleDetector.ts +0 -72
- package/src/core/MCPBridge.ts +0 -163
- package/src/core/ScreenDehydrator.ts +0 -53
- package/src/core/ZoneRegistry.ts +0 -44
- package/src/core/systemPrompt.ts +0 -431
- package/src/core/types.ts +0 -521
- package/src/hooks/useAction.ts +0 -182
- package/src/index.ts +0 -83
- package/src/plugin/withAppIntents.ts +0 -98
- package/src/providers/GeminiProvider.ts +0 -357
- package/src/providers/OpenAIProvider.ts +0 -379
- package/src/providers/ProviderFactory.ts +0 -36
- package/src/services/AudioInputService.ts +0 -226
- package/src/services/AudioOutputService.ts +0 -236
- package/src/services/KnowledgeBaseService.ts +0 -156
- package/src/services/VoiceService.ts +0 -451
- package/src/services/flags/FlagService.ts +0 -137
- package/src/services/telemetry/MobileAI.ts +0 -66
- package/src/services/telemetry/PiiScrubber.ts +0 -17
- package/src/services/telemetry/TelemetryService.ts +0 -323
- package/src/services/telemetry/TouchAutoCapture.ts +0 -165
- package/src/services/telemetry/device.ts +0 -93
- package/src/services/telemetry/deviceMetadata.ts +0 -13
- package/src/services/telemetry/index.ts +0 -13
- package/src/services/telemetry/types.ts +0 -75
- package/src/support/CSATSurvey.tsx +0 -304
- package/src/support/EscalationEventSource.ts +0 -190
- package/src/support/EscalationSocket.ts +0 -152
- package/src/support/SupportChatModal.tsx +0 -563
- package/src/support/SupportGreeting.tsx +0 -161
- package/src/support/TicketStore.ts +0 -100
- package/src/support/escalateTool.ts +0 -174
- package/src/support/index.ts +0 -29
- package/src/support/supportPrompt.ts +0 -55
- package/src/support/types.ts +0 -155
- package/src/tools/datePickerTool.ts +0 -60
- package/src/tools/guideTool.ts +0 -76
- package/src/tools/index.ts +0 -20
- package/src/tools/keyboardTool.ts +0 -30
- package/src/tools/longPressTool.ts +0 -61
- package/src/tools/pickerTool.ts +0 -115
- package/src/tools/restoreTool.ts +0 -33
- package/src/tools/scrollTool.ts +0 -156
- package/src/tools/simplifyTool.ts +0 -33
- package/src/tools/sliderTool.ts +0 -65
- package/src/tools/tapTool.ts +0 -93
- package/src/tools/typeTool.ts +0 -113
- package/src/tools/types.ts +0 -58
- package/src/types/jsx.d.ts +0 -20
- package/src/utils/audioUtils.ts +0 -54
- package/src/utils/logger.ts +0 -38
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentErrorBoundary — Catches React rendering errors caused by AI agent actions.
|
|
3
|
-
*
|
|
4
|
-
* When the AI taps, scrolls, or navigates, the action itself may succeed
|
|
5
|
-
* but trigger async side-effects (useEffect, onViewableItemsChanged) that
|
|
6
|
-
* crash during the next React render cycle. This boundary catches those
|
|
7
|
-
* errors, preventing red screen crashes and auto-recovering the UI.
|
|
8
|
-
*
|
|
9
|
-
* Recovery strategy:
|
|
10
|
-
* 1. Catch the error via getDerivedStateFromError
|
|
11
|
-
* 2. Log it and report to agent runtime via onError callback
|
|
12
|
-
* 3. Auto-reset after a brief delay — remounts children cleanly
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import React from 'react';
|
|
16
|
-
import { logger } from '../utils/logger';
|
|
17
|
-
|
|
18
|
-
interface Props {
|
|
19
|
-
children: React.ReactNode;
|
|
20
|
-
/** Called when an error is caught — reports back to agent runtime */
|
|
21
|
-
onError?: (error: Error, componentStack?: string) => void;
|
|
22
|
-
telemetryRef?: React.RefObject<any>; // Using any to avoid circular import, we duck-type track()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface State {
|
|
26
|
-
hasError: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class AgentErrorBoundary extends React.Component<Props, State> {
|
|
30
|
-
state: State = { hasError: false };
|
|
31
|
-
private resetTimer: ReturnType<typeof setTimeout> | null = null;
|
|
32
|
-
|
|
33
|
-
static getDerivedStateFromError(_error: Error): State {
|
|
34
|
-
return { hasError: true };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
38
|
-
const componentStack = errorInfo?.componentStack || '';
|
|
39
|
-
logger.warn(
|
|
40
|
-
'AgentErrorBoundary',
|
|
41
|
-
`🛡️ Caught rendering error: ${error.message}\n${componentStack}`
|
|
42
|
-
);
|
|
43
|
-
this.props.onError?.(error, componentStack);
|
|
44
|
-
|
|
45
|
-
// Track the render error silently in analytics
|
|
46
|
-
if (this.props.telemetryRef?.current?.track) {
|
|
47
|
-
this.props.telemetryRef.current.track('render_error', {
|
|
48
|
-
message: error.message,
|
|
49
|
-
component: componentStack?.split('\n')[1]?.trim() ?? 'unknown',
|
|
50
|
-
screen: this.props.telemetryRef.current.screen,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
componentDidUpdate(_prevProps: Props, prevState: State): void {
|
|
56
|
-
// Auto-recover: reset error state after brief delay to remount children
|
|
57
|
-
if (this.state.hasError && !prevState.hasError) {
|
|
58
|
-
this.resetTimer = setTimeout(() => {
|
|
59
|
-
this.setState({ hasError: false });
|
|
60
|
-
}, 50);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
componentWillUnmount(): void {
|
|
65
|
-
if (this.resetTimer) {
|
|
66
|
-
clearTimeout(this.resetTimer);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
render(): React.ReactNode {
|
|
71
|
-
if (this.state.hasError) {
|
|
72
|
-
// Return null briefly — children will remount on next tick
|
|
73
|
-
// when hasError resets to false via componentDidUpdate
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
return this.props.children;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentOverlay — Subtle thinking indicator shown while the AI agent is processing.
|
|
3
|
-
* Includes a cancel button to stop the agent mid-execution.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
|
|
7
|
-
import { CloseIcon } from './Icons';
|
|
8
|
-
|
|
9
|
-
interface AgentOverlayProps {
|
|
10
|
-
visible: boolean;
|
|
11
|
-
statusText: string;
|
|
12
|
-
onCancel?: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function AgentOverlay({ visible, statusText, onCancel }: AgentOverlayProps) {
|
|
16
|
-
if (!visible) return null;
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<View style={styles.overlay} pointerEvents="box-none">
|
|
20
|
-
<View style={styles.pill}>
|
|
21
|
-
<ActivityIndicator size="small" color="#fff" />
|
|
22
|
-
<Text style={styles.text}>{statusText || 'Thinking...'}</Text>
|
|
23
|
-
{onCancel && (
|
|
24
|
-
<TouchableOpacity
|
|
25
|
-
onPress={onCancel}
|
|
26
|
-
style={styles.cancelButton}
|
|
27
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
28
|
-
>
|
|
29
|
-
<CloseIcon size={12} color="#fff" />
|
|
30
|
-
</TouchableOpacity>
|
|
31
|
-
)}
|
|
32
|
-
</View>
|
|
33
|
-
</View>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const styles = StyleSheet.create({
|
|
38
|
-
overlay: {
|
|
39
|
-
position: 'absolute',
|
|
40
|
-
top: 60,
|
|
41
|
-
left: 0,
|
|
42
|
-
right: 0,
|
|
43
|
-
alignItems: 'center',
|
|
44
|
-
zIndex: 9999,
|
|
45
|
-
},
|
|
46
|
-
pill: {
|
|
47
|
-
flexDirection: 'row',
|
|
48
|
-
alignItems: 'center',
|
|
49
|
-
gap: 8,
|
|
50
|
-
backgroundColor: 'rgba(26, 26, 46, 0.9)',
|
|
51
|
-
paddingHorizontal: 16,
|
|
52
|
-
paddingVertical: 10,
|
|
53
|
-
borderRadius: 20,
|
|
54
|
-
maxWidth: '85%',
|
|
55
|
-
},
|
|
56
|
-
text: {
|
|
57
|
-
color: '#fff',
|
|
58
|
-
fontSize: 14,
|
|
59
|
-
fontWeight: '500',
|
|
60
|
-
flexShrink: 1,
|
|
61
|
-
},
|
|
62
|
-
cancelButton: {
|
|
63
|
-
marginLeft: 4,
|
|
64
|
-
width: 22,
|
|
65
|
-
height: 22,
|
|
66
|
-
borderRadius: 11,
|
|
67
|
-
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
|
68
|
-
alignItems: 'center',
|
|
69
|
-
justifyContent: 'center',
|
|
70
|
-
flexShrink: 0,
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DiscoveryTooltip — One-time tooltip shown above the FAB on first use.
|
|
3
|
-
*
|
|
4
|
-
* Tells users the AI can navigate the app and do things for them.
|
|
5
|
-
* Shows once, then persists dismissal via AsyncStorage.
|
|
6
|
-
* Bilingual: EN/AR.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useEffect, useRef } from 'react';
|
|
10
|
-
import {
|
|
11
|
-
View,
|
|
12
|
-
Text,
|
|
13
|
-
Pressable,
|
|
14
|
-
StyleSheet,
|
|
15
|
-
Animated,
|
|
16
|
-
} from 'react-native';
|
|
17
|
-
|
|
18
|
-
interface DiscoveryTooltipProps {
|
|
19
|
-
language: 'en' | 'ar';
|
|
20
|
-
primaryColor?: string;
|
|
21
|
-
onDismiss: () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const LABELS = {
|
|
25
|
-
en: '✨ I can help you navigate the app and do things for you!',
|
|
26
|
-
ar: '✨ أقدر أساعدك تتنقل في التطبيق وأعمل حاجات بدالك!',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const AUTO_DISMISS_MS = 6000;
|
|
30
|
-
|
|
31
|
-
export function DiscoveryTooltip({
|
|
32
|
-
language,
|
|
33
|
-
primaryColor,
|
|
34
|
-
onDismiss,
|
|
35
|
-
}: DiscoveryTooltipProps) {
|
|
36
|
-
const scaleAnim = useRef(new Animated.Value(0)).current;
|
|
37
|
-
const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
// Spring-in entry
|
|
41
|
-
Animated.parallel([
|
|
42
|
-
Animated.spring(scaleAnim, {
|
|
43
|
-
toValue: 1,
|
|
44
|
-
friction: 6,
|
|
45
|
-
tension: 80,
|
|
46
|
-
useNativeDriver: true,
|
|
47
|
-
}),
|
|
48
|
-
Animated.timing(opacityAnim, {
|
|
49
|
-
toValue: 1,
|
|
50
|
-
duration: 200,
|
|
51
|
-
useNativeDriver: true,
|
|
52
|
-
}),
|
|
53
|
-
]).start();
|
|
54
|
-
|
|
55
|
-
// Auto-dismiss after timeout
|
|
56
|
-
const timer = setTimeout(() => {
|
|
57
|
-
dismissWithAnimation();
|
|
58
|
-
}, AUTO_DISMISS_MS);
|
|
59
|
-
|
|
60
|
-
return () => clearTimeout(timer);
|
|
61
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
const dismissWithAnimation = () => {
|
|
65
|
-
Animated.parallel([
|
|
66
|
-
Animated.timing(scaleAnim, {
|
|
67
|
-
toValue: 0,
|
|
68
|
-
duration: 200,
|
|
69
|
-
useNativeDriver: true,
|
|
70
|
-
}),
|
|
71
|
-
Animated.timing(opacityAnim, {
|
|
72
|
-
toValue: 0,
|
|
73
|
-
duration: 200,
|
|
74
|
-
useNativeDriver: true,
|
|
75
|
-
}),
|
|
76
|
-
]).start(() => onDismiss());
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const isArabic = language === 'ar';
|
|
80
|
-
const bgColor = primaryColor || '#1a1a2e';
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<Animated.View
|
|
84
|
-
style={[
|
|
85
|
-
styles.container,
|
|
86
|
-
{
|
|
87
|
-
backgroundColor: bgColor,
|
|
88
|
-
transform: [{ scale: scaleAnim }],
|
|
89
|
-
opacity: opacityAnim,
|
|
90
|
-
},
|
|
91
|
-
]}
|
|
92
|
-
>
|
|
93
|
-
<Pressable onPress={dismissWithAnimation} style={styles.contentArea}>
|
|
94
|
-
<Text style={[styles.text, isArabic && styles.textRTL]}>
|
|
95
|
-
{LABELS[language]}
|
|
96
|
-
</Text>
|
|
97
|
-
</Pressable>
|
|
98
|
-
|
|
99
|
-
{/* Triangle pointer toward FAB */}
|
|
100
|
-
<View style={[styles.pointer, { borderTopColor: bgColor }]} />
|
|
101
|
-
</Animated.View>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const styles = StyleSheet.create({
|
|
106
|
-
container: {
|
|
107
|
-
position: 'absolute',
|
|
108
|
-
bottom: 70,
|
|
109
|
-
right: -4,
|
|
110
|
-
minWidth: 200,
|
|
111
|
-
maxWidth: 260,
|
|
112
|
-
borderRadius: 16,
|
|
113
|
-
paddingHorizontal: 14,
|
|
114
|
-
paddingVertical: 10,
|
|
115
|
-
shadowColor: '#000',
|
|
116
|
-
shadowOffset: { width: 0, height: 4 },
|
|
117
|
-
shadowOpacity: 0.3,
|
|
118
|
-
shadowRadius: 8,
|
|
119
|
-
elevation: 6,
|
|
120
|
-
},
|
|
121
|
-
contentArea: {
|
|
122
|
-
flexDirection: 'row',
|
|
123
|
-
alignItems: 'center',
|
|
124
|
-
},
|
|
125
|
-
text: {
|
|
126
|
-
color: '#ffffff',
|
|
127
|
-
fontSize: 13,
|
|
128
|
-
lineHeight: 19,
|
|
129
|
-
fontWeight: '500',
|
|
130
|
-
},
|
|
131
|
-
textRTL: {
|
|
132
|
-
textAlign: 'right',
|
|
133
|
-
writingDirection: 'rtl',
|
|
134
|
-
},
|
|
135
|
-
pointer: {
|
|
136
|
-
position: 'absolute',
|
|
137
|
-
bottom: -8,
|
|
138
|
-
right: 22,
|
|
139
|
-
width: 0,
|
|
140
|
-
height: 0,
|
|
141
|
-
borderLeftWidth: 8,
|
|
142
|
-
borderRightWidth: 8,
|
|
143
|
-
borderTopWidth: 8,
|
|
144
|
-
borderLeftColor: 'transparent',
|
|
145
|
-
borderRightColor: 'transparent',
|
|
146
|
-
borderTopColor: '#1a1a2e',
|
|
147
|
-
},
|
|
148
|
-
});
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState, useRef } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet, Animated, Pressable, DeviceEventEmitter } from 'react-native';
|
|
3
|
-
|
|
4
|
-
export interface HighlightEventData {
|
|
5
|
-
pageX: number;
|
|
6
|
-
pageY: number;
|
|
7
|
-
width: number;
|
|
8
|
-
height: number;
|
|
9
|
-
message: string;
|
|
10
|
-
autoRemoveAfterMs?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function HighlightOverlay() {
|
|
14
|
-
const [highlight, setHighlight] = useState<HighlightEventData | null>(null);
|
|
15
|
-
const pulseAnim = useRef(new Animated.Value(1)).current;
|
|
16
|
-
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
const sub = DeviceEventEmitter.addListener('MOBILE_AI_HIGHLIGHT', (data: HighlightEventData | null) => {
|
|
20
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
21
|
-
|
|
22
|
-
setHighlight(data);
|
|
23
|
-
|
|
24
|
-
if (data) {
|
|
25
|
-
// Start pulsing ring
|
|
26
|
-
pulseAnim.setValue(1);
|
|
27
|
-
Animated.loop(
|
|
28
|
-
Animated.sequence([
|
|
29
|
-
Animated.timing(pulseAnim, { toValue: 1.2, duration: 800, useNativeDriver: true }),
|
|
30
|
-
Animated.timing(pulseAnim, { toValue: 1, duration: 800, useNativeDriver: true }),
|
|
31
|
-
])
|
|
32
|
-
).start();
|
|
33
|
-
|
|
34
|
-
// Auto-dismiss
|
|
35
|
-
const ms = data.autoRemoveAfterMs || 5000;
|
|
36
|
-
timerRef.current = setTimeout(() => {
|
|
37
|
-
setHighlight(null);
|
|
38
|
-
}, ms);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return () => sub.remove();
|
|
43
|
-
}, [pulseAnim]);
|
|
44
|
-
|
|
45
|
-
if (!highlight) return null;
|
|
46
|
-
|
|
47
|
-
const { pageX, pageY, width, height, message } = highlight;
|
|
48
|
-
|
|
49
|
-
// Calculate tooltip position (prefer top, fallback to bottom if too close to top edge)
|
|
50
|
-
const isTooHigh = pageY < 60;
|
|
51
|
-
const tooltipTop = isTooHigh ? pageY + height + 12 : pageY - 45;
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<View style={StyleSheet.absoluteFill} pointerEvents="box-none">
|
|
55
|
-
{/* Invisible pressable to dismiss on tap anywhere */}
|
|
56
|
-
<Pressable testID="highlight-close-zone" style={StyleSheet.absoluteFill} onPress={() => setHighlight(null)} />
|
|
57
|
-
|
|
58
|
-
{/* The Animated Ring */}
|
|
59
|
-
<Animated.View
|
|
60
|
-
style={[
|
|
61
|
-
styles.ring,
|
|
62
|
-
{
|
|
63
|
-
left: pageX - 4,
|
|
64
|
-
top: pageY - 4,
|
|
65
|
-
width: width + 8,
|
|
66
|
-
height: height + 8,
|
|
67
|
-
transform: [{ scale: pulseAnim }],
|
|
68
|
-
}
|
|
69
|
-
]}
|
|
70
|
-
pointerEvents="none"
|
|
71
|
-
/>
|
|
72
|
-
|
|
73
|
-
{/* The Tooltip */}
|
|
74
|
-
<View style={[styles.tooltip, { top: tooltipTop }]} pointerEvents="none">
|
|
75
|
-
<Text style={styles.message}>{message}</Text>
|
|
76
|
-
<View style={isTooHigh ? styles.arrowUp : styles.arrowDown} />
|
|
77
|
-
</View>
|
|
78
|
-
</View>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const styles = StyleSheet.create({
|
|
83
|
-
ring: {
|
|
84
|
-
position: 'absolute',
|
|
85
|
-
borderWidth: 3,
|
|
86
|
-
borderColor: '#007AFF', // iOS blue
|
|
87
|
-
borderRadius: 8,
|
|
88
|
-
backgroundColor: 'rgba(0, 122, 255, 0.15)', // Very subtle fill
|
|
89
|
-
},
|
|
90
|
-
tooltip: {
|
|
91
|
-
position: 'absolute',
|
|
92
|
-
alignSelf: 'center',
|
|
93
|
-
backgroundColor: '#007AFF',
|
|
94
|
-
paddingHorizontal: 12,
|
|
95
|
-
paddingVertical: 8,
|
|
96
|
-
borderRadius: 8,
|
|
97
|
-
maxWidth: '80%',
|
|
98
|
-
shadowColor: '#000',
|
|
99
|
-
shadowOpacity: 0.2,
|
|
100
|
-
shadowOffset: { width: 0, height: 2 },
|
|
101
|
-
shadowRadius: 4,
|
|
102
|
-
elevation: 4,
|
|
103
|
-
},
|
|
104
|
-
message: {
|
|
105
|
-
color: '#fff',
|
|
106
|
-
fontSize: 14,
|
|
107
|
-
fontWeight: 'bold',
|
|
108
|
-
textAlign: 'center',
|
|
109
|
-
},
|
|
110
|
-
arrowDown: {
|
|
111
|
-
position: 'absolute',
|
|
112
|
-
bottom: -6,
|
|
113
|
-
alignSelf: 'center',
|
|
114
|
-
width: 0,
|
|
115
|
-
height: 0,
|
|
116
|
-
borderLeftWidth: 6,
|
|
117
|
-
borderRightWidth: 6,
|
|
118
|
-
borderTopWidth: 6,
|
|
119
|
-
borderLeftColor: 'transparent',
|
|
120
|
-
borderRightColor: 'transparent',
|
|
121
|
-
borderTopColor: '#007AFF',
|
|
122
|
-
},
|
|
123
|
-
arrowUp: {
|
|
124
|
-
position: 'absolute',
|
|
125
|
-
top: -6,
|
|
126
|
-
alignSelf: 'center',
|
|
127
|
-
width: 0,
|
|
128
|
-
height: 0,
|
|
129
|
-
borderLeftWidth: 6,
|
|
130
|
-
borderRightWidth: 6,
|
|
131
|
-
borderBottomWidth: 6,
|
|
132
|
-
borderLeftColor: 'transparent',
|
|
133
|
-
borderRightColor: 'transparent',
|
|
134
|
-
borderBottomColor: '#007AFF',
|
|
135
|
-
}
|
|
136
|
-
});
|
package/src/components/Icons.tsx
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Icons — Zero-dependency, View-based icons for the AI Agent chat bar.
|
|
3
|
-
*
|
|
4
|
-
* Why not emoji? iOS Simulator 26+ has a bug where emoji renders as "?".
|
|
5
|
-
* Why not Unicode symbols? They look obscure and unprofessional.
|
|
6
|
-
* Why not icon libraries? This is a library — zero runtime dependencies.
|
|
7
|
-
*
|
|
8
|
-
* These icons are built purely from React Native View components,
|
|
9
|
-
* rendering identically on every platform and screen size.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { View } from 'react-native';
|
|
13
|
-
|
|
14
|
-
// ─── Mic Icon (pill + stem + base) ────────────────────────────
|
|
15
|
-
|
|
16
|
-
export function MicIcon({ size = 20, color = '#fff' }: { size?: number; color?: string }) {
|
|
17
|
-
const pillW = size * 0.4;
|
|
18
|
-
const pillH = size * 0.5;
|
|
19
|
-
const stemW = size * 0.08;
|
|
20
|
-
const stemH = size * 0.18;
|
|
21
|
-
const baseW = size * 0.35;
|
|
22
|
-
const arcW = size * 0.55;
|
|
23
|
-
const arcH = size * 0.35;
|
|
24
|
-
const arcBorder = size * 0.07;
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
28
|
-
{/* Pill (mic head) */}
|
|
29
|
-
<View style={{
|
|
30
|
-
width: pillW,
|
|
31
|
-
height: pillH,
|
|
32
|
-
borderRadius: pillW / 2,
|
|
33
|
-
backgroundColor: color,
|
|
34
|
-
}} />
|
|
35
|
-
{/* Arc (U-shape around mic) */}
|
|
36
|
-
<View style={{
|
|
37
|
-
width: arcW,
|
|
38
|
-
height: arcH,
|
|
39
|
-
borderBottomLeftRadius: arcW / 2,
|
|
40
|
-
borderBottomRightRadius: arcW / 2,
|
|
41
|
-
borderWidth: arcBorder,
|
|
42
|
-
borderTopWidth: 0,
|
|
43
|
-
borderColor: color,
|
|
44
|
-
marginTop: -(pillH * 0.3),
|
|
45
|
-
}} />
|
|
46
|
-
{/* Stem */}
|
|
47
|
-
<View style={{
|
|
48
|
-
width: stemW,
|
|
49
|
-
height: stemH,
|
|
50
|
-
backgroundColor: color,
|
|
51
|
-
marginTop: -1,
|
|
52
|
-
}} />
|
|
53
|
-
{/* Base */}
|
|
54
|
-
<View style={{
|
|
55
|
-
width: baseW,
|
|
56
|
-
height: stemW,
|
|
57
|
-
backgroundColor: color,
|
|
58
|
-
borderRadius: stemW / 2,
|
|
59
|
-
}} />
|
|
60
|
-
</View>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ─── Speaker Icon (cone + sound waves) ────────────────────────
|
|
65
|
-
|
|
66
|
-
export function SpeakerIcon({ size = 20, color = '#fff', muted = false }: { size?: number; color?: string; muted?: boolean }) {
|
|
67
|
-
const bodyW = size * 0.25;
|
|
68
|
-
const bodyH = size * 0.3;
|
|
69
|
-
const coneW = size * 0.2;
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center', flexDirection: 'row' }}>
|
|
73
|
-
{/* Speaker body (rectangle) */}
|
|
74
|
-
<View style={{
|
|
75
|
-
width: bodyW,
|
|
76
|
-
height: bodyH,
|
|
77
|
-
backgroundColor: color,
|
|
78
|
-
borderRadius: size * 0.03,
|
|
79
|
-
}} />
|
|
80
|
-
{/* Speaker cone (triangle via borders) */}
|
|
81
|
-
<View style={{
|
|
82
|
-
width: 0,
|
|
83
|
-
height: 0,
|
|
84
|
-
borderTopWidth: size * 0.25,
|
|
85
|
-
borderTopColor: 'transparent',
|
|
86
|
-
borderBottomWidth: size * 0.25,
|
|
87
|
-
borderBottomColor: 'transparent',
|
|
88
|
-
borderLeftWidth: coneW,
|
|
89
|
-
borderLeftColor: color,
|
|
90
|
-
marginLeft: -1,
|
|
91
|
-
}} />
|
|
92
|
-
{muted ? (
|
|
93
|
-
/* Mute slash */
|
|
94
|
-
<View style={{
|
|
95
|
-
position: 'absolute',
|
|
96
|
-
width: size * 0.08,
|
|
97
|
-
height: size * 0.8,
|
|
98
|
-
backgroundColor: color,
|
|
99
|
-
borderRadius: size * 0.04,
|
|
100
|
-
transform: [{ rotate: '45deg' }],
|
|
101
|
-
}} />
|
|
102
|
-
) : (
|
|
103
|
-
/* Sound waves */
|
|
104
|
-
<View style={{ marginLeft: size * 0.05 }}>
|
|
105
|
-
<View style={{
|
|
106
|
-
width: size * 0.15,
|
|
107
|
-
height: size * 0.3,
|
|
108
|
-
borderWidth: size * 0.05,
|
|
109
|
-
borderColor: color,
|
|
110
|
-
borderLeftWidth: 0,
|
|
111
|
-
borderTopLeftRadius: 0,
|
|
112
|
-
borderBottomLeftRadius: 0,
|
|
113
|
-
borderTopRightRadius: size * 0.15,
|
|
114
|
-
borderBottomRightRadius: size * 0.15,
|
|
115
|
-
}} />
|
|
116
|
-
</View>
|
|
117
|
-
)}
|
|
118
|
-
</View>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ─── Send Arrow (upward arrow) ────────────────────────────────
|
|
123
|
-
|
|
124
|
-
export function SendArrowIcon({ size = 18, color = '#fff' }: { size?: number; color?: string }) {
|
|
125
|
-
// Filled right-pointing triangle (like iOS Messages send button)
|
|
126
|
-
const triH = size * 0.55;
|
|
127
|
-
return (
|
|
128
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
129
|
-
<View style={{
|
|
130
|
-
width: 0,
|
|
131
|
-
height: 0,
|
|
132
|
-
borderTopWidth: triH / 2,
|
|
133
|
-
borderTopColor: 'transparent',
|
|
134
|
-
borderBottomWidth: triH / 2,
|
|
135
|
-
borderBottomColor: 'transparent',
|
|
136
|
-
borderLeftWidth: triH * 0.85,
|
|
137
|
-
borderLeftColor: color,
|
|
138
|
-
marginLeft: size * 0.1,
|
|
139
|
-
}} />
|
|
140
|
-
</View>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ─── Stop Icon (filled square) ────────────────────────────────
|
|
145
|
-
|
|
146
|
-
export function StopIcon({ size = 18, color = '#fff' }: { size?: number; color?: string }) {
|
|
147
|
-
const sq = size * 0.45;
|
|
148
|
-
return (
|
|
149
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
150
|
-
<View style={{
|
|
151
|
-
width: sq,
|
|
152
|
-
height: sq,
|
|
153
|
-
backgroundColor: color,
|
|
154
|
-
borderRadius: size * 0.05,
|
|
155
|
-
}} />
|
|
156
|
-
</View>
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ─── Recording Dot (pulsing filled circle) ────────────────────
|
|
161
|
-
|
|
162
|
-
export function RecordingDot({ size = 18, color = '#FF3B30' }: { size?: number; color?: string }) {
|
|
163
|
-
const dotSize = size * 0.45;
|
|
164
|
-
return (
|
|
165
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
166
|
-
<View style={{
|
|
167
|
-
width: dotSize,
|
|
168
|
-
height: dotSize,
|
|
169
|
-
borderRadius: dotSize / 2,
|
|
170
|
-
backgroundColor: color,
|
|
171
|
-
}} />
|
|
172
|
-
</View>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ─── Loading Spinner (three dots) ─────────────────────────────
|
|
177
|
-
|
|
178
|
-
export function LoadingDots({ size = 18, color = '#fff' }: { size?: number; color?: string }) {
|
|
179
|
-
const dotSize = size * 0.15;
|
|
180
|
-
return (
|
|
181
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center', flexDirection: 'row', gap: dotSize * 0.8 }}>
|
|
182
|
-
{[0.4, 0.7, 1].map((opacity, i) => (
|
|
183
|
-
<View key={i} style={{
|
|
184
|
-
width: dotSize,
|
|
185
|
-
height: dotSize,
|
|
186
|
-
borderRadius: dotSize / 2,
|
|
187
|
-
backgroundColor: color,
|
|
188
|
-
opacity,
|
|
189
|
-
}} />
|
|
190
|
-
))}
|
|
191
|
-
</View>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ─── Close / Dismiss (X mark) ─────────────────────────────────
|
|
196
|
-
|
|
197
|
-
export function CloseIcon({ size = 14, color = 'rgba(255,255,255,0.6)' }: { size?: number; color?: string }) {
|
|
198
|
-
const barW = size * 0.7;
|
|
199
|
-
const barH = size * 0.12;
|
|
200
|
-
return (
|
|
201
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
202
|
-
<View style={{
|
|
203
|
-
position: 'absolute',
|
|
204
|
-
width: barW,
|
|
205
|
-
height: barH,
|
|
206
|
-
backgroundColor: color,
|
|
207
|
-
borderRadius: barH,
|
|
208
|
-
transform: [{ rotate: '45deg' }],
|
|
209
|
-
}} />
|
|
210
|
-
<View style={{
|
|
211
|
-
position: 'absolute',
|
|
212
|
-
width: barW,
|
|
213
|
-
height: barH,
|
|
214
|
-
backgroundColor: color,
|
|
215
|
-
borderRadius: barH,
|
|
216
|
-
transform: [{ rotate: '-45deg' }],
|
|
217
|
-
}} />
|
|
218
|
-
</View>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ─── AI Badge (for FAB) ───────────────────────────────────────
|
|
223
|
-
|
|
224
|
-
export function AIBadge({ size = 28 }: { size?: number }) {
|
|
225
|
-
// Chat bubble — clean, universally represents AI assistant
|
|
226
|
-
const bubbleW = size * 0.6;
|
|
227
|
-
const bubbleH = size * 0.45;
|
|
228
|
-
const tailSize = size * 0.12;
|
|
229
|
-
return (
|
|
230
|
-
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
231
|
-
{/* Bubble body */}
|
|
232
|
-
<View style={{
|
|
233
|
-
width: bubbleW,
|
|
234
|
-
height: bubbleH,
|
|
235
|
-
backgroundColor: '#fff',
|
|
236
|
-
borderRadius: size * 0.12,
|
|
237
|
-
marginBottom: tailSize * 0.5,
|
|
238
|
-
}} />
|
|
239
|
-
{/* Tail (small triangle at bottom-left) */}
|
|
240
|
-
<View style={{
|
|
241
|
-
position: 'absolute',
|
|
242
|
-
bottom: size * 0.18,
|
|
243
|
-
left: size * 0.22,
|
|
244
|
-
width: 0,
|
|
245
|
-
height: 0,
|
|
246
|
-
borderTopWidth: tailSize,
|
|
247
|
-
borderTopColor: '#fff',
|
|
248
|
-
borderRightWidth: tailSize,
|
|
249
|
-
borderRightColor: 'transparent',
|
|
250
|
-
}} />
|
|
251
|
-
</View>
|
|
252
|
-
);
|
|
253
|
-
}
|