yaver-feedback-react-native 0.8.0 → 0.8.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.
- package/README.md +40 -6
- package/dist/AuthOverlay.js +1 -1
- package/dist/FeedbackModal.js +475 -265
- package/dist/P2PClient.d.ts +9 -3
- package/dist/P2PClient.js +30 -2
- package/dist/QuickActionIcon.d.ts +10 -2
- package/dist/QuickActionIcon.js +49 -12
- package/dist/YaverFeedback.d.ts +5 -0
- package/dist/YaverFeedback.js +24 -0
- package/dist/__tests__/P2PClient.test.js +30 -0
- package/dist/__tests__/YaverFeedback.test.js +39 -0
- package/dist/capture.d.ts +11 -0
- package/dist/capture.js +59 -0
- package/dist/index.d.ts +8 -7
- package/dist/index.js +9 -7
- package/dist/preferences.d.ts +11 -0
- package/dist/preferences.js +82 -0
- package/dist/types.d.ts +29 -4
- package/dist/upload.d.ts +1 -0
- package/dist/upload.js +8 -0
- package/package.json +11 -5
- package/src/AuthOverlay.tsx +1 -0
- package/src/FeedbackModal.tsx +561 -319
- package/src/P2PClient.ts +44 -3
- package/src/QuickActionIcon.tsx +76 -13
- package/src/YaverFeedback.ts +31 -0
- package/src/__tests__/P2PClient.test.ts +40 -0
- package/src/__tests__/YaverFeedback.test.ts +42 -0
- package/src/capture.ts +74 -0
- package/src/index.ts +8 -6
- package/src/preferences.ts +96 -0
- package/src/types.ts +29 -4
- package/src/upload.ts +9 -0
package/dist/FeedbackModal.js
CHANGED
|
@@ -41,18 +41,14 @@ const capture_1 = require("./capture");
|
|
|
41
41
|
const upload_1 = require("./upload");
|
|
42
42
|
const AuthOverlay_1 = require("./AuthOverlay");
|
|
43
43
|
const QuickActionIcon_1 = require("./QuickActionIcon");
|
|
44
|
+
const auth_1 = require("./auth");
|
|
45
|
+
const preferences_1 = require("./preferences");
|
|
44
46
|
const FeedbackModal = () => {
|
|
45
47
|
const [visible, setVisible] = (0, react_1.useState)(false);
|
|
46
48
|
const [action, setAction] = (0, react_1.useState)('idle');
|
|
47
49
|
const [error, setError] = (0, react_1.useState)(null);
|
|
48
50
|
const [toast, setToast] = (0, react_1.useState)(null);
|
|
49
51
|
const [progress, setProgress] = (0, react_1.useState)(null);
|
|
50
|
-
const [isRecordingVideo, setIsRecordingVideo] = (0, react_1.useState)(false);
|
|
51
|
-
const [isRecordingVoice, setIsRecordingVoice] = (0, react_1.useState)(false);
|
|
52
|
-
// Cached once on mount — bare-RN apps without expo-av get a clean
|
|
53
|
-
// hidden button instead of a runtime error.
|
|
54
|
-
const voiceSupported = (0, react_1.useRef)((0, capture_1.isVoiceCaptureSupported)()).current;
|
|
55
|
-
const [lastVideo, setLastVideo] = (0, react_1.useState)(null);
|
|
56
52
|
// Tracks whether the user has hidden the QuickActionIcon via its
|
|
57
53
|
// long-press menu. Shake is always available, so the feedback modal
|
|
58
54
|
// is our guaranteed UI for bringing the icon back — we surface a
|
|
@@ -66,7 +62,112 @@ const FeedbackModal = () => {
|
|
|
66
62
|
const [showVibeInput, setShowVibeInput] = (0, react_1.useState)(false);
|
|
67
63
|
const [vibePrompt, setVibePrompt] = (0, react_1.useState)('');
|
|
68
64
|
const [lastVibeTaskId, setLastVibeTaskId] = (0, react_1.useState)(null);
|
|
65
|
+
const [quickIconColorPreset, setQuickIconColorPreset] = (0, react_1.useState)(null);
|
|
66
|
+
const [machineCard, setMachineCard] = (0, react_1.useState)({
|
|
67
|
+
device: null,
|
|
68
|
+
reachable: null,
|
|
69
|
+
loading: false,
|
|
70
|
+
status: 'none',
|
|
71
|
+
title: 'No machine selected',
|
|
72
|
+
detail: 'Pick a remote dev machine before using the feedback actions.',
|
|
73
|
+
});
|
|
69
74
|
const mountedRef = (0, react_1.useRef)(true);
|
|
75
|
+
const loadSelectedMachine = (0, react_1.useCallback)(async () => {
|
|
76
|
+
const cfg = YaverFeedback_1.YaverFeedback.getConfig();
|
|
77
|
+
if (!cfg?.authToken) {
|
|
78
|
+
if (mountedRef.current) {
|
|
79
|
+
setMachineCard({
|
|
80
|
+
device: null,
|
|
81
|
+
reachable: null,
|
|
82
|
+
loading: false,
|
|
83
|
+
status: 'none',
|
|
84
|
+
title: 'Not signed in',
|
|
85
|
+
detail: 'Sign in to pick and monitor a remote dev machine.',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!cfg.preferredDeviceId) {
|
|
91
|
+
if (mountedRef.current) {
|
|
92
|
+
setMachineCard({
|
|
93
|
+
device: null,
|
|
94
|
+
reachable: null,
|
|
95
|
+
loading: false,
|
|
96
|
+
status: 'none',
|
|
97
|
+
title: 'No machine selected',
|
|
98
|
+
detail: 'Choose which machine this SDK should talk to.',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (mountedRef.current) {
|
|
104
|
+
setMachineCard((prev) => ({ ...prev, loading: true }));
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const devices = await (0, auth_1.listReachableDevices)(cfg.authToken);
|
|
108
|
+
const all = [...devices.owned, ...devices.shared];
|
|
109
|
+
const device = all.find((candidate) => candidate.deviceId === cfg.preferredDeviceId) ?? null;
|
|
110
|
+
if (!device) {
|
|
111
|
+
if (mountedRef.current) {
|
|
112
|
+
setMachineCard({
|
|
113
|
+
device: null,
|
|
114
|
+
reachable: null,
|
|
115
|
+
loading: false,
|
|
116
|
+
status: 'offline',
|
|
117
|
+
title: 'Selected machine missing',
|
|
118
|
+
detail: 'The saved machine was not returned by the device list. Re-select it.',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let reachable = null;
|
|
124
|
+
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
125
|
+
if (device.isOnline && !device.needsAuth && client) {
|
|
126
|
+
reachable = await client.health();
|
|
127
|
+
}
|
|
128
|
+
const hostHint = device.hostEmail ? ` via ${device.hostEmail}` : '';
|
|
129
|
+
let status = 'live';
|
|
130
|
+
let detail = `${device.platform}${hostHint}`;
|
|
131
|
+
if (!device.isOnline) {
|
|
132
|
+
status = 'offline';
|
|
133
|
+
detail = 'Machine offline. Start `yaver serve` on the selected machine.';
|
|
134
|
+
}
|
|
135
|
+
else if (device.needsAuth) {
|
|
136
|
+
status = 'attention';
|
|
137
|
+
detail = 'Machine needs pairing again before feedback actions can run.';
|
|
138
|
+
}
|
|
139
|
+
else if (device.runnerDown) {
|
|
140
|
+
status = 'attention';
|
|
141
|
+
detail = 'Machine is online but the coding agent is down.';
|
|
142
|
+
}
|
|
143
|
+
else if (reachable === false) {
|
|
144
|
+
status = 'offline';
|
|
145
|
+
detail = 'Machine selected, but the agent is not responding.';
|
|
146
|
+
}
|
|
147
|
+
if (mountedRef.current) {
|
|
148
|
+
setMachineCard({
|
|
149
|
+
device,
|
|
150
|
+
reachable,
|
|
151
|
+
loading: false,
|
|
152
|
+
status,
|
|
153
|
+
title: device.name || device.deviceId,
|
|
154
|
+
detail,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (mountedRef.current) {
|
|
160
|
+
setMachineCard({
|
|
161
|
+
device: null,
|
|
162
|
+
reachable: null,
|
|
163
|
+
loading: false,
|
|
164
|
+
status: 'offline',
|
|
165
|
+
title: 'Machine status unavailable',
|
|
166
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}, []);
|
|
70
171
|
(0, react_1.useEffect)(() => {
|
|
71
172
|
mountedRef.current = true;
|
|
72
173
|
const sub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
@@ -74,7 +175,10 @@ const FeedbackModal = () => {
|
|
|
74
175
|
setVisible(true);
|
|
75
176
|
setError(null);
|
|
76
177
|
setToast(null);
|
|
178
|
+
setProgress(null);
|
|
77
179
|
setAction('idle');
|
|
180
|
+
setShowVibeInput(false);
|
|
181
|
+
setVibePrompt('');
|
|
78
182
|
// Re-read the "user hid the quick icon" flag on every open so
|
|
79
183
|
// the re-enable row reflects the latest preference (the user
|
|
80
184
|
// might have hidden or shown it between opens).
|
|
@@ -84,6 +188,13 @@ const FeedbackModal = () => {
|
|
|
84
188
|
setQuickIconHidden(v);
|
|
85
189
|
})
|
|
86
190
|
.catch(() => { });
|
|
191
|
+
YaverFeedback_1.YaverFeedback.getQuickIconColorPreset()
|
|
192
|
+
.then((preset) => {
|
|
193
|
+
if (mountedRef.current)
|
|
194
|
+
setQuickIconColorPreset(preset);
|
|
195
|
+
})
|
|
196
|
+
.catch(() => { });
|
|
197
|
+
void loadSelectedMachine();
|
|
87
198
|
}
|
|
88
199
|
});
|
|
89
200
|
// Agent streams build / compile progress through the BlackBox
|
|
@@ -111,7 +222,15 @@ const FeedbackModal = () => {
|
|
|
111
222
|
sub.remove();
|
|
112
223
|
statusSub.remove();
|
|
113
224
|
};
|
|
114
|
-
}, []);
|
|
225
|
+
}, [loadSelectedMachine]);
|
|
226
|
+
(0, react_1.useEffect)(() => {
|
|
227
|
+
if (!visible)
|
|
228
|
+
return;
|
|
229
|
+
const interval = setInterval(() => {
|
|
230
|
+
void loadSelectedMachine();
|
|
231
|
+
}, 5000);
|
|
232
|
+
return () => clearInterval(interval);
|
|
233
|
+
}, [loadSelectedMachine, visible]);
|
|
115
234
|
const closeSoon = (0, react_1.useCallback)((delayMs = 1200) => {
|
|
116
235
|
setTimeout(() => {
|
|
117
236
|
if (mountedRef.current)
|
|
@@ -122,7 +241,10 @@ const FeedbackModal = () => {
|
|
|
122
241
|
setVisible(false);
|
|
123
242
|
setError(null);
|
|
124
243
|
setToast(null);
|
|
244
|
+
setProgress(null);
|
|
125
245
|
setAction('idle');
|
|
246
|
+
setShowVibeInput(false);
|
|
247
|
+
setVibePrompt('');
|
|
126
248
|
}, []);
|
|
127
249
|
// Helper: run a P2P call; on network failure, ask YaverFeedback to
|
|
128
250
|
// re-query Convex for the fresh IP and retry once. Solves the common
|
|
@@ -178,51 +300,93 @@ const FeedbackModal = () => {
|
|
|
178
300
|
setAction('hot-reloading');
|
|
179
301
|
setError(null);
|
|
180
302
|
setProgress(0);
|
|
181
|
-
setToast('
|
|
303
|
+
setToast('Contacting selected machine…');
|
|
182
304
|
try {
|
|
305
|
+
await loadSelectedMachine();
|
|
306
|
+
const selected = await YaverFeedback_1.YaverFeedback.getSelectedRemoteDevice();
|
|
307
|
+
if (!selected) {
|
|
308
|
+
YaverFeedback_1.YaverFeedback.showMachinePicker();
|
|
309
|
+
throw new Error('No machine selected. Pick a machine and try again.');
|
|
310
|
+
}
|
|
311
|
+
if (selected.needsAuth) {
|
|
312
|
+
YaverFeedback_1.YaverFeedback.showMachinePicker();
|
|
313
|
+
throw new Error('Selected machine needs pairing again.');
|
|
314
|
+
}
|
|
315
|
+
if (!selected.isOnline) {
|
|
316
|
+
throw new Error('Selected machine is offline. Start `yaver serve` on it first.');
|
|
317
|
+
}
|
|
183
318
|
// Default mode: bundle. Always rebuilds via the agent regardless
|
|
184
319
|
// of Metro state. P2PClient.reloadApp auto-resolves projectName +
|
|
185
320
|
// bundleId from expo-constants / NativeModules so the agent can
|
|
186
321
|
// map this app to its MobileProject scan entry without needing
|
|
187
322
|
// `yaver dev start` to have been run.
|
|
323
|
+
let ackMessage = 'Reload request acknowledged.';
|
|
188
324
|
await runWithReconnect(async (client) => {
|
|
189
|
-
await client.reloadApp('bundle');
|
|
325
|
+
const ack = await client.reloadApp('bundle');
|
|
326
|
+
ackMessage = ack.message;
|
|
327
|
+
setToast(ack.message);
|
|
328
|
+
setProgress(0.2);
|
|
190
329
|
});
|
|
191
330
|
// We don't auto-close here — the agent's BlackBox status pings
|
|
192
331
|
// will keep the modal updated, and the on-device YaverBundleLoader
|
|
193
332
|
// will reload the JS once the fresh bundle arrives. Modal stays
|
|
194
333
|
// up for a beat so the user sees the final progress state.
|
|
334
|
+
setToast(ackMessage);
|
|
195
335
|
closeSoon(2500);
|
|
196
336
|
}
|
|
197
337
|
catch (err) {
|
|
198
|
-
|
|
338
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
339
|
+
setError(message);
|
|
340
|
+
setToast(message.toLowerCase().indexOf('session expired') >= 0
|
|
341
|
+
? 'Session expired. Sign in again.'
|
|
342
|
+
: 'Hot reload did not start.');
|
|
343
|
+
await loadSelectedMachine();
|
|
199
344
|
setProgress(null);
|
|
200
345
|
}
|
|
201
346
|
finally {
|
|
202
347
|
if (mountedRef.current)
|
|
203
348
|
setAction('idle');
|
|
204
349
|
}
|
|
205
|
-
}, [closeSoon, runWithReconnect]);
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
// Hide the modal first so the screenshot captures the actual screen
|
|
209
|
-
// (the bug) — not the modal card. Await a short animation delay,
|
|
210
|
-
// snapshot, upload the feedback bundle with any buffered errors, then
|
|
211
|
-
// kick `/feedback/{id}/fix` to create the repair task.
|
|
212
|
-
const handleScreenshotAndFix = (0, react_1.useCallback)(async () => {
|
|
350
|
+
}, [closeSoon, loadSelectedMachine, runWithReconnect]);
|
|
351
|
+
const uploadBundleWithOptionalFix = (0, react_1.useCallback)(async (bundle, fixOnUpload, successToast, failureToast) => {
|
|
213
352
|
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
214
353
|
const config = YaverFeedback_1.YaverFeedback.getConfig();
|
|
215
354
|
if (!client || !config?.agentUrl) {
|
|
216
355
|
setError('Not connected to the agent yet.');
|
|
217
356
|
return;
|
|
218
357
|
}
|
|
358
|
+
try {
|
|
359
|
+
const uploaded = await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle);
|
|
360
|
+
// The agent returns the new report id as `id` (see
|
|
361
|
+
// feedback_http.go::ReceiveFeedback). Trigger the fix loop if we got
|
|
362
|
+
// one back; otherwise just ack the upload.
|
|
363
|
+
const reportId = uploaded?.id ??
|
|
364
|
+
uploaded?.reportId;
|
|
365
|
+
if (reportId && fixOnUpload) {
|
|
366
|
+
try {
|
|
367
|
+
await client.triggerFix(reportId);
|
|
368
|
+
setToast(successToast);
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
setToast(failureToast ?? 'Report uploaded — fix trigger failed');
|
|
372
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
setToast(successToast);
|
|
377
|
+
}
|
|
378
|
+
closeSoon(1400);
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
382
|
+
}
|
|
383
|
+
}, [closeSoon]);
|
|
384
|
+
const handleScreenshotAndFix = (0, react_1.useCallback)(async () => {
|
|
219
385
|
setAction('capturing');
|
|
220
386
|
setError(null);
|
|
221
|
-
// Step 1: Hide the modal so the screenshot contains the real screen.
|
|
222
387
|
setVisible(false);
|
|
223
|
-
// Wait out the slide-down animation on both platforms.
|
|
224
388
|
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
225
|
-
let path
|
|
389
|
+
let path;
|
|
226
390
|
try {
|
|
227
391
|
path = await (0, capture_1.captureScreenshot)();
|
|
228
392
|
}
|
|
@@ -232,7 +396,6 @@ const FeedbackModal = () => {
|
|
|
232
396
|
setAction('idle');
|
|
233
397
|
return;
|
|
234
398
|
}
|
|
235
|
-
// Step 2: Re-show the modal for progress + ack.
|
|
236
399
|
setVisible(true);
|
|
237
400
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
238
401
|
try {
|
|
@@ -256,35 +419,18 @@ const FeedbackModal = () => {
|
|
|
256
419
|
screenshots: [path],
|
|
257
420
|
errors: capturedErrors.length > 0 ? capturedErrors : undefined,
|
|
258
421
|
};
|
|
259
|
-
|
|
260
|
-
// The agent returns the new report id as `id` (see
|
|
261
|
-
// feedback_http.go::ReceiveFeedback). Trigger the fix loop if we got
|
|
262
|
-
// one back; otherwise just ack the upload.
|
|
263
|
-
const reportId = uploaded?.id ??
|
|
264
|
-
uploaded?.reportId;
|
|
265
|
-
if (reportId) {
|
|
266
|
-
try {
|
|
267
|
-
await client.triggerFix(reportId);
|
|
268
|
-
setToast('Fix task started');
|
|
269
|
-
}
|
|
270
|
-
catch (err) {
|
|
271
|
-
setToast('Report uploaded — fix trigger failed');
|
|
272
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
setToast('Report uploaded');
|
|
277
|
-
}
|
|
278
|
-
closeSoon(1400);
|
|
279
|
-
}
|
|
280
|
-
catch (err) {
|
|
281
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
422
|
+
await uploadBundleWithOptionalFix(bundle, true, 'Fix task started');
|
|
282
423
|
}
|
|
283
424
|
finally {
|
|
284
425
|
if (mountedRef.current)
|
|
285
426
|
setAction('idle');
|
|
286
427
|
}
|
|
287
|
-
}, [
|
|
428
|
+
}, [uploadBundleWithOptionalFix]);
|
|
429
|
+
/*
|
|
430
|
+
const handleFileUpload = useCallback(async () => {
|
|
431
|
+
...
|
|
432
|
+
}, [uploadBundleWithOptionalFix]);
|
|
433
|
+
*/
|
|
288
434
|
// ─── 3. Vibing ─────────────────────────────────────────────────────
|
|
289
435
|
// First tap expands the input; second submit fires the actual
|
|
290
436
|
// /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
|
|
@@ -337,184 +483,23 @@ const FeedbackModal = () => {
|
|
|
337
483
|
setAction('idle');
|
|
338
484
|
}
|
|
339
485
|
}, [vibePrompt]);
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const result = await (0, capture_1.stopVideoRecording)();
|
|
346
|
-
if (mountedRef.current) {
|
|
347
|
-
setIsRecordingVideo(false);
|
|
348
|
-
setLastVideo(result);
|
|
349
|
-
setToast(`Recording stopped — ${Math.round(result.duration)}s`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch (err) {
|
|
353
|
-
setIsRecordingVideo(false);
|
|
354
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
try {
|
|
359
|
-
await (0, capture_1.startVideoRecording)();
|
|
360
|
-
if (mountedRef.current) {
|
|
361
|
-
setIsRecordingVideo(true);
|
|
362
|
-
setToast('Recording…');
|
|
363
|
-
setLastVideo(null);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
catch (err) {
|
|
367
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}, [isRecordingVideo]);
|
|
371
|
-
// ─── Voice note: record → transcribe → send as feedback ───────────
|
|
372
|
-
// Tap once to start; tap again to stop. On stop: upload the audio
|
|
373
|
-
// to the agent's /voice/transcribe (which routes through whichever
|
|
374
|
-
// STT provider is configured — Whisper / Deepgram / OpenAI / etc.),
|
|
375
|
-
// then file the transcript as a bug report. The audio file itself
|
|
376
|
-
// is also attached to the feedback bundle so the agent can re-play
|
|
377
|
-
// it if the transcript is wrong.
|
|
378
|
-
const handleToggleVoice = (0, react_1.useCallback)(async () => {
|
|
379
|
-
setError(null);
|
|
380
|
-
if (!isRecordingVoice) {
|
|
381
|
-
try {
|
|
382
|
-
await (0, capture_1.startAudioRecording)();
|
|
383
|
-
if (mountedRef.current) {
|
|
384
|
-
setIsRecordingVoice(true);
|
|
385
|
-
setAction('recording-voice');
|
|
386
|
-
setToast('Recording voice note…');
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
catch (err) {
|
|
390
|
-
setIsRecordingVoice(false);
|
|
391
|
-
setAction('idle');
|
|
392
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
393
|
-
}
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
// Stopping → transcribe → send.
|
|
397
|
-
try {
|
|
398
|
-
const audio = await (0, capture_1.stopAudioRecording)();
|
|
399
|
-
setIsRecordingVoice(false);
|
|
400
|
-
if (!audio) {
|
|
401
|
-
setAction('idle');
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
setAction('transcribing-voice');
|
|
405
|
-
setToast('Transcribing…');
|
|
406
|
-
const config = YaverFeedback_1.YaverFeedback.getConfig();
|
|
407
|
-
if (!config?.agentUrl) {
|
|
408
|
-
setError('Not connected to the agent yet.');
|
|
409
|
-
setAction('idle');
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
let transcript = '';
|
|
413
|
-
try {
|
|
414
|
-
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
415
|
-
if (client) {
|
|
416
|
-
const res = await client.transcribeVoice(audio.path);
|
|
417
|
-
transcript = res.text ?? '';
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch {
|
|
421
|
-
// Transcription can fail (no STT provider configured on the
|
|
422
|
-
// agent, network blip, etc.). Don't block the flow — ship
|
|
423
|
-
// the raw audio file with a "[no transcript]" note so the
|
|
424
|
-
// agent + human reviewer can still play it back.
|
|
425
|
-
}
|
|
426
|
-
const { Dimensions } = require('react-native');
|
|
427
|
-
const { width, height } = Dimensions.get('window');
|
|
428
|
-
const deviceInfo = {
|
|
429
|
-
platform: react_native_1.Platform.OS,
|
|
430
|
-
osVersion: String(react_native_1.Platform.Version),
|
|
431
|
-
model: react_native_1.Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
432
|
-
screenWidth: width,
|
|
433
|
-
screenHeight: height,
|
|
434
|
-
};
|
|
435
|
-
const bundle = {
|
|
436
|
-
metadata: {
|
|
437
|
-
timestamp: new Date().toISOString(),
|
|
438
|
-
device: deviceInfo,
|
|
439
|
-
app: {},
|
|
440
|
-
userNote: transcript.length > 0
|
|
441
|
-
? `[Voice note] ${transcript}`
|
|
442
|
-
: `[Voice note · ${Math.round(audio.duration)}s — transcription unavailable]`,
|
|
443
|
-
},
|
|
444
|
-
screenshots: [],
|
|
445
|
-
audio: audio.path,
|
|
446
|
-
errors: YaverFeedback_1.YaverFeedback.getCapturedErrors().length
|
|
447
|
-
? YaverFeedback_1.YaverFeedback.getCapturedErrors()
|
|
448
|
-
: undefined,
|
|
449
|
-
};
|
|
450
|
-
await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle);
|
|
451
|
-
setToast(transcript ? `Sent: "${transcript.slice(0, 60)}${transcript.length > 60 ? '…' : ''}"` : 'Voice note sent');
|
|
452
|
-
closeSoon(1800);
|
|
453
|
-
}
|
|
454
|
-
catch (err) {
|
|
455
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
456
|
-
}
|
|
457
|
-
finally {
|
|
458
|
-
if (mountedRef.current)
|
|
459
|
-
setAction('idle');
|
|
460
|
-
}
|
|
461
|
-
}, [isRecordingVoice, closeSoon]);
|
|
462
|
-
// ─── 5. Send the last recorded video ───────────────────────────────
|
|
463
|
-
const handleSendVideo = (0, react_1.useCallback)(async () => {
|
|
464
|
-
const config = YaverFeedback_1.YaverFeedback.getConfig();
|
|
465
|
-
if (!config?.agentUrl) {
|
|
466
|
-
setError('Not connected to the agent yet.');
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (!lastVideo) {
|
|
470
|
-
setError('No video recorded yet.');
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
setAction('sending-video');
|
|
474
|
-
setError(null);
|
|
475
|
-
try {
|
|
476
|
-
const { Dimensions } = require('react-native');
|
|
477
|
-
const { width, height } = Dimensions.get('window');
|
|
478
|
-
const deviceInfo = {
|
|
479
|
-
platform: react_native_1.Platform.OS,
|
|
480
|
-
osVersion: String(react_native_1.Platform.Version),
|
|
481
|
-
model: react_native_1.Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
482
|
-
screenWidth: width,
|
|
483
|
-
screenHeight: height,
|
|
484
|
-
};
|
|
485
|
-
const bundle = {
|
|
486
|
-
metadata: {
|
|
487
|
-
timestamp: new Date().toISOString(),
|
|
488
|
-
device: deviceInfo,
|
|
489
|
-
app: {},
|
|
490
|
-
userNote: '[Screen recording]',
|
|
491
|
-
},
|
|
492
|
-
screenshots: [],
|
|
493
|
-
video: lastVideo.path,
|
|
494
|
-
errors: YaverFeedback_1.YaverFeedback.getCapturedErrors().length
|
|
495
|
-
? YaverFeedback_1.YaverFeedback.getCapturedErrors()
|
|
496
|
-
: undefined,
|
|
497
|
-
};
|
|
498
|
-
await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle);
|
|
499
|
-
setToast('Video sent');
|
|
500
|
-
setLastVideo(null);
|
|
501
|
-
closeSoon(1200);
|
|
502
|
-
}
|
|
503
|
-
catch (err) {
|
|
504
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
505
|
-
}
|
|
506
|
-
finally {
|
|
507
|
-
if (mountedRef.current)
|
|
508
|
-
setAction('idle');
|
|
509
|
-
}
|
|
510
|
-
}, [lastVideo, closeSoon]);
|
|
486
|
+
/*
|
|
487
|
+
const handleScreenRecording = useCallback(async () => {
|
|
488
|
+
...
|
|
489
|
+
}, [closeSoon, isRecordingVideo, lastVideo]);
|
|
490
|
+
*/
|
|
511
491
|
const busy = action !== 'idle';
|
|
512
492
|
return (<>
|
|
513
493
|
<AuthOverlay_1.AuthOverlay />
|
|
514
494
|
<QuickActionIcon_1.QuickActionIcon />
|
|
515
495
|
{visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
|
|
516
496
|
<react_native_1.Pressable style={styles.overlay} onPress={handleClose}>
|
|
517
|
-
<react_native_1.
|
|
497
|
+
<react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.kbAvoider} pointerEvents="box-none">
|
|
498
|
+
<react_native_1.Pressable style={styles.modal} onPress={(e) => {
|
|
499
|
+
e.stopPropagation();
|
|
500
|
+
react_native_1.Keyboard.dismiss();
|
|
501
|
+
}}>
|
|
502
|
+
<react_native_1.ScrollView style={styles.scroll} contentContainerStyle={styles.scrollContent} keyboardShouldPersistTaps="handled">
|
|
518
503
|
<react_native_1.View style={styles.header}>
|
|
519
504
|
<react_native_1.Text style={styles.title}>Send Feedback</react_native_1.Text>
|
|
520
505
|
<react_native_1.Pressable onPress={handleClose} hitSlop={12} style={styles.closeBtn} accessibilityRole="button" accessibilityLabel="Close">
|
|
@@ -522,14 +507,92 @@ const FeedbackModal = () => {
|
|
|
522
507
|
</react_native_1.Pressable>
|
|
523
508
|
</react_native_1.View>
|
|
524
509
|
|
|
510
|
+
<react_native_1.Pressable onPress={() => {
|
|
511
|
+
if (!YaverFeedback_1.YaverFeedback.isAuthed()) {
|
|
512
|
+
YaverFeedback_1.YaverFeedback.showLogin();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
YaverFeedback_1.YaverFeedback.showMachinePicker();
|
|
516
|
+
}} style={[
|
|
517
|
+
styles.machineCard,
|
|
518
|
+
machineCard.status === 'live' && styles.machineCardLive,
|
|
519
|
+
machineCard.status === 'attention' && styles.machineCardAttention,
|
|
520
|
+
machineCard.status === 'offline' && styles.machineCardOffline,
|
|
521
|
+
]}>
|
|
522
|
+
<react_native_1.View style={styles.machineHeader}>
|
|
523
|
+
<react_native_1.View style={styles.machineTitleWrap}>
|
|
524
|
+
<react_native_1.View style={[
|
|
525
|
+
styles.machineDot,
|
|
526
|
+
machineCard.status === 'live' && styles.machineDotLive,
|
|
527
|
+
machineCard.status === 'attention' && styles.machineDotAttention,
|
|
528
|
+
machineCard.status === 'offline' && styles.machineDotOffline,
|
|
529
|
+
]}/>
|
|
530
|
+
<react_native_1.Text style={styles.machineLabel}>Selected Machine</react_native_1.Text>
|
|
531
|
+
</react_native_1.View>
|
|
532
|
+
<react_native_1.Text style={styles.machineAction}>
|
|
533
|
+
{machineCard.loading ? 'Refreshing…' : 'Change'}
|
|
534
|
+
</react_native_1.Text>
|
|
535
|
+
</react_native_1.View>
|
|
536
|
+
<react_native_1.Text style={styles.machineName}>
|
|
537
|
+
{machineCard.loading ? 'Checking machine…' : machineCard.title}
|
|
538
|
+
</react_native_1.Text>
|
|
539
|
+
<react_native_1.Text style={styles.machineMeta}>{machineCard.detail}</react_native_1.Text>
|
|
540
|
+
</react_native_1.Pressable>
|
|
541
|
+
|
|
542
|
+
{quickIconHidden && (<react_native_1.View style={styles.quickIconNote}>
|
|
543
|
+
<react_native_1.Text style={styles.quickIconNoteText}>
|
|
544
|
+
Quick access icon is hidden. Shake the phone if you want feedback back fast.
|
|
545
|
+
</react_native_1.Text>
|
|
546
|
+
<react_native_1.Pressable onPress={() => {
|
|
547
|
+
void YaverFeedback_1.YaverFeedback.setQuickIconVisible(true);
|
|
548
|
+
setQuickIconHidden(false);
|
|
549
|
+
}} style={({ pressed }) => [
|
|
550
|
+
styles.quickIconToggle,
|
|
551
|
+
pressed && styles.buttonPressed,
|
|
552
|
+
]}>
|
|
553
|
+
<react_native_1.Text style={styles.quickIconToggleText}>Show quick icon again</react_native_1.Text>
|
|
554
|
+
</react_native_1.Pressable>
|
|
555
|
+
</react_native_1.View>)}
|
|
556
|
+
|
|
557
|
+
<react_native_1.View style={styles.iconSelector}>
|
|
558
|
+
<react_native_1.Text style={styles.iconSelectorTitle}>Quick Icon Color</react_native_1.Text>
|
|
559
|
+
<react_native_1.Text style={styles.iconSelectorText}>
|
|
560
|
+
Pick a runtime color so the floating y icon does not overlap with your app UI.
|
|
561
|
+
</react_native_1.Text>
|
|
562
|
+
<react_native_1.View style={styles.iconSelectorGrid}>
|
|
563
|
+
{Object.entries(preferences_1.QUICK_ICON_COLOR_PRESETS).map(([preset, colors]) => {
|
|
564
|
+
const selected = quickIconColorPreset === preset;
|
|
565
|
+
return (<react_native_1.Pressable key={preset} onPress={() => {
|
|
566
|
+
setQuickIconColorPreset(preset);
|
|
567
|
+
void YaverFeedback_1.YaverFeedback.setQuickIconColorPreset(preset);
|
|
568
|
+
}} style={[
|
|
569
|
+
styles.iconOption,
|
|
570
|
+
selected && styles.iconOptionSelected,
|
|
571
|
+
]}>
|
|
572
|
+
<react_native_1.View style={[
|
|
573
|
+
styles.iconOptionCircle,
|
|
574
|
+
{
|
|
575
|
+
backgroundColor: colors.backgroundColor,
|
|
576
|
+
borderColor: colors.borderColor,
|
|
577
|
+
shadowColor: colors.shadowColor,
|
|
578
|
+
},
|
|
579
|
+
]}>
|
|
580
|
+
<react_native_1.Text style={[
|
|
581
|
+
styles.iconOptionLabel,
|
|
582
|
+
{ color: colors.foregroundColor },
|
|
583
|
+
]}>
|
|
584
|
+
y
|
|
585
|
+
</react_native_1.Text>
|
|
586
|
+
</react_native_1.View>
|
|
587
|
+
<react_native_1.Text style={styles.iconOptionText}>{colors.label}</react_native_1.Text>
|
|
588
|
+
</react_native_1.Pressable>);
|
|
589
|
+
})}
|
|
590
|
+
</react_native_1.View>
|
|
591
|
+
</react_native_1.View>
|
|
592
|
+
|
|
525
593
|
{/* 1. Hot Reload — the common path */}
|
|
526
594
|
<ActionRow label={action === 'hot-reloading' ? 'Reloading…' : 'Hot Reload'} tint="#fbbf24" onPress={handleHotReload} disabled={busy} busy={action === 'hot-reloading'}/>
|
|
527
595
|
|
|
528
|
-
{/* 2. Screenshot + Fix — for bug fixes */}
|
|
529
|
-
<ActionRow label={action === 'capturing'
|
|
530
|
-
? 'Capturing…'
|
|
531
|
-
: 'Screenshot & Fix'} tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy} busy={action === 'capturing'}/>
|
|
532
|
-
|
|
533
596
|
{/* 3. Vibing — expands to an input box on first tap
|
|
534
597
|
so the user says WHAT they want to vibe on, just
|
|
535
598
|
like the Yaver mobile app's Vibing tab. Second
|
|
@@ -555,24 +618,8 @@ const FeedbackModal = () => {
|
|
|
555
618
|
Last vibing task: {lastVibeTaskId.slice(0, 12)}…
|
|
556
619
|
</react_native_1.Text>)}
|
|
557
620
|
|
|
558
|
-
{/*
|
|
559
|
-
|
|
560
|
-
the agent and files as a feedback report. */}
|
|
561
|
-
{voiceSupported && (<ActionRow label={action === 'transcribing-voice'
|
|
562
|
-
? 'Transcribing…'
|
|
563
|
-
: isRecordingVoice
|
|
564
|
-
? 'Stop & Send Voice'
|
|
565
|
-
: 'Voice Note'} tint={isRecordingVoice ? '#ef4444' : '#f472b6'} onPress={handleToggleVoice} disabled={busy && action !== 'recording-voice' && action !== 'idle'} busy={action === 'transcribing-voice'}/>)}
|
|
566
|
-
|
|
567
|
-
{/* 4. Start/Stop Recording */}
|
|
568
|
-
<ActionRow label={isRecordingVideo ? 'Stop Recording' : 'Start Recording'} tint={isRecordingVideo ? '#ef4444' : '#60a5fa'} onPress={handleToggleRecording} disabled={busy && action !== 'idle' && !isRecordingVideo}/>
|
|
569
|
-
|
|
570
|
-
{/* 5. Send Video (only tappable when a clip is ready) */}
|
|
571
|
-
<ActionRow label={action === 'sending-video'
|
|
572
|
-
? 'Sending…'
|
|
573
|
-
: lastVideo
|
|
574
|
-
? `Send Video · ${Math.round(lastVideo.duration)}s`
|
|
575
|
-
: 'Send Video'} tint="#a78bfa" onPress={handleSendVideo} disabled={busy || !lastVideo} busy={action === 'sending-video'}/>
|
|
621
|
+
{/* Screenshot & Fix */}
|
|
622
|
+
<ActionRow label={action === 'capturing' ? 'Working…' : 'Screenshot & Fix'} tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy} busy={action === 'capturing'}/>
|
|
576
623
|
|
|
577
624
|
{progress !== null && (<react_native_1.View style={styles.progressTrack}>
|
|
578
625
|
<react_native_1.View style={[
|
|
@@ -583,27 +630,15 @@ const FeedbackModal = () => {
|
|
|
583
630
|
{toast && <react_native_1.Text style={styles.toast}>{toast}</react_native_1.Text>}
|
|
584
631
|
{error && <react_native_1.Text style={styles.error}>{error}</react_native_1.Text>}
|
|
585
632
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
exposed their own settings UI. */}
|
|
592
|
-
<react_native_1.Pressable onPress={async () => {
|
|
593
|
-
const next = !quickIconHidden;
|
|
594
|
-
setQuickIconHidden(next);
|
|
595
|
-
await YaverFeedback_1.YaverFeedback.setQuickIconVisible(!next);
|
|
596
|
-
}} style={({ pressed }) => [
|
|
597
|
-
styles.quickIconToggle,
|
|
598
|
-
pressed && { opacity: 0.7 },
|
|
599
|
-
]} accessibilityRole="button" accessibilityLabel={quickIconHidden ? 'Show quick icon' : 'Hide quick icon'}>
|
|
600
|
-
<react_native_1.Text style={styles.quickIconToggleText}>
|
|
601
|
-
{quickIconHidden
|
|
602
|
-
? '◯ Show quick-access icon'
|
|
603
|
-
: '● Hide quick-access icon'}
|
|
604
|
-
</react_native_1.Text>
|
|
633
|
+
<react_native_1.Pressable onPress={handleClose} style={({ pressed }) => [
|
|
634
|
+
styles.cancelBtn,
|
|
635
|
+
pressed && styles.buttonPressed,
|
|
636
|
+
]} accessibilityRole="button" accessibilityLabel="Cancel">
|
|
637
|
+
<react_native_1.Text style={styles.cancelBtnText}>Cancel</react_native_1.Text>
|
|
605
638
|
</react_native_1.Pressable>
|
|
639
|
+
</react_native_1.ScrollView>
|
|
606
640
|
</react_native_1.Pressable>
|
|
641
|
+
</react_native_1.KeyboardAvoidingView>
|
|
607
642
|
</react_native_1.Pressable>
|
|
608
643
|
</react_native_1.Modal>)}
|
|
609
644
|
</>);
|
|
@@ -676,6 +711,10 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
676
711
|
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
677
712
|
justifyContent: 'flex-end',
|
|
678
713
|
},
|
|
714
|
+
kbAvoider: {
|
|
715
|
+
width: '100%',
|
|
716
|
+
justifyContent: 'flex-end',
|
|
717
|
+
},
|
|
679
718
|
modal: {
|
|
680
719
|
backgroundColor: '#141422',
|
|
681
720
|
borderTopLeftRadius: 22,
|
|
@@ -683,6 +722,14 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
683
722
|
padding: 22,
|
|
684
723
|
paddingBottom: 36,
|
|
685
724
|
gap: 12,
|
|
725
|
+
maxHeight: '92%',
|
|
726
|
+
},
|
|
727
|
+
scroll: {
|
|
728
|
+
maxHeight: '100%',
|
|
729
|
+
},
|
|
730
|
+
scrollContent: {
|
|
731
|
+
gap: 12,
|
|
732
|
+
paddingBottom: 8,
|
|
686
733
|
},
|
|
687
734
|
header: {
|
|
688
735
|
flexDirection: 'row',
|
|
@@ -723,6 +770,77 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
723
770
|
fontSize: 15,
|
|
724
771
|
fontWeight: '700',
|
|
725
772
|
},
|
|
773
|
+
machineCard: {
|
|
774
|
+
borderRadius: 14,
|
|
775
|
+
borderWidth: 1,
|
|
776
|
+
padding: 14,
|
|
777
|
+
backgroundColor: 'rgba(255,255,255,0.04)',
|
|
778
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
779
|
+
},
|
|
780
|
+
machineCardLive: {
|
|
781
|
+
backgroundColor: 'rgba(34,197,94,0.10)',
|
|
782
|
+
borderColor: 'rgba(34,197,94,0.35)',
|
|
783
|
+
},
|
|
784
|
+
machineCardAttention: {
|
|
785
|
+
backgroundColor: 'rgba(245,158,11,0.10)',
|
|
786
|
+
borderColor: 'rgba(245,158,11,0.35)',
|
|
787
|
+
},
|
|
788
|
+
machineCardOffline: {
|
|
789
|
+
backgroundColor: 'rgba(239,68,68,0.10)',
|
|
790
|
+
borderColor: 'rgba(239,68,68,0.35)',
|
|
791
|
+
},
|
|
792
|
+
machineHeader: {
|
|
793
|
+
flexDirection: 'row',
|
|
794
|
+
alignItems: 'center',
|
|
795
|
+
justifyContent: 'space-between',
|
|
796
|
+
marginBottom: 6,
|
|
797
|
+
},
|
|
798
|
+
machineTitleWrap: {
|
|
799
|
+
flexDirection: 'row',
|
|
800
|
+
alignItems: 'center',
|
|
801
|
+
gap: 8,
|
|
802
|
+
},
|
|
803
|
+
machineDot: {
|
|
804
|
+
width: 10,
|
|
805
|
+
height: 10,
|
|
806
|
+
borderRadius: 5,
|
|
807
|
+
backgroundColor: '#6b7280',
|
|
808
|
+
},
|
|
809
|
+
machineDotLive: {
|
|
810
|
+
backgroundColor: '#22c55e',
|
|
811
|
+
},
|
|
812
|
+
machineDotAttention: {
|
|
813
|
+
backgroundColor: '#f59e0b',
|
|
814
|
+
},
|
|
815
|
+
machineDotOffline: {
|
|
816
|
+
backgroundColor: '#ef4444',
|
|
817
|
+
},
|
|
818
|
+
machineLabel: {
|
|
819
|
+
color: '#cbd5e1',
|
|
820
|
+
fontSize: 12,
|
|
821
|
+
fontWeight: '700',
|
|
822
|
+
textTransform: 'uppercase',
|
|
823
|
+
letterSpacing: 0.8,
|
|
824
|
+
},
|
|
825
|
+
machineAction: {
|
|
826
|
+
color: '#a5b4fc',
|
|
827
|
+
fontSize: 12,
|
|
828
|
+
fontWeight: '700',
|
|
829
|
+
},
|
|
830
|
+
machineName: {
|
|
831
|
+
color: '#fff',
|
|
832
|
+
fontSize: 16,
|
|
833
|
+
fontWeight: '700',
|
|
834
|
+
},
|
|
835
|
+
machineMeta: {
|
|
836
|
+
color: '#cbd5e1',
|
|
837
|
+
fontSize: 12,
|
|
838
|
+
marginTop: 4,
|
|
839
|
+
lineHeight: 17,
|
|
840
|
+
},
|
|
841
|
+
captureChoices: {
|
|
842
|
+
gap: 10,
|
|
843
|
+
},
|
|
726
844
|
progressTrack: {
|
|
727
845
|
height: 6,
|
|
728
846
|
borderRadius: 3,
|
|
@@ -748,14 +866,106 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
748
866
|
marginTop: 4,
|
|
749
867
|
},
|
|
750
868
|
quickIconToggle: {
|
|
751
|
-
marginTop:
|
|
752
|
-
alignSelf: '
|
|
869
|
+
marginTop: 6,
|
|
870
|
+
alignSelf: 'flex-start',
|
|
753
871
|
paddingVertical: 6,
|
|
754
|
-
paddingHorizontal:
|
|
872
|
+
paddingHorizontal: 10,
|
|
873
|
+
borderRadius: 10,
|
|
874
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
755
875
|
},
|
|
756
876
|
quickIconToggleText: {
|
|
757
|
-
color: '#
|
|
877
|
+
color: '#cbd5e1',
|
|
878
|
+
fontSize: 12,
|
|
879
|
+
fontWeight: '700',
|
|
880
|
+
},
|
|
881
|
+
quickIconNote: {
|
|
882
|
+
borderRadius: 12,
|
|
883
|
+
borderWidth: 1,
|
|
884
|
+
borderColor: 'rgba(251,191,36,0.28)',
|
|
885
|
+
backgroundColor: 'rgba(251,191,36,0.08)',
|
|
886
|
+
padding: 12,
|
|
887
|
+
},
|
|
888
|
+
quickIconNoteText: {
|
|
889
|
+
color: '#fde68a',
|
|
890
|
+
fontSize: 12,
|
|
891
|
+
lineHeight: 17,
|
|
892
|
+
},
|
|
893
|
+
iconSelector: {
|
|
894
|
+
borderRadius: 12,
|
|
895
|
+
borderWidth: 1,
|
|
896
|
+
borderColor: 'rgba(255,255,255,0.09)',
|
|
897
|
+
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
898
|
+
padding: 12,
|
|
899
|
+
gap: 10,
|
|
900
|
+
},
|
|
901
|
+
iconSelectorTitle: {
|
|
902
|
+
color: '#f8fafc',
|
|
903
|
+
fontSize: 13,
|
|
904
|
+
fontWeight: '700',
|
|
905
|
+
},
|
|
906
|
+
iconSelectorText: {
|
|
907
|
+
color: '#cbd5e1',
|
|
758
908
|
fontSize: 12,
|
|
759
|
-
|
|
909
|
+
lineHeight: 17,
|
|
910
|
+
},
|
|
911
|
+
iconSelectorGrid: {
|
|
912
|
+
flexDirection: 'row',
|
|
913
|
+
flexWrap: 'wrap',
|
|
914
|
+
gap: 10,
|
|
915
|
+
},
|
|
916
|
+
iconOption: {
|
|
917
|
+
width: '31%',
|
|
918
|
+
minWidth: 84,
|
|
919
|
+
borderRadius: 12,
|
|
920
|
+
borderWidth: 1,
|
|
921
|
+
borderColor: 'rgba(255,255,255,0.08)',
|
|
922
|
+
backgroundColor: 'rgba(255,255,255,0.02)',
|
|
923
|
+
paddingVertical: 10,
|
|
924
|
+
paddingHorizontal: 8,
|
|
925
|
+
alignItems: 'center',
|
|
926
|
+
gap: 8,
|
|
927
|
+
},
|
|
928
|
+
iconOptionSelected: {
|
|
929
|
+
borderColor: 'rgba(129,140,248,0.72)',
|
|
930
|
+
backgroundColor: 'rgba(129,140,248,0.12)',
|
|
931
|
+
},
|
|
932
|
+
iconOptionCircle: {
|
|
933
|
+
width: 38,
|
|
934
|
+
height: 38,
|
|
935
|
+
borderRadius: 19,
|
|
936
|
+
alignItems: 'center',
|
|
937
|
+
justifyContent: 'center',
|
|
938
|
+
borderWidth: 2,
|
|
939
|
+
shadowOffset: { width: 0, height: 2 },
|
|
940
|
+
shadowOpacity: 0.28,
|
|
941
|
+
shadowRadius: 5,
|
|
942
|
+
elevation: 5,
|
|
943
|
+
},
|
|
944
|
+
iconOptionLabel: {
|
|
945
|
+
fontSize: 18,
|
|
946
|
+
fontWeight: '700',
|
|
947
|
+
},
|
|
948
|
+
iconOptionText: {
|
|
949
|
+
color: '#e2e8f0',
|
|
950
|
+
fontSize: 11,
|
|
951
|
+
fontWeight: '600',
|
|
952
|
+
},
|
|
953
|
+
cancelBtn: {
|
|
954
|
+
marginTop: 4,
|
|
955
|
+
borderRadius: 14,
|
|
956
|
+
borderWidth: 1,
|
|
957
|
+
borderColor: 'rgba(255,255,255,0.1)',
|
|
958
|
+
paddingVertical: 15,
|
|
959
|
+
alignItems: 'center',
|
|
960
|
+
justifyContent: 'center',
|
|
961
|
+
backgroundColor: 'rgba(255,255,255,0.04)',
|
|
962
|
+
},
|
|
963
|
+
cancelBtnText: {
|
|
964
|
+
color: '#e5e7eb',
|
|
965
|
+
fontSize: 15,
|
|
966
|
+
fontWeight: '700',
|
|
967
|
+
},
|
|
968
|
+
buttonPressed: {
|
|
969
|
+
opacity: 0.7,
|
|
760
970
|
},
|
|
761
971
|
});
|