yaver-feedback-react-native 0.8.1 → 0.8.3
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 +2 -2
- package/dist/AuthOverlay.js +33 -3
- package/dist/Discovery.js +2 -0
- package/dist/FeedbackModal.js +438 -215
- package/dist/GuestOnboardingScreen.d.ts +8 -0
- package/dist/GuestOnboardingScreen.js +282 -0
- package/dist/LoginScreen.d.ts +5 -1
- package/dist/LoginScreen.js +5 -2
- package/dist/MachinePickerScreen.js +1 -1
- package/dist/P2PClient.d.ts +22 -3
- package/dist/P2PClient.js +45 -2
- package/dist/QuickActionIcon.js +16 -4
- package/dist/YaverFeedback.d.ts +7 -0
- package/dist/YaverFeedback.js +68 -4
- package/dist/__tests__/P2PClient.test.js +30 -0
- package/dist/__tests__/YaverFeedback.test.js +39 -0
- package/dist/auth.d.ts +52 -0
- package/dist/auth.js +75 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +10 -5
- package/dist/preferences.d.ts +11 -0
- package/dist/preferences.js +82 -0
- package/dist/types.d.ts +7 -0
- package/package.json +6 -4
- package/src/AuthOverlay.tsx +47 -2
- package/src/Discovery.ts +2 -0
- package/src/FeedbackModal.tsx +507 -258
- package/src/GuestOnboardingScreen.tsx +307 -0
- package/src/LoginScreen.tsx +21 -2
- package/src/MachinePickerScreen.tsx +3 -1
- package/src/P2PClient.ts +69 -3
- package/src/QuickActionIcon.tsx +25 -5
- package/src/YaverFeedback.ts +79 -4
- package/src/__tests__/P2PClient.test.ts +40 -0
- package/src/__tests__/YaverFeedback.test.ts +42 -0
- package/src/auth.ts +137 -0
- package/src/index.ts +14 -4
- package/src/preferences.ts +96 -0
- package/src/types.ts +7 -0
package/dist/FeedbackModal.js
CHANGED
|
@@ -41,14 +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 [lastVideo, setLastVideo] = (0, react_1.useState)(null);
|
|
52
52
|
// Tracks whether the user has hidden the QuickActionIcon via its
|
|
53
53
|
// long-press menu. Shake is always available, so the feedback modal
|
|
54
54
|
// is our guaranteed UI for bringing the icon back — we surface a
|
|
@@ -61,9 +61,113 @@ const FeedbackModal = () => {
|
|
|
61
61
|
// the wrong project because the matcher grepped the prompt itself).
|
|
62
62
|
const [showVibeInput, setShowVibeInput] = (0, react_1.useState)(false);
|
|
63
63
|
const [vibePrompt, setVibePrompt] = (0, react_1.useState)('');
|
|
64
|
-
const [showCaptureChoices, setShowCaptureChoices] = (0, react_1.useState)(false);
|
|
65
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
|
+
});
|
|
66
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
|
+
}, []);
|
|
67
171
|
(0, react_1.useEffect)(() => {
|
|
68
172
|
mountedRef.current = true;
|
|
69
173
|
const sub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
@@ -71,8 +175,10 @@ const FeedbackModal = () => {
|
|
|
71
175
|
setVisible(true);
|
|
72
176
|
setError(null);
|
|
73
177
|
setToast(null);
|
|
178
|
+
setProgress(null);
|
|
74
179
|
setAction('idle');
|
|
75
|
-
|
|
180
|
+
setShowVibeInput(false);
|
|
181
|
+
setVibePrompt('');
|
|
76
182
|
// Re-read the "user hid the quick icon" flag on every open so
|
|
77
183
|
// the re-enable row reflects the latest preference (the user
|
|
78
184
|
// might have hidden or shown it between opens).
|
|
@@ -82,6 +188,13 @@ const FeedbackModal = () => {
|
|
|
82
188
|
setQuickIconHidden(v);
|
|
83
189
|
})
|
|
84
190
|
.catch(() => { });
|
|
191
|
+
YaverFeedback_1.YaverFeedback.getQuickIconColorPreset()
|
|
192
|
+
.then((preset) => {
|
|
193
|
+
if (mountedRef.current)
|
|
194
|
+
setQuickIconColorPreset(preset);
|
|
195
|
+
})
|
|
196
|
+
.catch(() => { });
|
|
197
|
+
void loadSelectedMachine();
|
|
85
198
|
}
|
|
86
199
|
});
|
|
87
200
|
// Agent streams build / compile progress through the BlackBox
|
|
@@ -109,7 +222,15 @@ const FeedbackModal = () => {
|
|
|
109
222
|
sub.remove();
|
|
110
223
|
statusSub.remove();
|
|
111
224
|
};
|
|
112
|
-
}, []);
|
|
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]);
|
|
113
234
|
const closeSoon = (0, react_1.useCallback)((delayMs = 1200) => {
|
|
114
235
|
setTimeout(() => {
|
|
115
236
|
if (mountedRef.current)
|
|
@@ -120,8 +241,10 @@ const FeedbackModal = () => {
|
|
|
120
241
|
setVisible(false);
|
|
121
242
|
setError(null);
|
|
122
243
|
setToast(null);
|
|
244
|
+
setProgress(null);
|
|
123
245
|
setAction('idle');
|
|
124
|
-
|
|
246
|
+
setShowVibeInput(false);
|
|
247
|
+
setVibePrompt('');
|
|
125
248
|
}, []);
|
|
126
249
|
// Helper: run a P2P call; on network failure, ask YaverFeedback to
|
|
127
250
|
// re-query Convex for the fresh IP and retry once. Solves the common
|
|
@@ -177,31 +300,54 @@ const FeedbackModal = () => {
|
|
|
177
300
|
setAction('hot-reloading');
|
|
178
301
|
setError(null);
|
|
179
302
|
setProgress(0);
|
|
180
|
-
setToast('
|
|
303
|
+
setToast('Contacting selected machine…');
|
|
181
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
|
+
}
|
|
182
318
|
// Default mode: bundle. Always rebuilds via the agent regardless
|
|
183
319
|
// of Metro state. P2PClient.reloadApp auto-resolves projectName +
|
|
184
320
|
// bundleId from expo-constants / NativeModules so the agent can
|
|
185
321
|
// map this app to its MobileProject scan entry without needing
|
|
186
322
|
// `yaver dev start` to have been run.
|
|
323
|
+
let ackMessage = 'Reload request acknowledged.';
|
|
187
324
|
await runWithReconnect(async (client) => {
|
|
188
|
-
await client.reloadApp('bundle');
|
|
325
|
+
const ack = await client.reloadApp('bundle');
|
|
326
|
+
ackMessage = ack.message;
|
|
327
|
+
setToast(ack.message);
|
|
328
|
+
setProgress(0.2);
|
|
189
329
|
});
|
|
190
330
|
// We don't auto-close here — the agent's BlackBox status pings
|
|
191
331
|
// will keep the modal updated, and the on-device YaverBundleLoader
|
|
192
332
|
// will reload the JS once the fresh bundle arrives. Modal stays
|
|
193
333
|
// up for a beat so the user sees the final progress state.
|
|
334
|
+
setToast(ackMessage);
|
|
194
335
|
closeSoon(2500);
|
|
195
336
|
}
|
|
196
337
|
catch (err) {
|
|
197
|
-
|
|
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();
|
|
198
344
|
setProgress(null);
|
|
199
345
|
}
|
|
200
346
|
finally {
|
|
201
347
|
if (mountedRef.current)
|
|
202
348
|
setAction('idle');
|
|
203
349
|
}
|
|
204
|
-
}, [closeSoon, runWithReconnect]);
|
|
350
|
+
}, [closeSoon, loadSelectedMachine, runWithReconnect]);
|
|
205
351
|
const uploadBundleWithOptionalFix = (0, react_1.useCallback)(async (bundle, fixOnUpload, successToast, failureToast) => {
|
|
206
352
|
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
207
353
|
const config = YaverFeedback_1.YaverFeedback.getConfig();
|
|
@@ -235,14 +381,9 @@ const FeedbackModal = () => {
|
|
|
235
381
|
setError(err instanceof Error ? err.message : String(err));
|
|
236
382
|
}
|
|
237
383
|
}, [closeSoon]);
|
|
238
|
-
// ─── 3. Screenshot / Upload ───────────────────────────────────────
|
|
239
|
-
const handleCaptureChoiceToggle = (0, react_1.useCallback)(() => {
|
|
240
|
-
setShowCaptureChoices((v) => !v);
|
|
241
|
-
}, []);
|
|
242
384
|
const handleScreenshotAndFix = (0, react_1.useCallback)(async () => {
|
|
243
385
|
setAction('capturing');
|
|
244
386
|
setError(null);
|
|
245
|
-
setShowCaptureChoices(false);
|
|
246
387
|
setVisible(false);
|
|
247
388
|
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
248
389
|
let path;
|
|
@@ -285,58 +426,40 @@ const FeedbackModal = () => {
|
|
|
285
426
|
setAction('idle');
|
|
286
427
|
}
|
|
287
428
|
}, [uploadBundleWithOptionalFix]);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
setShowCaptureChoices(false);
|
|
292
|
-
try {
|
|
293
|
-
const picked = await (0, capture_1.pickFeedbackFile)();
|
|
294
|
-
const { Dimensions } = require('react-native');
|
|
295
|
-
const { width, height } = Dimensions.get('window');
|
|
296
|
-
const deviceInfo = {
|
|
297
|
-
platform: react_native_1.Platform.OS,
|
|
298
|
-
osVersion: String(react_native_1.Platform.Version),
|
|
299
|
-
model: react_native_1.Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
300
|
-
screenWidth: width,
|
|
301
|
-
screenHeight: height,
|
|
302
|
-
};
|
|
303
|
-
const capturedErrors = YaverFeedback_1.YaverFeedback.getCapturedErrors();
|
|
304
|
-
const bundle = {
|
|
305
|
-
metadata: {
|
|
306
|
-
timestamp: new Date().toISOString(),
|
|
307
|
-
device: deviceInfo,
|
|
308
|
-
app: {},
|
|
309
|
-
userNote: `[Uploaded file] ${picked.name}`,
|
|
310
|
-
},
|
|
311
|
-
screenshots: picked.kind === 'image' ? [picked.path] : [],
|
|
312
|
-
video: picked.kind === 'video' ? picked.path : undefined,
|
|
313
|
-
audio: picked.kind === 'audio' ? picked.path : undefined,
|
|
314
|
-
errors: capturedErrors.length > 0 ? capturedErrors : undefined,
|
|
315
|
-
};
|
|
316
|
-
if (picked.kind === 'unknown') {
|
|
317
|
-
throw new Error('Pick an image, video, or audio file.');
|
|
318
|
-
}
|
|
319
|
-
await uploadBundleWithOptionalFix(bundle, picked.kind === 'image', picked.kind === 'image' ? 'Fix task started' : 'File uploaded');
|
|
320
|
-
}
|
|
321
|
-
catch (err) {
|
|
322
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
323
|
-
if (message !== 'File selection canceled.') {
|
|
324
|
-
setError(message);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
finally {
|
|
328
|
-
if (mountedRef.current)
|
|
329
|
-
setAction('idle');
|
|
330
|
-
}
|
|
429
|
+
/*
|
|
430
|
+
const handleFileUpload = useCallback(async () => {
|
|
431
|
+
...
|
|
331
432
|
}, [uploadBundleWithOptionalFix]);
|
|
433
|
+
*/
|
|
332
434
|
// ─── 3. Vibing ─────────────────────────────────────────────────────
|
|
333
435
|
// First tap expands the input; second submit fires the actual
|
|
334
436
|
// /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
|
|
335
437
|
// user types what they want, hits Send, sees the task id back. If
|
|
336
438
|
// left blank, we default to "pick the next small improvement"
|
|
337
439
|
// so a one-tap workflow still works for lazy days.
|
|
338
|
-
const handleVibingButton = (0, react_1.useCallback)(() => {
|
|
440
|
+
const handleVibingButton = (0, react_1.useCallback)(async () => {
|
|
339
441
|
if (!showVibeInput) {
|
|
442
|
+
const client = YaverFeedback_1.YaverFeedback.getP2PClient();
|
|
443
|
+
if (!client) {
|
|
444
|
+
setError('Not connected to the agent yet.');
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
setError(null);
|
|
448
|
+
try {
|
|
449
|
+
const eligibility = await client.getVibingEligibility();
|
|
450
|
+
if (!eligibility.canVibe) {
|
|
451
|
+
const message = eligibility.guidance && eligibility.guidance.trim()
|
|
452
|
+
? `${eligibility.reason ?? 'Vibe coding is unavailable.'} ${eligibility.guidance}`
|
|
453
|
+
: eligibility.reason ?? 'Vibe coding is unavailable.';
|
|
454
|
+
setError(message);
|
|
455
|
+
setToast('Vibe coding unavailable for this project.');
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
340
463
|
setShowVibeInput(true);
|
|
341
464
|
return;
|
|
342
465
|
}
|
|
@@ -381,127 +504,23 @@ const FeedbackModal = () => {
|
|
|
381
504
|
setAction('idle');
|
|
382
505
|
}
|
|
383
506
|
}, [vibePrompt]);
|
|
384
|
-
|
|
385
|
-
const handleScreenRecording =
|
|
386
|
-
|
|
387
|
-
if (!isRecordingVideo && lastVideo) {
|
|
388
|
-
const config = YaverFeedback_1.YaverFeedback.getConfig();
|
|
389
|
-
if (!config?.agentUrl) {
|
|
390
|
-
setError('Not connected to the agent yet.');
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
setAction('uploading-video');
|
|
394
|
-
try {
|
|
395
|
-
const { Dimensions } = require('react-native');
|
|
396
|
-
const { width, height } = Dimensions.get('window');
|
|
397
|
-
const deviceInfo = {
|
|
398
|
-
platform: react_native_1.Platform.OS,
|
|
399
|
-
osVersion: String(react_native_1.Platform.Version),
|
|
400
|
-
model: react_native_1.Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
401
|
-
screenWidth: width,
|
|
402
|
-
screenHeight: height,
|
|
403
|
-
};
|
|
404
|
-
const bundle = {
|
|
405
|
-
metadata: {
|
|
406
|
-
timestamp: new Date().toISOString(),
|
|
407
|
-
device: deviceInfo,
|
|
408
|
-
app: {},
|
|
409
|
-
userNote: '[Screen recording]',
|
|
410
|
-
},
|
|
411
|
-
screenshots: [],
|
|
412
|
-
video: lastVideo.path,
|
|
413
|
-
errors: YaverFeedback_1.YaverFeedback.getCapturedErrors().length
|
|
414
|
-
? YaverFeedback_1.YaverFeedback.getCapturedErrors()
|
|
415
|
-
: undefined,
|
|
416
|
-
};
|
|
417
|
-
await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle);
|
|
418
|
-
if (mountedRef.current) {
|
|
419
|
-
setToast(`Recording uploaded — ${Math.round(lastVideo.duration)}s`);
|
|
420
|
-
setLastVideo(null);
|
|
421
|
-
}
|
|
422
|
-
closeSoon(1200);
|
|
423
|
-
}
|
|
424
|
-
catch (err) {
|
|
425
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
426
|
-
}
|
|
427
|
-
finally {
|
|
428
|
-
if (mountedRef.current)
|
|
429
|
-
setAction('idle');
|
|
430
|
-
}
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
if (isRecordingVideo) {
|
|
434
|
-
try {
|
|
435
|
-
const result = await (0, capture_1.stopVideoRecording)();
|
|
436
|
-
if (mountedRef.current) {
|
|
437
|
-
setIsRecordingVideo(false);
|
|
438
|
-
setLastVideo(result);
|
|
439
|
-
setAction('uploading-video');
|
|
440
|
-
setToast('Uploading recording…');
|
|
441
|
-
}
|
|
442
|
-
const config = YaverFeedback_1.YaverFeedback.getConfig();
|
|
443
|
-
if (!config?.agentUrl) {
|
|
444
|
-
throw new Error('Not connected to the agent yet.');
|
|
445
|
-
}
|
|
446
|
-
const { Dimensions } = require('react-native');
|
|
447
|
-
const { width, height } = Dimensions.get('window');
|
|
448
|
-
const deviceInfo = {
|
|
449
|
-
platform: react_native_1.Platform.OS,
|
|
450
|
-
osVersion: String(react_native_1.Platform.Version),
|
|
451
|
-
model: react_native_1.Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
452
|
-
screenWidth: width,
|
|
453
|
-
screenHeight: height,
|
|
454
|
-
};
|
|
455
|
-
const bundle = {
|
|
456
|
-
metadata: {
|
|
457
|
-
timestamp: new Date().toISOString(),
|
|
458
|
-
device: deviceInfo,
|
|
459
|
-
app: {},
|
|
460
|
-
userNote: '[Screen recording]',
|
|
461
|
-
},
|
|
462
|
-
screenshots: [],
|
|
463
|
-
video: result.path,
|
|
464
|
-
errors: YaverFeedback_1.YaverFeedback.getCapturedErrors().length
|
|
465
|
-
? YaverFeedback_1.YaverFeedback.getCapturedErrors()
|
|
466
|
-
: undefined,
|
|
467
|
-
};
|
|
468
|
-
await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle);
|
|
469
|
-
if (mountedRef.current) {
|
|
470
|
-
setToast(`Recording uploaded — ${Math.round(result.duration)}s`);
|
|
471
|
-
setLastVideo(null);
|
|
472
|
-
}
|
|
473
|
-
closeSoon(1200);
|
|
474
|
-
}
|
|
475
|
-
catch (err) {
|
|
476
|
-
setIsRecordingVideo(false);
|
|
477
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
478
|
-
}
|
|
479
|
-
finally {
|
|
480
|
-
if (mountedRef.current)
|
|
481
|
-
setAction('idle');
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
try {
|
|
486
|
-
await (0, capture_1.startVideoRecording)();
|
|
487
|
-
if (mountedRef.current) {
|
|
488
|
-
setIsRecordingVideo(true);
|
|
489
|
-
setToast('Recording… tap again to stop and upload');
|
|
490
|
-
setLastVideo(null);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
catch (err) {
|
|
494
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
495
|
-
}
|
|
496
|
-
}
|
|
507
|
+
/*
|
|
508
|
+
const handleScreenRecording = useCallback(async () => {
|
|
509
|
+
...
|
|
497
510
|
}, [closeSoon, isRecordingVideo, lastVideo]);
|
|
511
|
+
*/
|
|
498
512
|
const busy = action !== 'idle';
|
|
499
513
|
return (<>
|
|
500
514
|
<AuthOverlay_1.AuthOverlay />
|
|
501
515
|
<QuickActionIcon_1.QuickActionIcon />
|
|
502
516
|
{visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
|
|
503
517
|
<react_native_1.Pressable style={styles.overlay} onPress={handleClose}>
|
|
504
|
-
<react_native_1.
|
|
518
|
+
<react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.kbAvoider} pointerEvents="box-none">
|
|
519
|
+
<react_native_1.Pressable style={styles.modal} onPress={(e) => {
|
|
520
|
+
e.stopPropagation();
|
|
521
|
+
react_native_1.Keyboard.dismiss();
|
|
522
|
+
}}>
|
|
523
|
+
<react_native_1.ScrollView style={styles.scroll} contentContainerStyle={styles.scrollContent} keyboardShouldPersistTaps="handled">
|
|
505
524
|
<react_native_1.View style={styles.header}>
|
|
506
525
|
<react_native_1.Text style={styles.title}>Send Feedback</react_native_1.Text>
|
|
507
526
|
<react_native_1.Pressable onPress={handleClose} hitSlop={12} style={styles.closeBtn} accessibilityRole="button" accessibilityLabel="Close">
|
|
@@ -509,6 +528,89 @@ const FeedbackModal = () => {
|
|
|
509
528
|
</react_native_1.Pressable>
|
|
510
529
|
</react_native_1.View>
|
|
511
530
|
|
|
531
|
+
<react_native_1.Pressable onPress={() => {
|
|
532
|
+
if (!YaverFeedback_1.YaverFeedback.isAuthed()) {
|
|
533
|
+
YaverFeedback_1.YaverFeedback.showLogin();
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
YaverFeedback_1.YaverFeedback.showMachinePicker();
|
|
537
|
+
}} style={[
|
|
538
|
+
styles.machineCard,
|
|
539
|
+
machineCard.status === 'live' && styles.machineCardLive,
|
|
540
|
+
machineCard.status === 'attention' && styles.machineCardAttention,
|
|
541
|
+
machineCard.status === 'offline' && styles.machineCardOffline,
|
|
542
|
+
]}>
|
|
543
|
+
<react_native_1.View style={styles.machineHeader}>
|
|
544
|
+
<react_native_1.View style={styles.machineTitleWrap}>
|
|
545
|
+
<react_native_1.View style={[
|
|
546
|
+
styles.machineDot,
|
|
547
|
+
machineCard.status === 'live' && styles.machineDotLive,
|
|
548
|
+
machineCard.status === 'attention' && styles.machineDotAttention,
|
|
549
|
+
machineCard.status === 'offline' && styles.machineDotOffline,
|
|
550
|
+
]}/>
|
|
551
|
+
<react_native_1.Text style={styles.machineLabel}>Selected Machine</react_native_1.Text>
|
|
552
|
+
</react_native_1.View>
|
|
553
|
+
<react_native_1.Text style={styles.machineAction}>
|
|
554
|
+
{machineCard.loading ? 'Refreshing…' : 'Change'}
|
|
555
|
+
</react_native_1.Text>
|
|
556
|
+
</react_native_1.View>
|
|
557
|
+
<react_native_1.Text style={styles.machineName}>
|
|
558
|
+
{machineCard.loading ? 'Checking machine…' : machineCard.title}
|
|
559
|
+
</react_native_1.Text>
|
|
560
|
+
<react_native_1.Text style={styles.machineMeta}>{machineCard.detail}</react_native_1.Text>
|
|
561
|
+
</react_native_1.Pressable>
|
|
562
|
+
|
|
563
|
+
{quickIconHidden && (<react_native_1.View style={styles.quickIconNote}>
|
|
564
|
+
<react_native_1.Text style={styles.quickIconNoteText}>
|
|
565
|
+
Quick access icon is hidden. Shake the phone if you want feedback back fast.
|
|
566
|
+
</react_native_1.Text>
|
|
567
|
+
<react_native_1.Pressable onPress={() => {
|
|
568
|
+
void YaverFeedback_1.YaverFeedback.setQuickIconVisible(true);
|
|
569
|
+
setQuickIconHidden(false);
|
|
570
|
+
}} style={({ pressed }) => [
|
|
571
|
+
styles.quickIconToggle,
|
|
572
|
+
pressed && styles.buttonPressed,
|
|
573
|
+
]}>
|
|
574
|
+
<react_native_1.Text style={styles.quickIconToggleText}>Show quick icon again</react_native_1.Text>
|
|
575
|
+
</react_native_1.Pressable>
|
|
576
|
+
</react_native_1.View>)}
|
|
577
|
+
|
|
578
|
+
<react_native_1.View style={styles.iconSelector}>
|
|
579
|
+
<react_native_1.Text style={styles.iconSelectorTitle}>Quick Icon Color</react_native_1.Text>
|
|
580
|
+
<react_native_1.Text style={styles.iconSelectorText}>
|
|
581
|
+
Pick a runtime color so the floating y icon does not overlap with your app UI.
|
|
582
|
+
</react_native_1.Text>
|
|
583
|
+
<react_native_1.View style={styles.iconSelectorGrid}>
|
|
584
|
+
{Object.entries(preferences_1.QUICK_ICON_COLOR_PRESETS).map(([preset, colors]) => {
|
|
585
|
+
const selected = quickIconColorPreset === preset;
|
|
586
|
+
return (<react_native_1.Pressable key={preset} onPress={() => {
|
|
587
|
+
setQuickIconColorPreset(preset);
|
|
588
|
+
void YaverFeedback_1.YaverFeedback.setQuickIconColorPreset(preset);
|
|
589
|
+
}} style={[
|
|
590
|
+
styles.iconOption,
|
|
591
|
+
selected && styles.iconOptionSelected,
|
|
592
|
+
]}>
|
|
593
|
+
<react_native_1.View style={[
|
|
594
|
+
styles.iconOptionCircle,
|
|
595
|
+
{
|
|
596
|
+
backgroundColor: colors.backgroundColor,
|
|
597
|
+
borderColor: colors.borderColor,
|
|
598
|
+
shadowColor: colors.shadowColor,
|
|
599
|
+
},
|
|
600
|
+
]}>
|
|
601
|
+
<react_native_1.Text style={[
|
|
602
|
+
styles.iconOptionLabel,
|
|
603
|
+
{ color: colors.foregroundColor },
|
|
604
|
+
]}>
|
|
605
|
+
y
|
|
606
|
+
</react_native_1.Text>
|
|
607
|
+
</react_native_1.View>
|
|
608
|
+
<react_native_1.Text style={styles.iconOptionText}>{colors.label}</react_native_1.Text>
|
|
609
|
+
</react_native_1.Pressable>);
|
|
610
|
+
})}
|
|
611
|
+
</react_native_1.View>
|
|
612
|
+
</react_native_1.View>
|
|
613
|
+
|
|
512
614
|
{/* 1. Hot Reload — the common path */}
|
|
513
615
|
<ActionRow label={action === 'hot-reloading' ? 'Reloading…' : 'Hot Reload'} tint="#fbbf24" onPress={handleHotReload} disabled={busy} busy={action === 'hot-reloading'}/>
|
|
514
616
|
|
|
@@ -537,22 +639,8 @@ const FeedbackModal = () => {
|
|
|
537
639
|
Last vibing task: {lastVibeTaskId.slice(0, 12)}…
|
|
538
640
|
</react_native_1.Text>)}
|
|
539
641
|
|
|
540
|
-
{/* Screenshot
|
|
541
|
-
{
|
|
542
|
-
? 'Working…'
|
|
543
|
-
: 'Screenshot / Upload'} tint="#22c55e" onPress={handleCaptureChoiceToggle} disabled={busy} busy={action === 'capturing'}/>) : (<react_native_1.View style={styles.captureChoices}>
|
|
544
|
-
<ActionRow label="Take Screenshot" tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy}/>
|
|
545
|
-
<ActionRow label="Upload File" tint="#34d399" onPress={handleFileUpload} disabled={busy}/>
|
|
546
|
-
</react_native_1.View>)}
|
|
547
|
-
|
|
548
|
-
{/* 4. Screen recording */}
|
|
549
|
-
<ActionRow label={action === 'uploading-video'
|
|
550
|
-
? 'Uploading…'
|
|
551
|
-
: isRecordingVideo
|
|
552
|
-
? 'Stop & Upload Recording'
|
|
553
|
-
: lastVideo
|
|
554
|
-
? `Retry Upload Recording · ${Math.round(lastVideo.duration)}s`
|
|
555
|
-
: 'Screen Recording'} tint={isRecordingVideo ? '#ef4444' : '#60a5fa'} onPress={handleScreenRecording} disabled={busy && action !== 'uploading-video' && !isRecordingVideo} busy={action === 'uploading-video'}/>
|
|
642
|
+
{/* Screenshot & Fix */}
|
|
643
|
+
<ActionRow label={action === 'capturing' ? 'Working…' : 'Screenshot & Fix'} tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy} busy={action === 'capturing'}/>
|
|
556
644
|
|
|
557
645
|
{progress !== null && (<react_native_1.View style={styles.progressTrack}>
|
|
558
646
|
<react_native_1.View style={[
|
|
@@ -563,34 +651,15 @@ const FeedbackModal = () => {
|
|
|
563
651
|
{toast && <react_native_1.Text style={styles.toast}>{toast}</react_native_1.Text>}
|
|
564
652
|
{error && <react_native_1.Text style={styles.error}>{error}</react_native_1.Text>}
|
|
565
653
|
|
|
566
|
-
{/* Quick-icon toggle. The user's three ways to control
|
|
567
|
-
the floating icon are: (1) long-press the icon →
|
|
568
|
-
Hide, (2) tap this row to toggle it on/off, (3) shake
|
|
569
|
-
→ this modal → tap this row. Shake is the unkillable
|
|
570
|
-
back-door when the icon is hidden and the dev hasn't
|
|
571
|
-
exposed their own settings UI. */}
|
|
572
|
-
<react_native_1.Pressable onPress={async () => {
|
|
573
|
-
const next = !quickIconHidden;
|
|
574
|
-
setQuickIconHidden(next);
|
|
575
|
-
await YaverFeedback_1.YaverFeedback.setQuickIconVisible(!next);
|
|
576
|
-
}} style={({ pressed }) => [
|
|
577
|
-
styles.quickIconToggle,
|
|
578
|
-
pressed && { opacity: 0.7 },
|
|
579
|
-
]} accessibilityRole="button" accessibilityLabel={quickIconHidden ? 'Show quick icon' : 'Hide quick icon'}>
|
|
580
|
-
<react_native_1.Text style={styles.quickIconToggleText}>
|
|
581
|
-
{quickIconHidden
|
|
582
|
-
? '◯ Show quick-access icon'
|
|
583
|
-
: '● Hide quick-access icon'}
|
|
584
|
-
</react_native_1.Text>
|
|
585
|
-
</react_native_1.Pressable>
|
|
586
|
-
|
|
587
654
|
<react_native_1.Pressable onPress={handleClose} style={({ pressed }) => [
|
|
588
655
|
styles.cancelBtn,
|
|
589
656
|
pressed && styles.buttonPressed,
|
|
590
657
|
]} accessibilityRole="button" accessibilityLabel="Cancel">
|
|
591
658
|
<react_native_1.Text style={styles.cancelBtnText}>Cancel</react_native_1.Text>
|
|
592
659
|
</react_native_1.Pressable>
|
|
660
|
+
</react_native_1.ScrollView>
|
|
593
661
|
</react_native_1.Pressable>
|
|
662
|
+
</react_native_1.KeyboardAvoidingView>
|
|
594
663
|
</react_native_1.Pressable>
|
|
595
664
|
</react_native_1.Modal>)}
|
|
596
665
|
</>);
|
|
@@ -663,6 +732,10 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
663
732
|
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
664
733
|
justifyContent: 'flex-end',
|
|
665
734
|
},
|
|
735
|
+
kbAvoider: {
|
|
736
|
+
width: '100%',
|
|
737
|
+
justifyContent: 'flex-end',
|
|
738
|
+
},
|
|
666
739
|
modal: {
|
|
667
740
|
backgroundColor: '#141422',
|
|
668
741
|
borderTopLeftRadius: 22,
|
|
@@ -670,6 +743,14 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
670
743
|
padding: 22,
|
|
671
744
|
paddingBottom: 36,
|
|
672
745
|
gap: 12,
|
|
746
|
+
maxHeight: '92%',
|
|
747
|
+
},
|
|
748
|
+
scroll: {
|
|
749
|
+
maxHeight: '100%',
|
|
750
|
+
},
|
|
751
|
+
scrollContent: {
|
|
752
|
+
gap: 12,
|
|
753
|
+
paddingBottom: 8,
|
|
673
754
|
},
|
|
674
755
|
header: {
|
|
675
756
|
flexDirection: 'row',
|
|
@@ -710,6 +791,74 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
710
791
|
fontSize: 15,
|
|
711
792
|
fontWeight: '700',
|
|
712
793
|
},
|
|
794
|
+
machineCard: {
|
|
795
|
+
borderRadius: 14,
|
|
796
|
+
borderWidth: 1,
|
|
797
|
+
padding: 14,
|
|
798
|
+
backgroundColor: 'rgba(255,255,255,0.04)',
|
|
799
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
800
|
+
},
|
|
801
|
+
machineCardLive: {
|
|
802
|
+
backgroundColor: 'rgba(34,197,94,0.10)',
|
|
803
|
+
borderColor: 'rgba(34,197,94,0.35)',
|
|
804
|
+
},
|
|
805
|
+
machineCardAttention: {
|
|
806
|
+
backgroundColor: 'rgba(245,158,11,0.10)',
|
|
807
|
+
borderColor: 'rgba(245,158,11,0.35)',
|
|
808
|
+
},
|
|
809
|
+
machineCardOffline: {
|
|
810
|
+
backgroundColor: 'rgba(239,68,68,0.10)',
|
|
811
|
+
borderColor: 'rgba(239,68,68,0.35)',
|
|
812
|
+
},
|
|
813
|
+
machineHeader: {
|
|
814
|
+
flexDirection: 'row',
|
|
815
|
+
alignItems: 'center',
|
|
816
|
+
justifyContent: 'space-between',
|
|
817
|
+
marginBottom: 6,
|
|
818
|
+
},
|
|
819
|
+
machineTitleWrap: {
|
|
820
|
+
flexDirection: 'row',
|
|
821
|
+
alignItems: 'center',
|
|
822
|
+
gap: 8,
|
|
823
|
+
},
|
|
824
|
+
machineDot: {
|
|
825
|
+
width: 10,
|
|
826
|
+
height: 10,
|
|
827
|
+
borderRadius: 5,
|
|
828
|
+
backgroundColor: '#6b7280',
|
|
829
|
+
},
|
|
830
|
+
machineDotLive: {
|
|
831
|
+
backgroundColor: '#22c55e',
|
|
832
|
+
},
|
|
833
|
+
machineDotAttention: {
|
|
834
|
+
backgroundColor: '#f59e0b',
|
|
835
|
+
},
|
|
836
|
+
machineDotOffline: {
|
|
837
|
+
backgroundColor: '#ef4444',
|
|
838
|
+
},
|
|
839
|
+
machineLabel: {
|
|
840
|
+
color: '#cbd5e1',
|
|
841
|
+
fontSize: 12,
|
|
842
|
+
fontWeight: '700',
|
|
843
|
+
textTransform: 'uppercase',
|
|
844
|
+
letterSpacing: 0.8,
|
|
845
|
+
},
|
|
846
|
+
machineAction: {
|
|
847
|
+
color: '#a5b4fc',
|
|
848
|
+
fontSize: 12,
|
|
849
|
+
fontWeight: '700',
|
|
850
|
+
},
|
|
851
|
+
machineName: {
|
|
852
|
+
color: '#fff',
|
|
853
|
+
fontSize: 16,
|
|
854
|
+
fontWeight: '700',
|
|
855
|
+
},
|
|
856
|
+
machineMeta: {
|
|
857
|
+
color: '#cbd5e1',
|
|
858
|
+
fontSize: 12,
|
|
859
|
+
marginTop: 4,
|
|
860
|
+
lineHeight: 17,
|
|
861
|
+
},
|
|
713
862
|
captureChoices: {
|
|
714
863
|
gap: 10,
|
|
715
864
|
},
|
|
@@ -738,15 +887,89 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
738
887
|
marginTop: 4,
|
|
739
888
|
},
|
|
740
889
|
quickIconToggle: {
|
|
741
|
-
marginTop:
|
|
742
|
-
alignSelf: '
|
|
890
|
+
marginTop: 6,
|
|
891
|
+
alignSelf: 'flex-start',
|
|
743
892
|
paddingVertical: 6,
|
|
744
|
-
paddingHorizontal:
|
|
893
|
+
paddingHorizontal: 10,
|
|
894
|
+
borderRadius: 10,
|
|
895
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
745
896
|
},
|
|
746
897
|
quickIconToggleText: {
|
|
747
|
-
color: '#
|
|
898
|
+
color: '#cbd5e1',
|
|
899
|
+
fontSize: 12,
|
|
900
|
+
fontWeight: '700',
|
|
901
|
+
},
|
|
902
|
+
quickIconNote: {
|
|
903
|
+
borderRadius: 12,
|
|
904
|
+
borderWidth: 1,
|
|
905
|
+
borderColor: 'rgba(251,191,36,0.28)',
|
|
906
|
+
backgroundColor: 'rgba(251,191,36,0.08)',
|
|
907
|
+
padding: 12,
|
|
908
|
+
},
|
|
909
|
+
quickIconNoteText: {
|
|
910
|
+
color: '#fde68a',
|
|
911
|
+
fontSize: 12,
|
|
912
|
+
lineHeight: 17,
|
|
913
|
+
},
|
|
914
|
+
iconSelector: {
|
|
915
|
+
borderRadius: 12,
|
|
916
|
+
borderWidth: 1,
|
|
917
|
+
borderColor: 'rgba(255,255,255,0.09)',
|
|
918
|
+
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
919
|
+
padding: 12,
|
|
920
|
+
gap: 10,
|
|
921
|
+
},
|
|
922
|
+
iconSelectorTitle: {
|
|
923
|
+
color: '#f8fafc',
|
|
924
|
+
fontSize: 13,
|
|
925
|
+
fontWeight: '700',
|
|
926
|
+
},
|
|
927
|
+
iconSelectorText: {
|
|
928
|
+
color: '#cbd5e1',
|
|
748
929
|
fontSize: 12,
|
|
749
|
-
|
|
930
|
+
lineHeight: 17,
|
|
931
|
+
},
|
|
932
|
+
iconSelectorGrid: {
|
|
933
|
+
flexDirection: 'row',
|
|
934
|
+
flexWrap: 'wrap',
|
|
935
|
+
gap: 10,
|
|
936
|
+
},
|
|
937
|
+
iconOption: {
|
|
938
|
+
width: '31%',
|
|
939
|
+
minWidth: 84,
|
|
940
|
+
borderRadius: 12,
|
|
941
|
+
borderWidth: 1,
|
|
942
|
+
borderColor: 'rgba(255,255,255,0.08)',
|
|
943
|
+
backgroundColor: 'rgba(255,255,255,0.02)',
|
|
944
|
+
paddingVertical: 10,
|
|
945
|
+
paddingHorizontal: 8,
|
|
946
|
+
alignItems: 'center',
|
|
947
|
+
gap: 8,
|
|
948
|
+
},
|
|
949
|
+
iconOptionSelected: {
|
|
950
|
+
borderColor: 'rgba(129,140,248,0.72)',
|
|
951
|
+
backgroundColor: 'rgba(129,140,248,0.12)',
|
|
952
|
+
},
|
|
953
|
+
iconOptionCircle: {
|
|
954
|
+
width: 38,
|
|
955
|
+
height: 38,
|
|
956
|
+
borderRadius: 19,
|
|
957
|
+
alignItems: 'center',
|
|
958
|
+
justifyContent: 'center',
|
|
959
|
+
borderWidth: 2,
|
|
960
|
+
shadowOffset: { width: 0, height: 2 },
|
|
961
|
+
shadowOpacity: 0.28,
|
|
962
|
+
shadowRadius: 5,
|
|
963
|
+
elevation: 5,
|
|
964
|
+
},
|
|
965
|
+
iconOptionLabel: {
|
|
966
|
+
fontSize: 18,
|
|
967
|
+
fontWeight: '700',
|
|
968
|
+
},
|
|
969
|
+
iconOptionText: {
|
|
970
|
+
color: '#e2e8f0',
|
|
971
|
+
fontSize: 11,
|
|
972
|
+
fontWeight: '600',
|
|
750
973
|
},
|
|
751
974
|
cancelBtn: {
|
|
752
975
|
marginTop: 4,
|