@buoy-gg/core 2.1.15 → 3.0.0
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/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +4 -34
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +3 -25
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +14 -1
- package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +19 -9
- package/lib/commonjs/floatingMenu/FloatingMenu.js +6 -6
- package/lib/commonjs/floatingMenu/defaultConfig.js +1 -1
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +206 -224
- package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +82 -7
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +77 -71
- package/lib/commonjs/floatingMenu/dial/DialPagination.js +170 -0
- package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +97 -0
- package/lib/module/floatingMenu/DevToolsSettingsModal.js +5 -35
- package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +4 -28
- package/lib/module/floatingMenu/FloatingDevTools.js +14 -1
- package/lib/module/floatingMenu/FloatingDevTools.web.js +19 -9
- package/lib/module/floatingMenu/FloatingMenu.js +7 -7
- package/lib/module/floatingMenu/defaultConfig.js +1 -1
- package/lib/module/floatingMenu/dial/DialDevTools.js +209 -226
- package/lib/module/floatingMenu/dial/DialDevTools.web.js +82 -7
- package/lib/module/floatingMenu/dial/DialIcon.js +81 -74
- package/lib/module/floatingMenu/dial/DialPagination.js +165 -0
- package/lib/module/floatingMenu/dial/dialUsageStore.js +89 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +0 -2
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts +7 -2
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts +22 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts +34 -0
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +0 -2
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts +7 -2
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts +22 -0
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts +34 -0
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +1 -0
- package/package.json +5 -5
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { Pressable, StyleSheet, View,
|
|
4
|
+
import { Pressable, StyleSheet, View, useWindowDimensions, Text, Animated, Easing } from "react-native";
|
|
5
5
|
// Icons are provided by installedApps; no direct icon imports here.
|
|
6
6
|
import { DialIcon } from "./DialIcon.js";
|
|
7
|
+
import { DialPagination } from "./DialPagination.js";
|
|
8
|
+
import { getRankedToolIds, isDialUsageLoaded, loadDialUsage, recordToolUsage } from "./dialUsageStore.js";
|
|
7
9
|
import { persistentStorage, useHintsDisabled, devToolsStorageKeys, buoyColors } from "@buoy-gg/shared-ui";
|
|
8
10
|
import { DevToolsSettingsModal, useDevToolsSettings } from "../DevToolsSettingsModal";
|
|
9
11
|
import { useIsPro } from "@buoy-gg/license";
|
|
10
12
|
import { useAppHost } from "../AppHost.js";
|
|
11
13
|
import { OnboardingTooltip } from "./OnboardingTooltip.js";
|
|
12
|
-
import { getDialLayout, MAX_DIAL_SLOTS, dialAnimationConfig, dialColors } from "@buoy-gg/floating-tools-core";
|
|
13
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
14
|
-
const {
|
|
15
|
-
width: SCREEN_WIDTH
|
|
16
|
-
} = Dimensions.get("window");
|
|
14
|
+
import { getDialLayout, MAX_DIAL_SLOTS, DIAL_BUTTON_SIZE, dialAnimationConfig, dialColors } from "@buoy-gg/floating-tools-core";
|
|
17
15
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
const
|
|
23
|
-
const BUTTON_SIZE = layout.buttonSize;
|
|
16
|
+
// The circle size depends on the live window width, so it's computed inside
|
|
17
|
+
// the component via useWindowDimensions — a module-scope Dimensions.get
|
|
18
|
+
// snapshot goes stale when the window resizes after load (web/desktop).
|
|
19
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
20
|
+
const BUTTON_SIZE = DIAL_BUTTON_SIZE;
|
|
24
21
|
const ONBOARDING_STORAGE_KEY = "@react_buoy_settings_tooltip_shown";
|
|
22
|
+
/** A non-interactive placeholder used to fill out the last dial page. */
|
|
23
|
+
const createEmptySlot = slotIndex => ({
|
|
24
|
+
id: `empty-${slotIndex}`,
|
|
25
|
+
name: `empty-${slotIndex}`,
|
|
26
|
+
icon: null,
|
|
27
|
+
color: "transparent",
|
|
28
|
+
onPress: () => {}
|
|
29
|
+
});
|
|
25
30
|
export const DialDevTools = ({
|
|
26
31
|
onClose,
|
|
27
32
|
onSettingsPress,
|
|
28
|
-
settings: externalSettings,
|
|
29
33
|
autoOpenSettings = false,
|
|
30
34
|
apps,
|
|
31
35
|
state,
|
|
@@ -38,18 +42,39 @@ export const DialDevTools = ({
|
|
|
38
42
|
const onboardingDismissedRef = useRef(false); // Track if onboarding was dismissed
|
|
39
43
|
const hintsDisabled = useHintsDisabled();
|
|
40
44
|
const {
|
|
41
|
-
settings: hookSettings,
|
|
42
45
|
refreshSettings
|
|
43
46
|
} = useDevToolsSettings();
|
|
44
47
|
const {
|
|
45
48
|
open
|
|
46
49
|
} = useAppHost();
|
|
47
50
|
const isPro = useIsPro();
|
|
48
|
-
// Initialize with external settings if provided, otherwise use hook settings
|
|
49
|
-
const [localSettings, setLocalSettings] = useState(externalSettings || hookSettings);
|
|
50
51
|
|
|
51
|
-
//
|
|
52
|
-
|
|
52
|
+
// Live window size — keeps the dial centered and sized correctly when the
|
|
53
|
+
// window resizes (Electron/web) or the device rotates.
|
|
54
|
+
const {
|
|
55
|
+
width: screenWidth,
|
|
56
|
+
height: screenHeight
|
|
57
|
+
} = useWindowDimensions();
|
|
58
|
+
const circleSize = getDialLayout({
|
|
59
|
+
screenWidth
|
|
60
|
+
}).circleSize;
|
|
61
|
+
const sizeStyles = useMemo(() => ({
|
|
62
|
+
parent: {
|
|
63
|
+
width: circleSize,
|
|
64
|
+
height: circleSize
|
|
65
|
+
},
|
|
66
|
+
circle: {
|
|
67
|
+
width: circleSize,
|
|
68
|
+
height: circleSize,
|
|
69
|
+
borderRadius: circleSize / 2
|
|
70
|
+
},
|
|
71
|
+
rounded: {
|
|
72
|
+
borderRadius: circleSize / 2
|
|
73
|
+
},
|
|
74
|
+
gridLine: {
|
|
75
|
+
width: circleSize
|
|
76
|
+
}
|
|
77
|
+
}), [circleSize]);
|
|
53
78
|
|
|
54
79
|
// Load persisted settings modal state on mount
|
|
55
80
|
useEffect(() => {
|
|
@@ -78,20 +103,6 @@ export const DialDevTools = ({
|
|
|
78
103
|
});
|
|
79
104
|
}, [isSettingsModalOpen, settingsModalStateLoaded]);
|
|
80
105
|
|
|
81
|
-
// Update local settings when external settings change
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (externalSettings) {
|
|
84
|
-
setLocalSettings(externalSettings);
|
|
85
|
-
}
|
|
86
|
-
}, [externalSettings]);
|
|
87
|
-
|
|
88
|
-
// Update local settings when hook settings change (if no external settings)
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (!externalSettings) {
|
|
91
|
-
setLocalSettings(hookSettings);
|
|
92
|
-
}
|
|
93
|
-
}, [hookSettings, externalSettings]);
|
|
94
|
-
|
|
95
106
|
// Auto-open settings modal when prop is true
|
|
96
107
|
useEffect(() => {
|
|
97
108
|
if (autoOpenSettings && !isSettingsModalOpen) {
|
|
@@ -128,7 +139,6 @@ export const DialDevTools = ({
|
|
|
128
139
|
const dialRotation = useRef(new Animated.Value(0)).current;
|
|
129
140
|
const centerButtonScale = useRef(new Animated.Value(0)).current;
|
|
130
141
|
const iconsProgress = useRef(new Animated.Value(0)).current;
|
|
131
|
-
const glitchOffset = useRef(new Animated.Value(0)).current;
|
|
132
142
|
const pulseScale = useRef(new Animated.Value(1)).current;
|
|
133
143
|
const availableApps = useMemo(() => apps.map(({
|
|
134
144
|
id,
|
|
@@ -142,88 +152,111 @@ export const DialDevTools = ({
|
|
|
142
152
|
description
|
|
143
153
|
})), [apps]);
|
|
144
154
|
|
|
145
|
-
// Subtle animations
|
|
146
|
-
const floatingAnim = useRef(new Animated.Value(0)).current;
|
|
147
|
-
const breathingScale = useRef(new Animated.Value(1)).current;
|
|
148
|
-
const circuitOpacity = useRef(new Animated.Value(0)).current;
|
|
149
|
-
|
|
150
155
|
// Animation tracking refs
|
|
151
|
-
const glitchIntervalRef = useRef(null);
|
|
152
156
|
const pulseAnimationRef = useRef(null);
|
|
153
157
|
|
|
154
|
-
//
|
|
155
|
-
|
|
158
|
+
// Dial-eligible apps: everything except row-only tools. All of them are
|
|
159
|
+
// shown — paginated across pages of MAX_DIAL_SLOTS — so there is no longer
|
|
160
|
+
// a per-tool show/hide setting.
|
|
161
|
+
const dialApps = useMemo(() => apps.filter(a => (a.slot ?? "both") !== "row"), [apps]);
|
|
156
162
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
if (enabledIcons.length >= MAX_DIAL_SLOTS) {
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
enabledIcons.push({
|
|
182
|
-
id: app.id,
|
|
183
|
-
name: app.name,
|
|
184
|
-
// Pass both the pre-rendered icon (for non-function icons) and the component (for dynamic rendering)
|
|
185
|
-
icon: typeof app.icon === "function" ? null // Will be rendered dynamically by DialIcon
|
|
186
|
-
: app.icon,
|
|
187
|
-
// Cast to the expected type - the function signature is compatible at runtime
|
|
188
|
-
iconComponent: typeof app.icon === "function" ? app.icon : undefined,
|
|
189
|
-
color: app.color ?? buoyColors.primary,
|
|
190
|
-
onPress: () => {
|
|
191
|
-
// Call the app's onPress callback if provided, passing actions for toggle tools
|
|
192
|
-
app?.onPress?.(actions);
|
|
163
|
+
// Build a stable IconType for every dial-eligible app, keyed by id.
|
|
164
|
+
const iconsById = useMemo(() => {
|
|
165
|
+
const map = new Map();
|
|
166
|
+
for (const app of dialApps) {
|
|
167
|
+
map.set(app.id, {
|
|
168
|
+
id: app.id,
|
|
169
|
+
name: app.name,
|
|
170
|
+
// Pass both the pre-rendered icon (for non-function icons) and the
|
|
171
|
+
// component (for dynamic rendering).
|
|
172
|
+
icon: typeof app.icon === "function" ? null : app.icon,
|
|
173
|
+
// Cast to the expected type - the signature is compatible at runtime.
|
|
174
|
+
iconComponent: typeof app.icon === "function" ? app.icon : undefined,
|
|
175
|
+
color: app.color ?? buoyColors.primary,
|
|
176
|
+
onPress: () => {
|
|
177
|
+
// Record usage so frequently/recently used tools rank toward page 1.
|
|
178
|
+
void recordToolUsage(app.id);
|
|
179
|
+
|
|
180
|
+
// Call the app's onPress callback if provided, passing actions for
|
|
181
|
+
// toggle tools.
|
|
182
|
+
app?.onPress?.(actions);
|
|
193
183
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
184
|
+
// Only open modal if not a toggle-only tool.
|
|
185
|
+
if (app.launchMode !== "toggle-only") {
|
|
186
|
+
const resolvedIcon = typeof app.icon === "function" ? app.icon({
|
|
187
|
+
slot: "dial",
|
|
188
|
+
size: 20
|
|
189
|
+
}) : app.icon;
|
|
190
|
+
open({
|
|
191
|
+
id: app.id,
|
|
192
|
+
title: app.name,
|
|
193
|
+
component: app.component,
|
|
194
|
+
props: app.props,
|
|
195
|
+
launchMode: app.launchMode ?? "self-modal",
|
|
196
|
+
singleton: app.singleton ?? true,
|
|
197
|
+
icon: resolvedIcon,
|
|
198
|
+
color: app.color
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Close the dial.
|
|
203
|
+
onClose?.();
|
|
210
204
|
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return map;
|
|
208
|
+
}, [dialApps, actions, open, onClose]);
|
|
211
209
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// More tools enabled than can be shown - they will be hidden
|
|
210
|
+
// Snapshot the usage-ranked order when the dial opens. It stays stable while
|
|
211
|
+
// open so icons don't jump positions mid-interaction.
|
|
212
|
+
const [rankedIds, setRankedIds] = useState(() => getRankedToolIds(dialApps.map(a => a.id)));
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
const ids = dialApps.map(a => a.id);
|
|
215
|
+
if (isDialUsageLoaded()) {
|
|
216
|
+
setRankedIds(getRankedToolIds(ids));
|
|
217
|
+
return;
|
|
221
218
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
219
|
+
// Usage data not loaded yet — show default order, then re-rank once ready.
|
|
220
|
+
let cancelled = false;
|
|
221
|
+
loadDialUsage().then(() => {
|
|
222
|
+
if (!cancelled) setRankedIds(getRankedToolIds(ids));
|
|
223
|
+
});
|
|
224
|
+
return () => {
|
|
225
|
+
cancelled = true;
|
|
226
|
+
};
|
|
227
|
+
}, [dialApps]);
|
|
228
|
+
const pageCount = Math.max(1, Math.ceil(rankedIds.length / MAX_DIAL_SLOTS));
|
|
229
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
230
|
+
const safePage = Math.min(currentPage, pageCount - 1);
|
|
231
|
+
|
|
232
|
+
// Every dial-eligible icon, with the page/slot it occupies. The ranking is
|
|
233
|
+
// snapshotted on open, so each icon's page and slot are fixed for the
|
|
234
|
+
// session — which lets us mount all icons once and paginate purely by
|
|
235
|
+
// toggling visibility (no remounts on page change).
|
|
236
|
+
const allDialIcons = useMemo(() => {
|
|
237
|
+
return rankedIds.map(id => iconsById.get(id)).filter(icon => Boolean(icon)).map((icon, i) => ({
|
|
238
|
+
icon,
|
|
239
|
+
page: Math.floor(i / MAX_DIAL_SLOTS),
|
|
240
|
+
slot: i % MAX_DIAL_SLOTS
|
|
241
|
+
}));
|
|
242
|
+
}, [rankedIds, iconsById]);
|
|
243
|
+
|
|
244
|
+
// Empty slot indices for the current page (only the last page can be
|
|
245
|
+
// partial). These are cheap placeholder dots.
|
|
246
|
+
const emptySlots = useMemo(() => {
|
|
247
|
+
const onThisPage = allDialIcons.filter(d => d.page === safePage).length;
|
|
248
|
+
const slots = [];
|
|
249
|
+
for (let s = onThisPage; s < MAX_DIAL_SLOTS; s += 1) slots.push(s);
|
|
250
|
+
return slots;
|
|
251
|
+
}, [allDialIcons, safePage]);
|
|
252
|
+
|
|
253
|
+
// Swap to another page. Every icon is already mounted, so this only toggles
|
|
254
|
+
// which ones are visible — no remount, no re-animation — keeping page
|
|
255
|
+
// changes instant.
|
|
256
|
+
const handlePageChange = next => {
|
|
257
|
+
const clamped = Math.max(0, Math.min(next, pageCount - 1));
|
|
258
|
+
if (clamped !== safePage) setCurrentPage(clamped);
|
|
259
|
+
};
|
|
227
260
|
|
|
228
261
|
// Initialize animations on mount - using shared config from core
|
|
229
262
|
useEffect(() => {
|
|
@@ -244,13 +277,13 @@ export const DialDevTools = ({
|
|
|
244
277
|
}
|
|
245
278
|
},
|
|
246
279
|
centerButton: {
|
|
247
|
-
delay:
|
|
280
|
+
delay: 150,
|
|
248
281
|
damping: 10,
|
|
249
282
|
stiffness: 200
|
|
250
283
|
},
|
|
251
284
|
icons: {
|
|
252
|
-
delay:
|
|
253
|
-
duration:
|
|
285
|
+
delay: 200,
|
|
286
|
+
duration: 400
|
|
254
287
|
},
|
|
255
288
|
circuitTraces: {
|
|
256
289
|
delay: 600,
|
|
@@ -321,81 +354,21 @@ export const DialDevTools = ({
|
|
|
321
354
|
useNativeDriver: true
|
|
322
355
|
})]).start();
|
|
323
356
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
duration: continuous.glitch.stepDuration,
|
|
329
|
-
useNativeDriver: true
|
|
330
|
-
}), Animated.timing(glitchOffset, {
|
|
331
|
-
toValue: -continuous.glitch.offset,
|
|
332
|
-
duration: continuous.glitch.stepDuration,
|
|
333
|
-
useNativeDriver: true
|
|
334
|
-
}), Animated.timing(glitchOffset, {
|
|
335
|
-
toValue: 0,
|
|
336
|
-
duration: continuous.glitch.stepDuration,
|
|
337
|
-
useNativeDriver: true
|
|
338
|
-
})]).start();
|
|
339
|
-
};
|
|
340
|
-
glitchIntervalRef.current = setInterval(glitchAnimation, continuous.glitch.interval);
|
|
341
|
-
|
|
342
|
-
// Pulse animation - using shared config
|
|
343
|
-
const startPulse = () => {
|
|
344
|
-
pulseAnimationRef.current = Animated.loop(Animated.sequence([Animated.timing(pulseScale, {
|
|
345
|
-
toValue: continuous.pulse.maxScale,
|
|
346
|
-
duration: continuous.pulse.duration,
|
|
347
|
-
easing: Easing.inOut(Easing.ease),
|
|
348
|
-
useNativeDriver: true
|
|
349
|
-
}), Animated.timing(pulseScale, {
|
|
350
|
-
toValue: continuous.pulse.minScale,
|
|
351
|
-
duration: continuous.pulse.duration,
|
|
352
|
-
easing: Easing.inOut(Easing.ease),
|
|
353
|
-
useNativeDriver: true
|
|
354
|
-
})]));
|
|
355
|
-
pulseAnimationRef.current.start();
|
|
356
|
-
};
|
|
357
|
-
startPulse();
|
|
358
|
-
|
|
359
|
-
// Subtle floating animation for the dial - using shared config
|
|
360
|
-
Animated.loop(Animated.sequence([Animated.timing(floatingAnim, {
|
|
361
|
-
toValue: continuous.floating.maxY,
|
|
362
|
-
duration: continuous.floating.duration,
|
|
363
|
-
easing: Easing.inOut(Easing.ease),
|
|
364
|
-
useNativeDriver: true
|
|
365
|
-
}), Animated.timing(floatingAnim, {
|
|
366
|
-
toValue: continuous.floating.minY,
|
|
367
|
-
duration: continuous.floating.duration,
|
|
368
|
-
easing: Easing.inOut(Easing.ease),
|
|
369
|
-
useNativeDriver: true
|
|
370
|
-
})])).start();
|
|
371
|
-
|
|
372
|
-
// Gentle breathing effect for center button - using shared config
|
|
373
|
-
Animated.loop(Animated.sequence([Animated.timing(breathingScale, {
|
|
374
|
-
toValue: continuous.breathing.maxScale,
|
|
375
|
-
duration: continuous.breathing.duration,
|
|
357
|
+
// Pulse animation - only continuous effect kept for performance
|
|
358
|
+
pulseAnimationRef.current = Animated.loop(Animated.sequence([Animated.timing(pulseScale, {
|
|
359
|
+
toValue: continuous.pulse.maxScale,
|
|
360
|
+
duration: continuous.pulse.duration,
|
|
376
361
|
easing: Easing.inOut(Easing.ease),
|
|
377
362
|
useNativeDriver: true
|
|
378
|
-
}), Animated.timing(
|
|
379
|
-
toValue: continuous.
|
|
380
|
-
duration: continuous.
|
|
363
|
+
}), Animated.timing(pulseScale, {
|
|
364
|
+
toValue: continuous.pulse.minScale,
|
|
365
|
+
duration: continuous.pulse.duration,
|
|
381
366
|
easing: Easing.inOut(Easing.ease),
|
|
382
367
|
useNativeDriver: true
|
|
383
|
-
})]))
|
|
384
|
-
|
|
385
|
-
// Circuit traces fade in - using shared config
|
|
386
|
-
Animated.timing(circuitOpacity, {
|
|
387
|
-
toValue: 1,
|
|
388
|
-
duration: entrance.circuitTraces.duration,
|
|
389
|
-
delay: entrance.circuitTraces.delay,
|
|
390
|
-
useNativeDriver: true
|
|
391
|
-
}).start();
|
|
368
|
+
})]));
|
|
369
|
+
pulseAnimationRef.current.start();
|
|
392
370
|
return () => {
|
|
393
|
-
|
|
394
|
-
clearInterval(glitchIntervalRef.current);
|
|
395
|
-
}
|
|
396
|
-
if (pulseAnimationRef.current) {
|
|
397
|
-
pulseAnimationRef.current.stop();
|
|
398
|
-
}
|
|
371
|
+
pulseAnimationRef.current?.stop();
|
|
399
372
|
};
|
|
400
373
|
}, []);
|
|
401
374
|
const handleOnboardingDismiss = () => {
|
|
@@ -467,8 +440,8 @@ export const DialDevTools = ({
|
|
|
467
440
|
}
|
|
468
441
|
});
|
|
469
442
|
};
|
|
470
|
-
const handleIconPress =
|
|
471
|
-
setSelectedIcon(
|
|
443
|
+
const handleIconPress = icon => {
|
|
444
|
+
setSelectedIcon(1);
|
|
472
445
|
const interaction = dialAnimationConfig?.interaction ?? {
|
|
473
446
|
iconSelect: {
|
|
474
447
|
pulse: [{
|
|
@@ -500,9 +473,9 @@ export const DialDevTools = ({
|
|
|
500
473
|
|
|
501
474
|
// Trigger action - using shared delay
|
|
502
475
|
setTimeout(() => {
|
|
503
|
-
|
|
476
|
+
icon.onPress();
|
|
504
477
|
// Only close if it's not the WiFi toggle (by id)
|
|
505
|
-
if (
|
|
478
|
+
if (icon.id !== "wifi") {
|
|
506
479
|
handleClose();
|
|
507
480
|
}
|
|
508
481
|
}, interaction.iconSelect.actionDelay);
|
|
@@ -512,14 +485,9 @@ export const DialDevTools = ({
|
|
|
512
485
|
const backdropAnimatedStyle = {
|
|
513
486
|
opacity: backdropOpacity
|
|
514
487
|
};
|
|
515
|
-
const glitchAnimatedStyle = {
|
|
516
|
-
transform: [{
|
|
517
|
-
translateX: glitchOffset
|
|
518
|
-
}]
|
|
519
|
-
};
|
|
520
488
|
const centerButtonAnimatedStyle = {
|
|
521
489
|
transform: [{
|
|
522
|
-
scale:
|
|
490
|
+
scale: centerButtonScale
|
|
523
491
|
}]
|
|
524
492
|
};
|
|
525
493
|
const pulseAnimatedStyle = {
|
|
@@ -537,13 +505,11 @@ export const DialDevTools = ({
|
|
|
537
505
|
onPress: handleClose
|
|
538
506
|
})
|
|
539
507
|
}), /*#__PURE__*/_jsxs(Animated.View, {
|
|
540
|
-
style: [styles.parent, {
|
|
508
|
+
style: [styles.parent, sizeStyles.parent, {
|
|
541
509
|
position: "absolute",
|
|
542
|
-
left: (
|
|
510
|
+
left: (screenWidth - circleSize) / 2,
|
|
543
511
|
bottom: 80,
|
|
544
512
|
transform: [{
|
|
545
|
-
translateY: floatingAnim
|
|
546
|
-
}, {
|
|
547
513
|
scale: dialScale
|
|
548
514
|
}, {
|
|
549
515
|
rotate: dialRotation.interpolate({
|
|
@@ -553,35 +519,47 @@ export const DialDevTools = ({
|
|
|
553
519
|
}]
|
|
554
520
|
}],
|
|
555
521
|
children: [/*#__PURE__*/_jsxs(Animated.View, {
|
|
556
|
-
style: [styles.circle,
|
|
522
|
+
style: [styles.circle, sizeStyles.circle],
|
|
557
523
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
558
|
-
style: styles.gradientBackground,
|
|
524
|
+
style: [styles.gradientBackground, sizeStyles.rounded],
|
|
559
525
|
children: [/*#__PURE__*/_jsx(View, {
|
|
560
|
-
style: styles.gradientLayer1
|
|
526
|
+
style: [styles.gradientLayer1, sizeStyles.rounded]
|
|
561
527
|
}), /*#__PURE__*/_jsx(View, {
|
|
562
|
-
style: styles.gradientLayer2
|
|
528
|
+
style: [styles.gradientLayer2, sizeStyles.rounded]
|
|
563
529
|
}), /*#__PURE__*/_jsx(View, {
|
|
564
|
-
style: styles.gradientLayer3
|
|
530
|
+
style: [styles.gradientLayer3, sizeStyles.rounded]
|
|
565
531
|
}), /*#__PURE__*/_jsx(View, {
|
|
566
532
|
style: styles.gridPattern,
|
|
567
533
|
children: Array.from({
|
|
568
534
|
length: 6
|
|
569
535
|
}).map((_, i) => /*#__PURE__*/_jsx(View, {
|
|
570
|
-
style: [styles.gridLine, {
|
|
536
|
+
style: [styles.gridLine, sizeStyles.gridLine, {
|
|
571
537
|
transform: [{
|
|
572
538
|
rotate: `${i * 60}deg`
|
|
573
539
|
}]
|
|
574
540
|
}]
|
|
575
541
|
}, i))
|
|
576
542
|
})]
|
|
577
|
-
}),
|
|
578
|
-
|
|
543
|
+
}), allDialIcons.filter(({
|
|
544
|
+
page
|
|
545
|
+
}) => page === safePage).map(({
|
|
546
|
+
icon,
|
|
547
|
+
slot
|
|
548
|
+
}) => /*#__PURE__*/_jsx(DialIcon, {
|
|
579
549
|
onPress: handleIconPress,
|
|
580
550
|
iconsProgress: iconsProgress,
|
|
581
551
|
icon: icon,
|
|
582
|
-
index:
|
|
583
|
-
totalIcons:
|
|
584
|
-
|
|
552
|
+
index: slot,
|
|
553
|
+
totalIcons: MAX_DIAL_SLOTS,
|
|
554
|
+
active: true
|
|
555
|
+
}, icon.id ?? `page${safePage}-${slot}`)), emptySlots.map(slot => /*#__PURE__*/_jsx(DialIcon, {
|
|
556
|
+
onPress: handleIconPress,
|
|
557
|
+
iconsProgress: iconsProgress,
|
|
558
|
+
icon: createEmptySlot(slot),
|
|
559
|
+
index: slot,
|
|
560
|
+
totalIcons: MAX_DIAL_SLOTS,
|
|
561
|
+
active: true
|
|
562
|
+
}, `empty-${slot}`))]
|
|
585
563
|
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
586
564
|
style: [styles.buttonContainer, centerButtonAnimatedStyle],
|
|
587
565
|
children: /*#__PURE__*/_jsxs(View, {
|
|
@@ -639,19 +617,32 @@ export const DialDevTools = ({
|
|
|
639
617
|
})]
|
|
640
618
|
})
|
|
641
619
|
})]
|
|
642
|
-
}), /*#__PURE__*/_jsx(
|
|
643
|
-
|
|
620
|
+
}), pageCount > 1 && /*#__PURE__*/_jsx(DialPagination, {
|
|
621
|
+
page: safePage,
|
|
622
|
+
pageCount: pageCount,
|
|
623
|
+
onPrev: () => handlePageChange(safePage - 1),
|
|
624
|
+
onNext: () => handlePageChange(safePage + 1),
|
|
625
|
+
animatedStyle: {
|
|
626
|
+
position: "absolute",
|
|
627
|
+
left: (screenWidth - circleSize) / 2,
|
|
628
|
+
// Circle's bottom edge sits at bottom: 80 -> screenHeight - 80
|
|
629
|
+
// from the top. Place the pager 16px below that edge.
|
|
630
|
+
top: screenHeight - 80 + 16,
|
|
631
|
+
width: circleSize,
|
|
632
|
+
opacity: dialScale,
|
|
633
|
+
transform: [{
|
|
634
|
+
scale: dialScale
|
|
635
|
+
}]
|
|
636
|
+
}
|
|
637
|
+
}), isSettingsModalOpen && /*#__PURE__*/_jsx(DevToolsSettingsModal, {
|
|
638
|
+
visible: true,
|
|
644
639
|
onClose: () => {
|
|
645
640
|
setIsSettingsModalOpen(false);
|
|
646
641
|
refreshSettings(); // Refresh from storage
|
|
647
642
|
},
|
|
648
|
-
onSettingsChange: newSettings => {
|
|
649
|
-
// Immediately update local settings for instant feedback
|
|
650
|
-
setLocalSettings(newSettings);
|
|
651
|
-
},
|
|
652
643
|
availableApps: availableApps
|
|
653
|
-
}), /*#__PURE__*/_jsx(OnboardingTooltip, {
|
|
654
|
-
visible:
|
|
644
|
+
}), showOnboardingTooltip && !isSettingsModalOpen && !onboardingDismissedRef.current && /*#__PURE__*/_jsx(OnboardingTooltip, {
|
|
645
|
+
visible: true,
|
|
655
646
|
onDismiss: handleOnboardingDismiss
|
|
656
647
|
})]
|
|
657
648
|
});
|
|
@@ -665,16 +656,13 @@ const styles = StyleSheet.create({
|
|
|
665
656
|
...StyleSheet.absoluteFillObject,
|
|
666
657
|
backgroundColor: dialColors.dialBackdrop
|
|
667
658
|
},
|
|
659
|
+
// width/height/borderRadius for the circle pieces come from sizeStyles —
|
|
660
|
+
// they track the live window width.
|
|
668
661
|
parent: {
|
|
669
|
-
width: CIRCLE_SIZE,
|
|
670
|
-
height: CIRCLE_SIZE,
|
|
671
662
|
alignItems: "center",
|
|
672
663
|
justifyContent: "center"
|
|
673
664
|
},
|
|
674
665
|
circle: {
|
|
675
|
-
width: CIRCLE_SIZE,
|
|
676
|
-
height: CIRCLE_SIZE,
|
|
677
|
-
borderRadius: CIRCLE_SIZE / 2,
|
|
678
666
|
position: "absolute",
|
|
679
667
|
backgroundColor: "transparent",
|
|
680
668
|
borderWidth: 1,
|
|
@@ -691,7 +679,6 @@ const styles = StyleSheet.create({
|
|
|
691
679
|
gradientBackground: {
|
|
692
680
|
width: "100%",
|
|
693
681
|
height: "100%",
|
|
694
|
-
borderRadius: CIRCLE_SIZE / 2,
|
|
695
682
|
position: "relative",
|
|
696
683
|
backgroundColor: dialColors.dialBackground,
|
|
697
684
|
overflow: "hidden"
|
|
@@ -699,24 +686,21 @@ const styles = StyleSheet.create({
|
|
|
699
686
|
gradientLayer1: {
|
|
700
687
|
...StyleSheet.absoluteFillObject,
|
|
701
688
|
backgroundColor: dialColors.dialGradient1,
|
|
702
|
-
opacity: 0.6
|
|
703
|
-
borderRadius: CIRCLE_SIZE / 2
|
|
689
|
+
opacity: 0.6
|
|
704
690
|
},
|
|
705
691
|
gradientLayer2: {
|
|
706
692
|
...StyleSheet.absoluteFillObject,
|
|
707
693
|
backgroundColor: dialColors.dialGradient2,
|
|
708
694
|
opacity: 0.4,
|
|
709
695
|
top: "30%",
|
|
710
|
-
left: "30%"
|
|
711
|
-
borderRadius: CIRCLE_SIZE / 2
|
|
696
|
+
left: "30%"
|
|
712
697
|
},
|
|
713
698
|
gradientLayer3: {
|
|
714
699
|
...StyleSheet.absoluteFillObject,
|
|
715
700
|
backgroundColor: dialColors.dialGradient3,
|
|
716
701
|
opacity: 0.3,
|
|
717
702
|
top: "50%",
|
|
718
|
-
left: "50%"
|
|
719
|
-
borderRadius: CIRCLE_SIZE / 2
|
|
703
|
+
left: "50%"
|
|
720
704
|
},
|
|
721
705
|
gridPattern: {
|
|
722
706
|
...StyleSheet.absoluteFillObject,
|
|
@@ -725,7 +709,6 @@ const styles = StyleSheet.create({
|
|
|
725
709
|
},
|
|
726
710
|
gridLine: {
|
|
727
711
|
position: "absolute",
|
|
728
|
-
width: CIRCLE_SIZE,
|
|
729
712
|
height: 1,
|
|
730
713
|
backgroundColor: dialColors.dialGridLine
|
|
731
714
|
},
|