@buoy-gg/core 1.7.2

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 (132) hide show
  1. package/README.md +43 -0
  2. package/lib/commonjs/floatingMenu/AppHost.js +410 -0
  3. package/lib/commonjs/floatingMenu/AppHostLogic.js +44 -0
  4. package/lib/commonjs/floatingMenu/DefaultConfigContext.js +45 -0
  5. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +2274 -0
  6. package/lib/commonjs/floatingMenu/DevToolsVisibilityContext.js +49 -0
  7. package/lib/commonjs/floatingMenu/DraggableHeader.js +114 -0
  8. package/lib/commonjs/floatingMenu/FloatingDevTools.js +254 -0
  9. package/lib/commonjs/floatingMenu/FloatingMenu.js +364 -0
  10. package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +247 -0
  11. package/lib/commonjs/floatingMenu/MinimizedToolsStack.js +206 -0
  12. package/lib/commonjs/floatingMenu/ToggleStateManager.js +36 -0
  13. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +241 -0
  14. package/lib/commonjs/floatingMenu/defaultConfig.js +160 -0
  15. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +835 -0
  16. package/lib/commonjs/floatingMenu/dial/DialIcon.js +246 -0
  17. package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +249 -0
  18. package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +70 -0
  19. package/lib/commonjs/floatingMenu/floatingTools.js +771 -0
  20. package/lib/commonjs/floatingMenu/settingsBus.js +23 -0
  21. package/lib/commonjs/floatingMenu/types.js +5 -0
  22. package/lib/commonjs/index.js +240 -0
  23. package/lib/commonjs/package.json +1 -0
  24. package/lib/module/floatingMenu/AppHost.js +402 -0
  25. package/lib/module/floatingMenu/AppHostLogic.js +39 -0
  26. package/lib/module/floatingMenu/DefaultConfigContext.js +39 -0
  27. package/lib/module/floatingMenu/DevToolsSettingsModal.js +2273 -0
  28. package/lib/module/floatingMenu/DevToolsVisibilityContext.js +44 -0
  29. package/lib/module/floatingMenu/DraggableHeader.js +110 -0
  30. package/lib/module/floatingMenu/FloatingDevTools.js +249 -0
  31. package/lib/module/floatingMenu/FloatingMenu.js +358 -0
  32. package/lib/module/floatingMenu/MinimizedToolsContext.js +239 -0
  33. package/lib/module/floatingMenu/MinimizedToolsStack.js +202 -0
  34. package/lib/module/floatingMenu/ToggleStateManager.js +32 -0
  35. package/lib/module/floatingMenu/autoDiscoverPresets.js +236 -0
  36. package/lib/module/floatingMenu/defaultConfig.js +151 -0
  37. package/lib/module/floatingMenu/dial/DialDevTools.js +829 -0
  38. package/lib/module/floatingMenu/dial/DialIcon.js +241 -0
  39. package/lib/module/floatingMenu/dial/OnboardingTooltip.js +244 -0
  40. package/lib/module/floatingMenu/dial/onboardingConstants.js +64 -0
  41. package/lib/module/floatingMenu/floatingTools.js +767 -0
  42. package/lib/module/floatingMenu/settingsBus.js +19 -0
  43. package/lib/module/floatingMenu/types.js +3 -0
  44. package/lib/module/index.js +29 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts +39 -0
  47. package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts +37 -0
  49. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +1 -0
  50. package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts +27 -0
  51. package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts.map +1 -0
  52. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +57 -0
  53. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts +25 -0
  55. package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts.map +1 -0
  56. package/lib/typescript/commonjs/floatingMenu/DraggableHeader.d.ts +30 -0
  57. package/lib/typescript/commonjs/floatingMenu/DraggableHeader.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +226 -0
  59. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts +39 -0
  61. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts +95 -0
  63. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts.map +1 -0
  64. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts +10 -0
  65. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts.map +1 -0
  66. package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts +21 -0
  67. package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts.map +1 -0
  68. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts +75 -0
  69. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +120 -0
  71. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -0
  72. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +35 -0
  73. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts +14 -0
  75. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts +12 -0
  77. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts +30 -0
  79. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts +56 -0
  81. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +1 -0
  82. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts +10 -0
  83. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/floatingMenu/types.d.ts +56 -0
  85. package/lib/typescript/commonjs/floatingMenu/types.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/index.d.ts +18 -0
  87. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/package.json +1 -0
  89. package/lib/typescript/module/floatingMenu/AppHost.d.ts +39 -0
  90. package/lib/typescript/module/floatingMenu/AppHost.d.ts.map +1 -0
  91. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts +37 -0
  92. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +1 -0
  93. package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts +27 -0
  94. package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts.map +1 -0
  95. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +57 -0
  96. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -0
  97. package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts +25 -0
  98. package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts.map +1 -0
  99. package/lib/typescript/module/floatingMenu/DraggableHeader.d.ts +30 -0
  100. package/lib/typescript/module/floatingMenu/DraggableHeader.d.ts.map +1 -0
  101. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +226 -0
  102. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -0
  103. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts +39 -0
  104. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -0
  105. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts +95 -0
  106. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts.map +1 -0
  107. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts +10 -0
  108. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts.map +1 -0
  109. package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts +21 -0
  110. package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts.map +1 -0
  111. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts +75 -0
  112. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -0
  113. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +120 -0
  114. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -0
  115. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +35 -0
  116. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -0
  117. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts +14 -0
  118. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -0
  119. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts +12 -0
  120. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -0
  121. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts +30 -0
  122. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +1 -0
  123. package/lib/typescript/module/floatingMenu/floatingTools.d.ts +56 -0
  124. package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +1 -0
  125. package/lib/typescript/module/floatingMenu/settingsBus.d.ts +10 -0
  126. package/lib/typescript/module/floatingMenu/settingsBus.d.ts.map +1 -0
  127. package/lib/typescript/module/floatingMenu/types.d.ts +56 -0
  128. package/lib/typescript/module/floatingMenu/types.d.ts.map +1 -0
  129. package/lib/typescript/module/index.d.ts +18 -0
  130. package/lib/typescript/module/index.d.ts.map +1 -0
  131. package/lib/typescript/module/package.json +1 -0
  132. package/package.json +79 -0
