yaver-feedback-react-native 0.8.11 → 0.8.13
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/app.plugin.js +13 -0
- package/dist/BlackBox.d.ts +1 -0
- package/dist/BlackBox.js +29 -14
- package/dist/DeployPanel.d.ts +7 -0
- package/dist/DeployPanel.js +354 -0
- package/dist/FeedbackModal.js +94 -6
- package/dist/FloatingButton.js +25 -3
- package/dist/P2PClient.d.ts +71 -0
- package/dist/P2PClient.js +254 -0
- package/dist/VibeChatScreen.d.ts +20 -0
- package/dist/VibeChatScreen.js +328 -0
- package/dist/YaverFeedback.d.ts +7 -0
- package/dist/YaverFeedback.js +10 -1
- package/dist/_core/buildFeedbackPrompt.d.ts +13 -0
- package/dist/_core/buildFeedbackPrompt.js +77 -0
- package/dist/capture.d.ts +14 -0
- package/dist/capture.js +29 -0
- package/dist/preferences.d.ts +4 -0
- package/dist/preferences.js +62 -0
- package/dist/types.d.ts +9 -0
- package/dist/upload.d.ts +1 -1
- package/dist/upload.js +8 -4
- package/package.json +14 -2
- package/src/BlackBox.ts +29 -14
- package/src/DeployPanel.tsx +403 -0
- package/src/FeedbackModal.tsx +126 -5
- package/src/FloatingButton.tsx +27 -3
- package/src/P2PClient.ts +247 -1
- package/src/VibeChatScreen.tsx +362 -0
- package/src/YaverFeedback.ts +11 -1
- package/src/_core/buildFeedbackPrompt.ts +102 -0
- package/src/capture.ts +31 -0
- package/src/preferences.ts +56 -0
- package/src/types.ts +9 -0
- package/src/upload.ts +8 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaver-feedback-react-native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "Visual feedback SDK for Yaver \u2014 bug reports, screen recording, voice annotations, and local-first developer workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
24
24
|
"expo-web-browser": ">=12.0.0",
|
|
25
25
|
"expo-apple-authentication": ">=6.0.0",
|
|
26
|
-
"expo-document-picker": ">=13.0.0"
|
|
26
|
+
"expo-document-picker": ">=13.0.0",
|
|
27
|
+
"react-native-view-shot": ">=3.0.0",
|
|
28
|
+
"react-native-record-screen": ">=0.0.10",
|
|
29
|
+
"expo-av": ">=14.0.0"
|
|
27
30
|
},
|
|
28
31
|
"peerDependenciesMeta": {
|
|
29
32
|
"@expo/config-plugins": {
|
|
@@ -37,6 +40,15 @@
|
|
|
37
40
|
},
|
|
38
41
|
"expo-document-picker": {
|
|
39
42
|
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"react-native-view-shot": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"react-native-record-screen": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"expo-av": {
|
|
51
|
+
"optional": true
|
|
40
52
|
}
|
|
41
53
|
},
|
|
42
54
|
"devDependencies": {
|
package/src/BlackBox.ts
CHANGED
|
@@ -69,6 +69,12 @@ export type CommandHandler = (cmd: BlackBoxCommand) => void;
|
|
|
69
69
|
export class BlackBox {
|
|
70
70
|
private static baseUrl: string | null = null;
|
|
71
71
|
private static authToken: string | null = null;
|
|
72
|
+
// Mirror of the user's relay password — required for EVERY request that
|
|
73
|
+
// crosses the relay (`https://public.yaver.io/d/<id>/...`). Without it
|
|
74
|
+
// the relay rejects with HTTP 401 "invalid relay password" and the
|
|
75
|
+
// event stream silently fails. Set on start() from YaverFeedback's
|
|
76
|
+
// resolved relay password (same source the P2PClient uses).
|
|
77
|
+
private static relayPassword: string = '';
|
|
72
78
|
private static deviceId: string = '';
|
|
73
79
|
private static appName: string = '';
|
|
74
80
|
private static buffer: BlackBoxEvent[] = [];
|
|
@@ -103,6 +109,7 @@ export class BlackBox {
|
|
|
103
109
|
|
|
104
110
|
BlackBox.baseUrl = feedbackConfig.agentUrl.replace(/\/$/, '');
|
|
105
111
|
BlackBox.authToken = feedbackConfig.authToken ?? null;
|
|
112
|
+
BlackBox.relayPassword = (feedbackConfig as { relayPassword?: string }).relayPassword ?? '';
|
|
106
113
|
BlackBox.deviceId = config?.deviceId ?? BlackBox.generateDeviceId();
|
|
107
114
|
BlackBox.appName = config?.appName ?? '';
|
|
108
115
|
BlackBox.flushInterval = config?.flushInterval ?? 2000;
|
|
@@ -375,15 +382,19 @@ export class BlackBox {
|
|
|
375
382
|
const url = `${BlackBox.baseUrl}/blackbox/command-stream?device=${encodeURIComponent(BlackBox.deviceId)}`;
|
|
376
383
|
|
|
377
384
|
try {
|
|
385
|
+
const sseHeaders: Record<string, string> = {
|
|
386
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
387
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
388
|
+
'X-Platform': Platform.OS,
|
|
389
|
+
'X-App-Name': BlackBox.appName,
|
|
390
|
+
Accept: 'text/event-stream',
|
|
391
|
+
};
|
|
392
|
+
if (BlackBox.relayPassword) {
|
|
393
|
+
sseHeaders['X-Relay-Password'] = BlackBox.relayPassword;
|
|
394
|
+
}
|
|
378
395
|
const response = await fetch(url, {
|
|
379
396
|
method: 'GET',
|
|
380
|
-
headers:
|
|
381
|
-
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
382
|
-
'X-Device-ID': BlackBox.deviceId,
|
|
383
|
-
'X-Platform': Platform.OS,
|
|
384
|
-
'X-App-Name': BlackBox.appName,
|
|
385
|
-
Accept: 'text/event-stream',
|
|
386
|
-
},
|
|
397
|
+
headers: sseHeaders,
|
|
387
398
|
// @ts-ignore — React Native supports signal on fetch
|
|
388
399
|
signal: controller.signal,
|
|
389
400
|
});
|
|
@@ -479,15 +490,19 @@ export class BlackBox {
|
|
|
479
490
|
BlackBox.buffer = [];
|
|
480
491
|
|
|
481
492
|
try {
|
|
493
|
+
const flushHeaders: Record<string, string> = {
|
|
494
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
495
|
+
'Content-Type': 'application/json',
|
|
496
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
497
|
+
'X-Platform': Platform.OS,
|
|
498
|
+
'X-App-Name': BlackBox.appName,
|
|
499
|
+
};
|
|
500
|
+
if (BlackBox.relayPassword) {
|
|
501
|
+
flushHeaders['X-Relay-Password'] = BlackBox.relayPassword;
|
|
502
|
+
}
|
|
482
503
|
await fetch(`${BlackBox.baseUrl}/blackbox/events`, {
|
|
483
504
|
method: 'POST',
|
|
484
|
-
headers:
|
|
485
|
-
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
486
|
-
'Content-Type': 'application/json',
|
|
487
|
-
'X-Device-ID': BlackBox.deviceId,
|
|
488
|
-
'X-Platform': Platform.OS,
|
|
489
|
-
'X-App-Name': BlackBox.appName,
|
|
490
|
-
},
|
|
505
|
+
headers: flushHeaders,
|
|
491
506
|
body: JSON.stringify(events),
|
|
492
507
|
});
|
|
493
508
|
} catch {
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Pressable,
|
|
5
|
+
ScrollView,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
View,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import { YaverFeedback } from './YaverFeedback';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inline Deploy panel — third-party RN apps using yaver-feedback can deploy
|
|
14
|
+
* to TestFlight / Play Store from a phone shake without leaving their app.
|
|
15
|
+
*
|
|
16
|
+
* Flow:
|
|
17
|
+
* 1. GET /fleet/deploy-options?app=<slug> on the SDK's selected machine.
|
|
18
|
+
* The agent fans out doctor probes to the user's other reachable
|
|
19
|
+
* devices (LAN > Tailscale > relay) and returns merged capabilities.
|
|
20
|
+
* 2. User picks a target (TestFlight / Play / Both) — disables machines
|
|
21
|
+
* whose doctor reports a blocker for any picked target. Linux boxes
|
|
22
|
+
* grey out for TestFlight ("xcodebuild: only on darwin"). macOS
|
|
23
|
+
* machines without Xcode grey out for the same reason.
|
|
24
|
+
* 3. Tap a machine row → POST /deploy/ship {app, target/targets, machine}.
|
|
25
|
+
* Toast + auto-collapse the panel. Live SSE log viewing is the
|
|
26
|
+
* desktop / web Deploy tab's job — this surface stays minimal.
|
|
27
|
+
*
|
|
28
|
+
* App slug resolution: prefer config.deployAppSlug → bundleId tail →
|
|
29
|
+
* literal "main". Documented on FeedbackConfig.deployAppSlug.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
interface FleetDeployTargetCap {
|
|
33
|
+
target: string;
|
|
34
|
+
ok: boolean;
|
|
35
|
+
reason?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface FleetDeployDevice {
|
|
39
|
+
deviceId: string;
|
|
40
|
+
name: string;
|
|
41
|
+
alias?: string;
|
|
42
|
+
platform: string;
|
|
43
|
+
isLocal: boolean;
|
|
44
|
+
isOnline: boolean;
|
|
45
|
+
probed: boolean;
|
|
46
|
+
probeError?: string;
|
|
47
|
+
capabilities: FleetDeployTargetCap[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface FleetDeployOptions {
|
|
51
|
+
app: string;
|
|
52
|
+
stack?: string;
|
|
53
|
+
targets: string[];
|
|
54
|
+
devices: FleetDeployDevice[];
|
|
55
|
+
warnings?: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TARGET_LABELS: Record<string, string> = {
|
|
59
|
+
testflight: 'TestFlight',
|
|
60
|
+
playstore: 'Play Store',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
interface DeployPanelProps {
|
|
64
|
+
/** Called when the user taps Cancel or after a successful deploy starts. */
|
|
65
|
+
onClose: () => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type SelectedTarget = 'testflight' | 'playstore' | 'both';
|
|
69
|
+
|
|
70
|
+
export const DeployPanel: React.FC<DeployPanelProps> = ({ onClose }) => {
|
|
71
|
+
const [options, setOptions] = useState<FleetDeployOptions | null>(null);
|
|
72
|
+
const [loading, setLoading] = useState(true);
|
|
73
|
+
const [error, setError] = useState<string | null>(null);
|
|
74
|
+
const [status, setStatus] = useState<string | null>(null);
|
|
75
|
+
const [statusTone, setStatusTone] = useState<'progress' | 'success' | 'error'>('progress');
|
|
76
|
+
const [selected, setSelected] = useState<SelectedTarget>('both');
|
|
77
|
+
const [shipping, setShipping] = useState(false);
|
|
78
|
+
|
|
79
|
+
const resolveAppSlug = useCallback((): string => {
|
|
80
|
+
const cfg = YaverFeedback.getConfig();
|
|
81
|
+
const explicit = (cfg as { deployAppSlug?: string } | null | undefined)?.deployAppSlug;
|
|
82
|
+
if (explicit && explicit.trim().length > 0) return explicit.trim();
|
|
83
|
+
// Best-effort fallback: bundleId's last dot-segment. iOS gives us
|
|
84
|
+
// `io.yaver.sfmg`; Android gives the same shape. The agent's
|
|
85
|
+
// workspace manifest typically names apps after the project basename
|
|
86
|
+
// which is usually the same word, but the user can override via
|
|
87
|
+
// config.deployAppSlug if it isn't.
|
|
88
|
+
const bundleId = (cfg as { bundleId?: string } | null | undefined)?.bundleId;
|
|
89
|
+
if (bundleId) {
|
|
90
|
+
const tail = bundleId.split('.').pop();
|
|
91
|
+
if (tail) return tail;
|
|
92
|
+
}
|
|
93
|
+
return 'main';
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const baseAuthHeaders = useCallback((): Record<string, string> => {
|
|
97
|
+
const cfg = YaverFeedback.getConfig();
|
|
98
|
+
const headers: Record<string, string> = {};
|
|
99
|
+
if (cfg?.authToken) headers.Authorization = `Bearer ${cfg.authToken}`;
|
|
100
|
+
const relay = YaverFeedback.getRelayPassword();
|
|
101
|
+
if (relay) headers['X-Relay-Password'] = relay;
|
|
102
|
+
return headers;
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
const fetchOptions = useCallback(async () => {
|
|
106
|
+
setLoading(true);
|
|
107
|
+
setError(null);
|
|
108
|
+
const cfg = YaverFeedback.getConfig();
|
|
109
|
+
if (!cfg?.agentUrl) {
|
|
110
|
+
setError('Not connected to a Yaver agent yet.');
|
|
111
|
+
setLoading(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const app = resolveAppSlug();
|
|
115
|
+
const url = `${cfg.agentUrl.replace(/\/$/, '')}/fleet/deploy-options?app=${encodeURIComponent(app)}`;
|
|
116
|
+
try {
|
|
117
|
+
const resp = await fetch(url, { headers: baseAuthHeaders() });
|
|
118
|
+
if (!resp.ok) {
|
|
119
|
+
const text = await resp.text().catch(() => '');
|
|
120
|
+
throw new Error(`fetch failed (${resp.status}): ${text || resp.statusText}`);
|
|
121
|
+
}
|
|
122
|
+
const json = (await resp.json()) as FleetDeployOptions;
|
|
123
|
+
setOptions(json);
|
|
124
|
+
} catch (err: unknown) {
|
|
125
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
126
|
+
} finally {
|
|
127
|
+
setLoading(false);
|
|
128
|
+
}
|
|
129
|
+
}, [baseAuthHeaders, resolveAppSlug]);
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
void fetchOptions();
|
|
133
|
+
}, [fetchOptions]);
|
|
134
|
+
|
|
135
|
+
const pickedTargets = (): string[] => {
|
|
136
|
+
switch (selected) {
|
|
137
|
+
case 'testflight':
|
|
138
|
+
return ['testflight'];
|
|
139
|
+
case 'playstore':
|
|
140
|
+
return ['playstore'];
|
|
141
|
+
default:
|
|
142
|
+
return ['testflight', 'playstore'];
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const machineRow = (d: FleetDeployDevice) => {
|
|
147
|
+
const targets = pickedTargets();
|
|
148
|
+
const blockers: string[] = [];
|
|
149
|
+
let allOK = true;
|
|
150
|
+
for (const t of targets) {
|
|
151
|
+
const cap = d.capabilities.find((c) => c.target === t);
|
|
152
|
+
if (!cap) {
|
|
153
|
+
allOK = false;
|
|
154
|
+
blockers.push(`${TARGET_LABELS[t] ?? t}: no capability data`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!cap.ok) {
|
|
158
|
+
allOK = false;
|
|
159
|
+
if (cap.reason) blockers.push(`${TARGET_LABELS[t] ?? t}: ${cap.reason}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!d.probed && allOK) {
|
|
163
|
+
allOK = false;
|
|
164
|
+
blockers.push(d.probeError || "couldn't reach this machine");
|
|
165
|
+
}
|
|
166
|
+
const label = (d.alias && d.alias.length > 0 ? d.alias : d.name) +
|
|
167
|
+
(d.isLocal ? ' (this phone’s primary)' : '');
|
|
168
|
+
return (
|
|
169
|
+
<Pressable
|
|
170
|
+
key={d.deviceId}
|
|
171
|
+
disabled={!allOK || shipping}
|
|
172
|
+
onPress={() => triggerDeploy(d.deviceId)}
|
|
173
|
+
style={({ pressed }) => [
|
|
174
|
+
styles.row,
|
|
175
|
+
!allOK && styles.rowDisabled,
|
|
176
|
+
pressed && allOK && styles.rowPressed,
|
|
177
|
+
]}
|
|
178
|
+
>
|
|
179
|
+
<Text style={styles.rowName}>{label}</Text>
|
|
180
|
+
<Text style={[styles.rowMeta, !allOK && styles.rowMetaWarning]}>
|
|
181
|
+
{d.platform} {'·'} {allOK ? 'ready' : blockers.join(' · ')}
|
|
182
|
+
</Text>
|
|
183
|
+
</Pressable>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const triggerDeploy = async (machine: string) => {
|
|
188
|
+
if (!options) return;
|
|
189
|
+
setShipping(true);
|
|
190
|
+
setStatus(`starting deploy on ${prettyMachineName(machine)}…`);
|
|
191
|
+
setStatusTone('progress');
|
|
192
|
+
const cfg = YaverFeedback.getConfig();
|
|
193
|
+
if (!cfg?.agentUrl) {
|
|
194
|
+
setStatus('Not connected to a Yaver agent yet.');
|
|
195
|
+
setStatusTone('error');
|
|
196
|
+
setShipping(false);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const targets = pickedTargets();
|
|
200
|
+
const body: Record<string, unknown> = {
|
|
201
|
+
app: options.app,
|
|
202
|
+
machine,
|
|
203
|
+
};
|
|
204
|
+
if (targets.length === 1) {
|
|
205
|
+
body.target = targets[0];
|
|
206
|
+
} else {
|
|
207
|
+
body.targets = targets;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const resp = await fetch(`${cfg.agentUrl.replace(/\/$/, '')}/deploy/ship`, {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: { ...baseAuthHeaders(), 'Content-Type': 'application/json' },
|
|
213
|
+
body: JSON.stringify(body),
|
|
214
|
+
});
|
|
215
|
+
if (!resp.ok) {
|
|
216
|
+
const text = await resp.text().catch(() => '');
|
|
217
|
+
throw new Error(`ship failed (${resp.status}): ${text || resp.statusText}`);
|
|
218
|
+
}
|
|
219
|
+
setStatus('deploy started — track progress in the desktop / web tab');
|
|
220
|
+
setStatusTone('success');
|
|
221
|
+
// Auto-close shortly so the user can keep using their app. Keep
|
|
222
|
+
// this in sync with the iOS / Android pane delays.
|
|
223
|
+
setTimeout(() => onClose(), 1600);
|
|
224
|
+
} catch (err: unknown) {
|
|
225
|
+
setStatus(err instanceof Error ? err.message : String(err));
|
|
226
|
+
setStatusTone('error');
|
|
227
|
+
} finally {
|
|
228
|
+
setShipping(false);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const prettyMachineName = (id: string): string => {
|
|
233
|
+
const d = options?.devices.find((x) => x.deviceId === id);
|
|
234
|
+
if (!d) return id;
|
|
235
|
+
return d.alias && d.alias.length > 0 ? d.alias : d.name;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const targetButton = (value: SelectedTarget, label: string) => (
|
|
239
|
+
<Pressable
|
|
240
|
+
key={value}
|
|
241
|
+
onPress={() => setSelected(value)}
|
|
242
|
+
style={[styles.segBtn, selected === value && styles.segBtnSelected]}
|
|
243
|
+
>
|
|
244
|
+
<Text style={[styles.segText, selected === value && styles.segTextSelected]}>{label}</Text>
|
|
245
|
+
</Pressable>
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<View style={styles.container}>
|
|
250
|
+
<View style={styles.headerRow}>
|
|
251
|
+
<Text style={styles.title}>Deploy</Text>
|
|
252
|
+
<Pressable onPress={onClose} hitSlop={10}>
|
|
253
|
+
<Text style={styles.closeIcon}>✕</Text>
|
|
254
|
+
</Pressable>
|
|
255
|
+
</View>
|
|
256
|
+
<Text style={styles.subtitle}>
|
|
257
|
+
{loading
|
|
258
|
+
? 'loading machines…'
|
|
259
|
+
: options
|
|
260
|
+
? `${options.devices.length} machine${options.devices.length === 1 ? '' : 's'} — pick a target, then tap to deploy`
|
|
261
|
+
: 'no data yet'}
|
|
262
|
+
</Text>
|
|
263
|
+
|
|
264
|
+
<View style={styles.segment}>
|
|
265
|
+
{targetButton('testflight', 'TestFlight')}
|
|
266
|
+
{targetButton('playstore', 'Play Store')}
|
|
267
|
+
{targetButton('both', 'Both')}
|
|
268
|
+
</View>
|
|
269
|
+
|
|
270
|
+
{loading ? (
|
|
271
|
+
<View style={styles.loading}>
|
|
272
|
+
<ActivityIndicator color="rgba(255,255,255,0.6)" />
|
|
273
|
+
</View>
|
|
274
|
+
) : error ? (
|
|
275
|
+
<Text style={styles.error}>{error}</Text>
|
|
276
|
+
) : options ? (
|
|
277
|
+
<ScrollView style={styles.list}>{options.devices.map(machineRow)}</ScrollView>
|
|
278
|
+
) : null}
|
|
279
|
+
|
|
280
|
+
{status && (
|
|
281
|
+
<Text
|
|
282
|
+
style={[
|
|
283
|
+
styles.status,
|
|
284
|
+
statusTone === 'success' && styles.statusSuccess,
|
|
285
|
+
statusTone === 'error' && styles.statusError,
|
|
286
|
+
]}
|
|
287
|
+
>
|
|
288
|
+
{status}
|
|
289
|
+
</Text>
|
|
290
|
+
)}
|
|
291
|
+
</View>
|
|
292
|
+
);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const styles = StyleSheet.create({
|
|
296
|
+
container: {
|
|
297
|
+
backgroundColor: 'rgba(14,12,28,0.92)',
|
|
298
|
+
borderRadius: 16,
|
|
299
|
+
paddingHorizontal: 16,
|
|
300
|
+
paddingTop: 14,
|
|
301
|
+
paddingBottom: 18,
|
|
302
|
+
marginVertical: 8,
|
|
303
|
+
borderWidth: 1,
|
|
304
|
+
borderColor: 'rgba(255,255,255,0.08)',
|
|
305
|
+
},
|
|
306
|
+
headerRow: {
|
|
307
|
+
flexDirection: 'row',
|
|
308
|
+
alignItems: 'center',
|
|
309
|
+
justifyContent: 'space-between',
|
|
310
|
+
},
|
|
311
|
+
title: {
|
|
312
|
+
color: '#fff',
|
|
313
|
+
fontSize: 16,
|
|
314
|
+
fontWeight: '600',
|
|
315
|
+
},
|
|
316
|
+
closeIcon: {
|
|
317
|
+
color: 'rgba(255,255,255,0.55)',
|
|
318
|
+
fontSize: 18,
|
|
319
|
+
paddingHorizontal: 4,
|
|
320
|
+
},
|
|
321
|
+
subtitle: {
|
|
322
|
+
color: 'rgba(255,255,255,0.55)',
|
|
323
|
+
fontSize: 12,
|
|
324
|
+
marginTop: 2,
|
|
325
|
+
},
|
|
326
|
+
segment: {
|
|
327
|
+
flexDirection: 'row',
|
|
328
|
+
backgroundColor: 'rgba(255,255,255,0.08)',
|
|
329
|
+
borderRadius: 10,
|
|
330
|
+
padding: 3,
|
|
331
|
+
marginTop: 14,
|
|
332
|
+
},
|
|
333
|
+
segBtn: {
|
|
334
|
+
flex: 1,
|
|
335
|
+
alignItems: 'center',
|
|
336
|
+
paddingVertical: 7,
|
|
337
|
+
borderRadius: 8,
|
|
338
|
+
},
|
|
339
|
+
segBtnSelected: {
|
|
340
|
+
backgroundColor: 'rgba(127,140,247,0.65)',
|
|
341
|
+
},
|
|
342
|
+
segText: {
|
|
343
|
+
color: 'rgba(255,255,255,0.65)',
|
|
344
|
+
fontSize: 13,
|
|
345
|
+
fontWeight: '500',
|
|
346
|
+
},
|
|
347
|
+
segTextSelected: {
|
|
348
|
+
color: '#fff',
|
|
349
|
+
fontWeight: '600',
|
|
350
|
+
},
|
|
351
|
+
loading: {
|
|
352
|
+
paddingVertical: 32,
|
|
353
|
+
alignItems: 'center',
|
|
354
|
+
},
|
|
355
|
+
error: {
|
|
356
|
+
color: 'rgb(255,115,115)',
|
|
357
|
+
fontSize: 12,
|
|
358
|
+
marginTop: 14,
|
|
359
|
+
},
|
|
360
|
+
list: {
|
|
361
|
+
marginTop: 14,
|
|
362
|
+
maxHeight: 260,
|
|
363
|
+
},
|
|
364
|
+
row: {
|
|
365
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
366
|
+
borderRadius: 12,
|
|
367
|
+
paddingHorizontal: 14,
|
|
368
|
+
paddingVertical: 12,
|
|
369
|
+
marginBottom: 8,
|
|
370
|
+
},
|
|
371
|
+
rowPressed: {
|
|
372
|
+
backgroundColor: 'rgba(255,255,255,0.10)',
|
|
373
|
+
},
|
|
374
|
+
rowDisabled: {
|
|
375
|
+
opacity: 0.55,
|
|
376
|
+
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
377
|
+
},
|
|
378
|
+
rowName: {
|
|
379
|
+
color: '#fff',
|
|
380
|
+
fontSize: 15,
|
|
381
|
+
fontWeight: '600',
|
|
382
|
+
},
|
|
383
|
+
rowMeta: {
|
|
384
|
+
color: 'rgba(255,255,255,0.55)',
|
|
385
|
+
fontSize: 12,
|
|
386
|
+
marginTop: 2,
|
|
387
|
+
},
|
|
388
|
+
rowMetaWarning: {
|
|
389
|
+
color: 'rgb(255,178,115)',
|
|
390
|
+
},
|
|
391
|
+
status: {
|
|
392
|
+
color: 'rgba(255,255,255,0.55)',
|
|
393
|
+
fontSize: 12,
|
|
394
|
+
marginTop: 12,
|
|
395
|
+
textAlign: 'center',
|
|
396
|
+
},
|
|
397
|
+
statusSuccess: {
|
|
398
|
+
color: 'rgb(34,197,94)',
|
|
399
|
+
},
|
|
400
|
+
statusError: {
|
|
401
|
+
color: 'rgb(255,115,115)',
|
|
402
|
+
},
|
|
403
|
+
});
|