@@ -0,0 +1,835 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DialDevTools = void 0;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _DialIcon = require("./DialIcon.js");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _DevToolsSettingsModal = require("../DevToolsSettingsModal.js");
12
+ var _license = require("@buoy-gg/license");
13
+ var _AppHost = require("../AppHost.js");
14
+ var _OnboardingTooltip = require("./OnboardingTooltip.js");
15
+ var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ // Icons are provided by installedApps; no direct icon imports here.
18
+
19
+ const {
20
+ width: SCREEN_WIDTH
21
+ } = _reactNative.Dimensions.get("window");
22
+
23
+ // Use shared layout calculation from core
24
+ const layout = (0, _floatingToolsCore.getDialLayout)({
25
+ screenWidth: SCREEN_WIDTH
26
+ });
27
+ const CIRCLE_SIZE = layout.circleSize;
28
+ const BUTTON_SIZE = layout.buttonSize;
29
+ const ONBOARDING_STORAGE_KEY = "@react_buoy_settings_tooltip_shown";
30
+ const DialDevTools = ({
31
+ onClose,
32
+ onSettingsPress,
33
+ settings: externalSettings,
34
+ autoOpenSettings = false,
35
+ apps,
36
+ state,
37
+ actions
38
+ }) => {
39
+ const [selectedIcon, setSelectedIcon] = (0, _react.useState)(-1);
40
+ const [isSettingsModalOpen, setIsSettingsModalOpen] = (0, _react.useState)(false);
41
+ const [settingsModalStateLoaded, setSettingsModalStateLoaded] = (0, _react.useState)(false);
42
+ const [showOnboardingTooltip, setShowOnboardingTooltip] = (0, _react.useState)(false);
43
+ const onboardingDismissedRef = (0, _react.useRef)(false); // Track if onboarding was dismissed
44
+ const hintsDisabled = (0, _sharedUi.useHintsDisabled)();
45
+ const {
46
+ settings: hookSettings,
47
+ refreshSettings
48
+ } = (0, _DevToolsSettingsModal.useDevToolsSettings)();
49
+ const {
50
+ open
51
+ } = (0, _AppHost.useAppHost)();
52
+ const isPro = (0, _license.useIsPro)();
53
+ // Initialize with external settings if provided, otherwise use hook settings
54
+ const [localSettings, setLocalSettings] = (0, _react.useState)(externalSettings || hookSettings);
55
+
56
+ // Always use localSettings (which can be updated by the modal)
57
+ const settings = localSettings;
58
+
59
+ // Load persisted settings modal state on mount
60
+ (0, _react.useEffect)(() => {
61
+ const loadSettingsModalState = async () => {
62
+ try {
63
+ const savedModalOpen = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.settings.modalOpen());
64
+ if (savedModalOpen === "true") {
65
+ setIsSettingsModalOpen(true);
66
+ }
67
+ } catch (error) {
68
+ // Failed to load settings modal state - use default (closed)
69
+ } finally {
70
+ setSettingsModalStateLoaded(true);
71
+ }
72
+ };
73
+ loadSettingsModalState();
74
+ }, []);
75
+
76
+ // Persist settings modal state when it changes
77
+ (0, _react.useEffect)(() => {
78
+ // Only persist after initial state is loaded to avoid overwriting with default
79
+ if (!settingsModalStateLoaded) return;
80
+ (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.settings.modalOpen(), isSettingsModalOpen ? "true" : "false").catch(error => {
81
+ // Failed to save settings modal state - continue without persistence
82
+ console.warn("Failed to save settings modal state:", error);
83
+ });
84
+ }, [isSettingsModalOpen, settingsModalStateLoaded]);
85
+
86
+ // Update local settings when external settings change
87
+ (0, _react.useEffect)(() => {
88
+ if (externalSettings) {
89
+ setLocalSettings(externalSettings);
90
+ }
91
+ }, [externalSettings]);
92
+
93
+ // Update local settings when hook settings change (if no external settings)
94
+ (0, _react.useEffect)(() => {
95
+ if (!externalSettings) {
96
+ setLocalSettings(hookSettings);
97
+ }
98
+ }, [hookSettings, externalSettings]);
99
+
100
+ // Auto-open settings modal when prop is true
101
+ (0, _react.useEffect)(() => {
102
+ if (autoOpenSettings && !isSettingsModalOpen) {
103
+ setIsSettingsModalOpen(true);
104
+ }
105
+ }, [autoOpenSettings, isSettingsModalOpen]);
106
+
107
+ // Check if we should show the onboarding tooltip
108
+ (0, _react.useEffect)(() => {
109
+ // Skip onboarding if hints are disabled
110
+ if (hintsDisabled) {
111
+ return;
112
+ }
113
+ const checkOnboarding = async () => {
114
+ try {
115
+ const hasSeenTooltip = await (0, _sharedUi.safeGetItem)(ONBOARDING_STORAGE_KEY);
116
+ if (!hasSeenTooltip) {
117
+ // Small delay to let the entrance animations play first
118
+ setTimeout(() => {
119
+ setShowOnboardingTooltip(true);
120
+ }, 1200);
121
+ }
122
+ } catch (error) {
123
+ // If there's an error reading storage, don't show the tooltip
124
+ // to avoid annoying the user repeatedly
125
+ }
126
+ };
127
+ checkOnboarding();
128
+ }, [hintsDisabled]);
129
+
130
+ // React Native Animated values
131
+ const backdropOpacity = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
132
+ const dialScale = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
133
+ const dialRotation = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
134
+ const centerButtonScale = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
135
+ const iconsProgress = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
136
+ const glitchOffset = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
137
+ const pulseScale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
138
+ const availableApps = (0, _react.useMemo)(() => apps.map(({
139
+ id,
140
+ name,
141
+ slot,
142
+ description
143
+ }) => ({
144
+ id,
145
+ name,
146
+ slot,
147
+ description
148
+ })), [apps]);
149
+
150
+ // Subtle animations
151
+ const floatingAnim = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
152
+ const breathingScale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
153
+ const circuitOpacity = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
154
+
155
+ // Animation tracking refs
156
+ const glitchIntervalRef = (0, _react.useRef)(null);
157
+ const pulseAnimationRef = (0, _react.useRef)(null);
158
+
159
+ // Map data-driven apps to dial icons, inserting empty slots for disabled items
160
+ const dialApps = apps.filter(a => (a.slot ?? "both") !== "row");
161
+ const isDialEnabled = id => {
162
+ if (!settings) return false;
163
+ return settings.dialTools[id] ?? false;
164
+ };
165
+ const createEmptySlot = slotIndex => ({
166
+ id: `empty-${slotIndex}`,
167
+ name: `empty-${slotIndex}`,
168
+ icon: null,
169
+ color: "transparent",
170
+ onPress: () => {}
171
+ });
172
+ const enabledIcons = [];
173
+ for (const app of dialApps) {
174
+ if (!isDialEnabled(app.id)) {
175
+ continue;
176
+ }
177
+ if (enabledIcons.length >= _floatingToolsCore.MAX_DIAL_SLOTS) {
178
+ break;
179
+ }
180
+ enabledIcons.push({
181
+ id: app.id,
182
+ name: app.name,
183
+ // Pass both the pre-rendered icon (for non-function icons) and the component (for dynamic rendering)
184
+ icon: typeof app.icon === "function" ? null // Will be rendered dynamically by DialIcon
185
+ : app.icon,
186
+ // Cast to the expected type - the function signature is compatible at runtime
187
+ iconComponent: typeof app.icon === "function" ? app.icon : undefined,
188
+ color: app.color ?? _sharedUi.buoyColors.primary,
189
+ onPress: () => {
190
+ // Call the app's onPress callback if provided, passing actions for toggle tools
191
+ app?.onPress?.(actions);
192
+
193
+ // Only open modal if not a toggle-only tool
194
+ if (app.launchMode !== "toggle-only") {
195
+ const resolvedIcon = typeof app.icon === "function" ? app.icon({
196
+ slot: "dial",
197
+ size: 20
198
+ }) : app.icon;
199
+ open({
200
+ id: app.id,
201
+ title: app.name,
202
+ component: app.component,
203
+ props: app.props,
204
+ launchMode: app.launchMode ?? "self-modal",
205
+ singleton: app.singleton ?? true,
206
+ icon: resolvedIcon,
207
+ color: app.color
208
+ });
209
+ }
210
+
211
+ // Close the dial
212
+ onClose?.();
213
+ }
214
+ });
215
+ }
216
+ if (__DEV__) {
217
+ const totalEnabled = dialApps.filter(app => isDialEnabled(app.id)).length;
218
+ if (totalEnabled > _floatingToolsCore.MAX_DIAL_SLOTS) {
219
+ // More tools enabled than can be shown - they will be hidden
220
+ }
221
+ }
222
+ const icons = [...enabledIcons];
223
+ while (icons.length < _floatingToolsCore.MAX_DIAL_SLOTS) {
224
+ icons.push(createEmptySlot(icons.length));
225
+ }
226
+
227
+ // Initialize animations on mount - using shared config from core
228
+ (0, _react.useEffect)(() => {
229
+ // Fallback config in case dialAnimationConfig hasn't loaded
230
+ const config = _floatingToolsCore.dialAnimationConfig ?? {
231
+ entrance: {
232
+ backdrop: {
233
+ duration: 400
234
+ },
235
+ dial: {
236
+ scale: {
237
+ damping: 15,
238
+ stiffness: 150,
239
+ mass: 1
240
+ },
241
+ rotation: {
242
+ duration: 800
243
+ }
244
+ },
245
+ centerButton: {
246
+ delay: 300,
247
+ damping: 10,
248
+ stiffness: 200
249
+ },
250
+ icons: {
251
+ delay: 500,
252
+ duration: 600
253
+ },
254
+ circuitTraces: {
255
+ delay: 600,
256
+ duration: 1000
257
+ }
258
+ },
259
+ continuous: {
260
+ glitch: {
261
+ interval: 3000,
262
+ offset: 2,
263
+ stepDuration: 50
264
+ },
265
+ pulse: {
266
+ maxScale: 1.02,
267
+ minScale: 0.98,
268
+ duration: 1000
269
+ },
270
+ floating: {
271
+ maxY: -8,
272
+ minY: 0,
273
+ duration: 3000
274
+ },
275
+ breathing: {
276
+ maxScale: 1.05,
277
+ minScale: 0.98,
278
+ duration: 2500
279
+ }
280
+ }
281
+ };
282
+ const {
283
+ entrance,
284
+ continuous
285
+ } = config;
286
+
287
+ // Entrance animation sequence
288
+ _reactNative.Animated.timing(backdropOpacity, {
289
+ toValue: 1,
290
+ duration: entrance.backdrop.duration,
291
+ useNativeDriver: true
292
+ }).start();
293
+ _reactNative.Animated.spring(dialScale, {
294
+ toValue: 1,
295
+ damping: entrance.dial.scale.damping,
296
+ stiffness: entrance.dial.scale.stiffness,
297
+ mass: entrance.dial.scale.mass,
298
+ useNativeDriver: true
299
+ }).start();
300
+ _reactNative.Animated.sequence([_reactNative.Animated.timing(dialRotation, {
301
+ toValue: 1,
302
+ duration: entrance.dial.rotation.duration,
303
+ easing: _reactNative.Easing.out(_reactNative.Easing.cubic),
304
+ useNativeDriver: true
305
+ }), _reactNative.Animated.timing(dialRotation, {
306
+ toValue: 0,
307
+ duration: 0,
308
+ useNativeDriver: true
309
+ })]).start();
310
+ _reactNative.Animated.sequence([_reactNative.Animated.delay(entrance.centerButton.delay), _reactNative.Animated.spring(centerButtonScale, {
311
+ toValue: 1,
312
+ damping: entrance.centerButton.damping,
313
+ stiffness: entrance.centerButton.stiffness,
314
+ useNativeDriver: true
315
+ })]).start();
316
+ _reactNative.Animated.sequence([_reactNative.Animated.delay(entrance.icons.delay), _reactNative.Animated.timing(iconsProgress, {
317
+ toValue: 1,
318
+ duration: entrance.icons.duration,
319
+ easing: _reactNative.Easing.out(_reactNative.Easing.cubic),
320
+ useNativeDriver: true
321
+ })]).start();
322
+
323
+ // Subtle glitch effect - using shared config
324
+ const glitchAnimation = () => {
325
+ _reactNative.Animated.sequence([_reactNative.Animated.timing(glitchOffset, {
326
+ toValue: continuous.glitch.offset,
327
+ duration: continuous.glitch.stepDuration,
328
+ useNativeDriver: true
329
+ }), _reactNative.Animated.timing(glitchOffset, {
330
+ toValue: -continuous.glitch.offset,
331
+ duration: continuous.glitch.stepDuration,
332
+ useNativeDriver: true
333
+ }), _reactNative.Animated.timing(glitchOffset, {
334
+ toValue: 0,
335
+ duration: continuous.glitch.stepDuration,
336
+ useNativeDriver: true
337
+ })]).start();
338
+ };
339
+ glitchIntervalRef.current = setInterval(glitchAnimation, continuous.glitch.interval);
340
+
341
+ // Pulse animation - using shared config
342
+ const startPulse = () => {
343
+ pulseAnimationRef.current = _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(pulseScale, {
344
+ toValue: continuous.pulse.maxScale,
345
+ duration: continuous.pulse.duration,
346
+ easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
347
+ useNativeDriver: true
348
+ }), _reactNative.Animated.timing(pulseScale, {
349
+ toValue: continuous.pulse.minScale,
350
+ duration: continuous.pulse.duration,
351
+ easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
352
+ useNativeDriver: true
353
+ })]));
354
+ pulseAnimationRef.current.start();
355
+ };
356
+ startPulse();
357
+
358
+ // Subtle floating animation for the dial - using shared config
359
+ _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(floatingAnim, {
360
+ toValue: continuous.floating.maxY,
361
+ duration: continuous.floating.duration,
362
+ easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
363
+ useNativeDriver: true
364
+ }), _reactNative.Animated.timing(floatingAnim, {
365
+ toValue: continuous.floating.minY,
366
+ duration: continuous.floating.duration,
367
+ easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
368
+ useNativeDriver: true
369
+ })])).start();
370
+
371
+ // Gentle breathing effect for center button - using shared config
372
+ _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(breathingScale, {
373
+ toValue: continuous.breathing.maxScale,
374
+ duration: continuous.breathing.duration,
375
+ easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
376
+ useNativeDriver: true
377
+ }), _reactNative.Animated.timing(breathingScale, {
378
+ toValue: continuous.breathing.minScale,
379
+ duration: continuous.breathing.duration,
380
+ easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
381
+ useNativeDriver: true
382
+ })])).start();
383
+
384
+ // Circuit traces fade in - using shared config
385
+ _reactNative.Animated.timing(circuitOpacity, {
386
+ toValue: 1,
387
+ duration: entrance.circuitTraces.duration,
388
+ delay: entrance.circuitTraces.delay,
389
+ useNativeDriver: true
390
+ }).start();
391
+ return () => {
392
+ if (glitchIntervalRef.current) {
393
+ clearInterval(glitchIntervalRef.current);
394
+ }
395
+ if (pulseAnimationRef.current) {
396
+ pulseAnimationRef.current.stop();
397
+ }
398
+ };
399
+ }, []);
400
+ const handleOnboardingDismiss = () => {
401
+ // Mark as dismissed immediately in ref (synchronous, no re-render needed)
402
+ onboardingDismissedRef.current = true;
403
+
404
+ // Hide the tooltip
405
+ setShowOnboardingTooltip(false);
406
+
407
+ // Save to storage asynchronously in the background
408
+ (0, _sharedUi.safeSetItem)(ONBOARDING_STORAGE_KEY, "true").catch(error => {
409
+ // Silently fail - user already saw onboarding, just won't persist
410
+ console.warn("Failed to save dial onboarding state:", error);
411
+ });
412
+ };
413
+ const handleClose = () => {
414
+ // Stop any ongoing animations first
415
+ if (pulseAnimationRef.current) {
416
+ pulseAnimationRef.current.stop();
417
+ }
418
+ const exit = _floatingToolsCore.dialAnimationConfig?.exit ?? {
419
+ icons: {
420
+ duration: 300
421
+ },
422
+ centerButton: {
423
+ duration: 200
424
+ },
425
+ dialScale: {
426
+ duration: 250
427
+ },
428
+ backdrop: {
429
+ duration: 200
430
+ }
431
+ };
432
+
433
+ // Exit animation sequence - reverse order of entrance, using shared config
434
+ _reactNative.Animated.sequence([
435
+ // First animate icons back to center
436
+ _reactNative.Animated.timing(iconsProgress, {
437
+ toValue: 0,
438
+ duration: exit.icons.duration,
439
+ easing: _reactNative.Easing.in(_reactNative.Easing.cubic),
440
+ useNativeDriver: true
441
+ }),
442
+ // Then scale down center button and dial
443
+ _reactNative.Animated.parallel([_reactNative.Animated.timing(centerButtonScale, {
444
+ toValue: 0,
445
+ duration: exit.centerButton.duration,
446
+ easing: _reactNative.Easing.in(_reactNative.Easing.cubic),
447
+ useNativeDriver: true
448
+ }), _reactNative.Animated.timing(dialScale, {
449
+ toValue: 0,
450
+ duration: exit.dialScale.duration,
451
+ easing: _reactNative.Easing.in(_reactNative.Easing.cubic),
452
+ useNativeDriver: true
453
+ })]),
454
+ // Finally fade out backdrop
455
+ _reactNative.Animated.timing(backdropOpacity, {
456
+ toValue: 0,
457
+ duration: exit.backdrop.duration,
458
+ useNativeDriver: true
459
+ })]).start(() => {
460
+ // Use setTimeout to defer the state update to the next tick
461
+ // This avoids the useInsertionEffect warning
462
+ if (onClose) {
463
+ setTimeout(() => {
464
+ onClose();
465
+ }, 0);
466
+ }
467
+ });
468
+ };
469
+ const handleIconPress = index => {
470
+ setSelectedIcon(index);
471
+ const interaction = _floatingToolsCore.dialAnimationConfig?.interaction ?? {
472
+ iconSelect: {
473
+ pulse: [{
474
+ scale: 0.9,
475
+ damping: 15,
476
+ stiffness: 500
477
+ }, {
478
+ scale: 1,
479
+ damping: 10,
480
+ stiffness: 200
481
+ }],
482
+ actionDelay: 50
483
+ }
484
+ };
485
+ const [pulseIn, pulseOut] = interaction.iconSelect.pulse;
486
+
487
+ // Pulse animation on selection - using shared config
488
+ _reactNative.Animated.sequence([_reactNative.Animated.spring(centerButtonScale, {
489
+ toValue: pulseIn.scale,
490
+ damping: pulseIn.damping,
491
+ stiffness: pulseIn.stiffness,
492
+ useNativeDriver: true
493
+ }), _reactNative.Animated.spring(centerButtonScale, {
494
+ toValue: pulseOut.scale,
495
+ damping: pulseOut.damping,
496
+ stiffness: pulseOut.stiffness,
497
+ useNativeDriver: true
498
+ })]).start();
499
+
500
+ // Trigger action - using shared delay
501
+ setTimeout(() => {
502
+ icons[index].onPress();
503
+ // Only close if it's not the WiFi toggle (by id)
504
+ if (icons[index].id !== "wifi") {
505
+ handleClose();
506
+ }
507
+ }, interaction.iconSelect.actionDelay);
508
+ };
509
+
510
+ // Animated styles
511
+ const backdropAnimatedStyle = {
512
+ opacity: backdropOpacity
513
+ };
514
+ const glitchAnimatedStyle = {
515
+ transform: [{
516
+ translateX: glitchOffset
517
+ }]
518
+ };
519
+ const centerButtonAnimatedStyle = {
520
+ transform: [{
521
+ scale: _reactNative.Animated.multiply(centerButtonScale, breathingScale)
522
+ }]
523
+ };
524
+ const pulseAnimatedStyle = {
525
+ transform: [{
526
+ scale: selectedIcon >= 0 ? 1 : pulseScale
527
+ }]
528
+ };
529
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
530
+ style: styles.container,
531
+ nativeID: "dial-devtools-root",
532
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
533
+ style: [styles.backdrop, backdropAnimatedStyle],
534
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
535
+ style: _reactNative.StyleSheet.absoluteFillObject,
536
+ onPress: handleClose
537
+ })
538
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
539
+ style: [styles.parent, {
540
+ position: "absolute",
541
+ left: (SCREEN_WIDTH - CIRCLE_SIZE) / 2,
542
+ bottom: 80,
543
+ transform: [{
544
+ translateY: floatingAnim
545
+ }, {
546
+ scale: dialScale
547
+ }, {
548
+ rotate: dialRotation.interpolate({
549
+ inputRange: [0, 1],
550
+ outputRange: ["0deg", "360deg"]
551
+ })
552
+ }]
553
+ }],
554
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
555
+ style: [styles.circle, glitchAnimatedStyle],
556
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
557
+ style: styles.gradientBackground,
558
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
559
+ style: styles.gradientLayer1
560
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
561
+ style: styles.gradientLayer2
562
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
563
+ style: styles.gradientLayer3
564
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
565
+ style: styles.gridPattern,
566
+ children: Array.from({
567
+ length: 6
568
+ }).map((_, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
569
+ style: [styles.gridLine, {
570
+ transform: [{
571
+ rotate: `${i * 60}deg`
572
+ }]
573
+ }]
574
+ }, i))
575
+ })]
576
+ }), icons.map((icon, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_DialIcon.DialIcon, {
577
+ selectedIcon: selectedIcon,
578
+ onPress: handleIconPress,
579
+ iconsProgress: iconsProgress,
580
+ icon: icon,
581
+ index: i,
582
+ totalIcons: icons.length
583
+ }, `${i}-${icon.name}`))]
584
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
585
+ style: [styles.buttonContainer, centerButtonAnimatedStyle],
586
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
587
+ style: styles.buttonGradient,
588
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
589
+ style: styles.buttonGradientLayer1
590
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
591
+ style: styles.buttonGradientLayer2
592
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
593
+ style: styles.buttonGradientLayer3
594
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
595
+ style: styles.buttonBorder,
596
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
597
+ style: [styles.button, pulseAnimatedStyle],
598
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
599
+ accessibilityRole: "button",
600
+ accessibilityLabel: isSettingsModalOpen ? "Close Settings" : "Open Dev Tools Settings",
601
+ onPress: () => {
602
+ if (isSettingsModalOpen) {
603
+ // Close settings modal
604
+ setIsSettingsModalOpen(false);
605
+ } else {
606
+ // Open internal settings modal
607
+ setIsSettingsModalOpen(true);
608
+ // Dismiss onboarding tooltip when user opens settings
609
+ if (showOnboardingTooltip && !onboardingDismissedRef.current) {
610
+ handleOnboardingDismiss();
611
+ }
612
+ // Also call external handler if provided
613
+ if (onSettingsPress) {
614
+ onSettingsPress();
615
+ }
616
+ }
617
+ },
618
+ style: styles.buttonPressable,
619
+ children: isSettingsModalOpen ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
620
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
621
+ style: [styles.centerText, styles.closeTextTop],
622
+ children: "CLOSE"
623
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
624
+ style: [styles.centerText, styles.closeTextBottom],
625
+ children: "SETTINGS"
626
+ })]
627
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
628
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
629
+ style: styles.centerText,
630
+ children: "BUOY"
631
+ }), isPro && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
632
+ style: styles.proText,
633
+ children: "PRO"
634
+ })]
635
+ })
636
+ })
637
+ })
638
+ })]
639
+ })
640
+ })]
641
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_DevToolsSettingsModal.DevToolsSettingsModal, {
642
+ visible: isSettingsModalOpen,
643
+ onClose: () => {
644
+ setIsSettingsModalOpen(false);
645
+ refreshSettings(); // Refresh from storage
646
+ },
647
+ onSettingsChange: newSettings => {
648
+ // Immediately update local settings for instant feedback
649
+ setLocalSettings(newSettings);
650
+ },
651
+ availableApps: availableApps
652
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_OnboardingTooltip.OnboardingTooltip, {
653
+ visible: showOnboardingTooltip && !isSettingsModalOpen && !onboardingDismissedRef.current,
654
+ onDismiss: handleOnboardingDismiss
655
+ })]
656
+ });
657
+ };
658
+ exports.DialDevTools = DialDevTools;
659
+ const styles = _reactNative.StyleSheet.create({
660
+ container: {
661
+ ..._reactNative.StyleSheet.absoluteFillObject,
662
+ zIndex: 9999
663
+ },
664
+ backdrop: {
665
+ ..._reactNative.StyleSheet.absoluteFillObject,
666
+ backgroundColor: _floatingToolsCore.dialColors.dialBackdrop
667
+ },
668
+ parent: {
669
+ width: CIRCLE_SIZE,
670
+ height: CIRCLE_SIZE,
671
+ alignItems: "center",
672
+ justifyContent: "center"
673
+ },
674
+ circle: {
675
+ width: CIRCLE_SIZE,
676
+ height: CIRCLE_SIZE,
677
+ borderRadius: CIRCLE_SIZE / 2,
678
+ position: "absolute",
679
+ backgroundColor: "transparent",
680
+ borderWidth: 1,
681
+ borderColor: _floatingToolsCore.dialColors.dialBorder,
682
+ shadowColor: _floatingToolsCore.dialColors.dialShadow,
683
+ shadowOffset: {
684
+ width: 0,
685
+ height: 0
686
+ },
687
+ shadowOpacity: 0.5,
688
+ shadowRadius: 20,
689
+ elevation: 10
690
+ },
691
+ gradientBackground: {
692
+ width: "100%",
693
+ height: "100%",
694
+ borderRadius: CIRCLE_SIZE / 2,
695
+ position: "relative",
696
+ backgroundColor: _floatingToolsCore.dialColors.dialBackground,
697
+ overflow: "hidden"
698
+ },
699
+ gradientLayer1: {
700
+ ..._reactNative.StyleSheet.absoluteFillObject,
701
+ backgroundColor: _floatingToolsCore.dialColors.dialGradient1,
702
+ opacity: 0.6,
703
+ borderRadius: CIRCLE_SIZE / 2
704
+ },
705
+ gradientLayer2: {
706
+ ..._reactNative.StyleSheet.absoluteFillObject,
707
+ backgroundColor: _floatingToolsCore.dialColors.dialGradient2,
708
+ opacity: 0.4,
709
+ top: "30%",
710
+ left: "30%",
711
+ borderRadius: CIRCLE_SIZE / 2
712
+ },
713
+ gradientLayer3: {
714
+ ..._reactNative.StyleSheet.absoluteFillObject,
715
+ backgroundColor: _floatingToolsCore.dialColors.dialGradient3,
716
+ opacity: 0.3,
717
+ top: "50%",
718
+ left: "50%",
719
+ borderRadius: CIRCLE_SIZE / 2
720
+ },
721
+ gridPattern: {
722
+ ..._reactNative.StyleSheet.absoluteFillObject,
723
+ alignItems: "center",
724
+ justifyContent: "center"
725
+ },
726
+ gridLine: {
727
+ position: "absolute",
728
+ width: CIRCLE_SIZE,
729
+ height: 1,
730
+ backgroundColor: _floatingToolsCore.dialColors.dialGridLine
731
+ },
732
+ buttonContainer: {
733
+ zIndex: 1,
734
+ backgroundColor: "transparent",
735
+ alignItems: "center",
736
+ justifyContent: "center",
737
+ position: "absolute",
738
+ width: BUTTON_SIZE * 1.5,
739
+ height: BUTTON_SIZE * 1.5,
740
+ borderRadius: BUTTON_SIZE
741
+ },
742
+ buttonGradient: {
743
+ width: "100%",
744
+ height: "100%",
745
+ borderRadius: BUTTON_SIZE,
746
+ alignItems: "center",
747
+ justifyContent: "center",
748
+ padding: 4,
749
+ backgroundColor: _floatingToolsCore.dialColors.dialBackground,
750
+ position: "relative",
751
+ overflow: "hidden"
752
+ },
753
+ buttonGradientLayer1: {
754
+ ..._reactNative.StyleSheet.absoluteFillObject,
755
+ backgroundColor: _floatingToolsCore.dialColors.dialGradient1,
756
+ opacity: 0.5,
757
+ borderRadius: BUTTON_SIZE
758
+ },
759
+ buttonGradientLayer2: {
760
+ ..._reactNative.StyleSheet.absoluteFillObject,
761
+ backgroundColor: _floatingToolsCore.dialColors.dialGradient2,
762
+ opacity: 0.3,
763
+ top: "20%",
764
+ left: "20%",
765
+ borderRadius: BUTTON_SIZE
766
+ },
767
+ buttonGradientLayer3: {
768
+ ..._reactNative.StyleSheet.absoluteFillObject,
769
+ backgroundColor: _floatingToolsCore.dialColors.dialGradient3,
770
+ opacity: 0.2,
771
+ top: "40%",
772
+ left: "40%",
773
+ borderRadius: BUTTON_SIZE
774
+ },
775
+ buttonBorder: {
776
+ backgroundColor: _floatingToolsCore.dialColors.dialGridLine,
777
+ alignItems: "center",
778
+ justifyContent: "center",
779
+ width: BUTTON_SIZE * 1.2,
780
+ height: BUTTON_SIZE * 1.2,
781
+ borderRadius: BUTTON_SIZE * 0.6,
782
+ borderWidth: 2,
783
+ borderColor: _floatingToolsCore.dialColors.dialBorder
784
+ },
785
+ button: {
786
+ width: BUTTON_SIZE,
787
+ height: BUTTON_SIZE,
788
+ borderRadius: BUTTON_SIZE / 2,
789
+ justifyContent: "center",
790
+ alignItems: "center",
791
+ position: "relative",
792
+ overflow: "hidden"
793
+ },
794
+ buttonPressable: {
795
+ width: "100%",
796
+ height: "100%",
797
+ justifyContent: "center",
798
+ alignItems: "center"
799
+ },
800
+ centerText: {
801
+ color: "#FFFFFF",
802
+ fontSize: 10,
803
+ fontWeight: "900",
804
+ fontFamily: "monospace",
805
+ letterSpacing: 1,
806
+ textAlign: "center",
807
+ textTransform: "uppercase",
808
+ textShadowColor: _sharedUi.buoyColors.primary,
809
+ textShadowOffset: {
810
+ width: 0,
811
+ height: 0
812
+ },
813
+ textShadowRadius: 4
814
+ },
815
+ closeTextTop: {
816
+ marginBottom: -2
817
+ },
818
+ closeTextBottom: {
819
+ marginTop: -2
820
+ },
821
+ proText: {
822
+ color: _sharedUi.buoyColors.warning,
823
+ fontSize: 8,
824
+ fontWeight: "900",
825
+ fontFamily: "monospace",
826
+ letterSpacing: 2,
827
+ textAlign: "center",
828
+ textShadowColor: _sharedUi.buoyColors.warning,
829
+ textShadowOffset: {
830
+ width: 0,
831
+ height: 0
832
+ },
833
+ textShadowRadius: 4
834
+ }
835
+ });