react-native-inapp-inspector 2.3.10 → 2.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/commonjs/components/Inspector/DebuggingTab.d.ts +3 -0
  2. package/dist/commonjs/components/Inspector/DebuggingTab.js +682 -0
  3. package/dist/commonjs/components/Inspector/MainScreen.js +2 -0
  4. package/dist/commonjs/components/Inspector/SettingsPanel.js +12 -0
  5. package/dist/commonjs/components/Inspector/TabBar.js +9 -0
  6. package/dist/commonjs/components/NetworkIcons.d.ts +1 -0
  7. package/dist/commonjs/components/NetworkIcons.js +8 -1
  8. package/dist/commonjs/components/QRCodeView.d.ts +9 -0
  9. package/dist/commonjs/components/QRCodeView.js +79 -0
  10. package/dist/commonjs/constants/version.d.ts +1 -1
  11. package/dist/commonjs/constants/version.js +1 -1
  12. package/dist/commonjs/helpers/qrGenerator.d.ts +2 -0
  13. package/dist/commonjs/helpers/qrGenerator.js +170 -0
  14. package/dist/commonjs/index.js +2 -0
  15. package/dist/commonjs/types/enums.d.ts +2 -0
  16. package/dist/commonjs/types/enums.js +2 -0
  17. package/dist/esm/components/Inspector/DebuggingTab.d.ts +3 -0
  18. package/dist/esm/components/Inspector/DebuggingTab.js +642 -0
  19. package/dist/esm/components/Inspector/MainScreen.js +2 -0
  20. package/dist/esm/components/Inspector/SettingsPanel.js +13 -1
  21. package/dist/esm/components/Inspector/TabBar.js +10 -1
  22. package/dist/esm/components/NetworkIcons.d.ts +1 -0
  23. package/dist/esm/components/NetworkIcons.js +6 -0
  24. package/dist/esm/components/QRCodeView.d.ts +9 -0
  25. package/dist/esm/components/QRCodeView.js +42 -0
  26. package/dist/esm/constants/version.d.ts +1 -1
  27. package/dist/esm/constants/version.js +1 -1
  28. package/dist/esm/helpers/qrGenerator.d.ts +2 -0
  29. package/dist/esm/helpers/qrGenerator.js +167 -0
  30. package/dist/esm/index.js +2 -0
  31. package/dist/esm/types/enums.d.ts +2 -0
  32. package/dist/esm/types/enums.js +2 -0
  33. package/package.json +1 -1
  34. package/src/components/Inspector/DebuggingTab.tsx +755 -0
  35. package/src/components/Inspector/MainScreen.tsx +2 -0
  36. package/src/components/Inspector/SettingsPanel.tsx +20 -0
  37. package/src/components/Inspector/TabBar.tsx +11 -0
  38. package/src/components/NetworkIcons.tsx +39 -0
  39. package/src/components/QRCodeView.tsx +71 -0
  40. package/src/constants/version.ts +1 -1
  41. package/src/helpers/qrGenerator.ts +184 -0
  42. package/src/index.tsx +2 -0
  43. package/src/types/enums.ts +2 -0
@@ -0,0 +1,642 @@
1
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
2
+ import { View, Text, ScrollView, StyleSheet, TextInput, TouchableOpacity, NativeModules, Share, } from 'react-native';
3
+ import { AppColors } from '../../styles/AppColors';
4
+ import { AppFonts } from '../../styles/AppFonts';
5
+ import QRCodeView from '../QRCodeView';
6
+ import TouchableScale from '../TouchableScale';
7
+ import { copyToClipboard } from '../../helpers';
8
+ import { showToast } from '../../helpers/toast';
9
+ import { useTranslation } from '../../i18n';
10
+ import { BoltIcon, SmartphoneIcon, PackageIcon, RepeatIcon, } from '../NetworkIcons';
11
+ const COMMON_PORTS = [8081, 8082, 8083, 19000];
12
+ export const DebuggingTab = () => {
13
+ const { t } = useTranslation();
14
+ const detectedConfig = useMemo(() => {
15
+ const scriptURL = NativeModules.SourceCode?.scriptURL || '';
16
+ let host = 'localhost';
17
+ let port = 8081;
18
+ try {
19
+ if (scriptURL.includes('://')) {
20
+ const urlWithoutScheme = scriptURL.split('://')[1];
21
+ const hostAndPort = urlWithoutScheme.split('/')[0];
22
+ if (hostAndPort.includes(':')) {
23
+ const parts = hostAndPort.split(':');
24
+ host = parts[0];
25
+ const parsedPort = parseInt(parts[1], 10);
26
+ if (!isNaN(parsedPort))
27
+ port = parsedPort;
28
+ }
29
+ else {
30
+ host = hostAndPort;
31
+ }
32
+ }
33
+ }
34
+ catch { }
35
+ return { host, port, scriptURL };
36
+ }, []);
37
+ const [hostIp, setHostIp] = useState(() => {
38
+ if (detectedConfig.host === 'localhost' ||
39
+ detectedConfig.host === '127.0.0.1' ||
40
+ detectedConfig.host === '10.0.2.2') {
41
+ return '192.168.1.15';
42
+ }
43
+ return detectedConfig.host;
44
+ });
45
+ const [selectedPort, setSelectedPort] = useState(detectedConfig.port);
46
+ const [apkPath, setApkPath] = useState('app-debug.apk');
47
+ const [activeQrTab, setActiveQrTab] = useState('metro');
48
+ const [isAutoDetecting, setIsAutoDetecting] = useState(false);
49
+ const [portsStatus, setPortsStatus] = useState({});
50
+ const probePorts = useCallback(async () => {
51
+ setIsAutoDetecting(true);
52
+ const results = {};
53
+ for (const p of COMMON_PORTS) {
54
+ results[p] = { port: p, status: 'checking' };
55
+ }
56
+ setPortsStatus({ ...results });
57
+ let foundActivePort = null;
58
+ for (const p of COMMON_PORTS) {
59
+ const startTime = Date.now();
60
+ try {
61
+ const controller = new AbortController();
62
+ const timeoutId = setTimeout(() => controller.abort(), 1500);
63
+ const res = await fetch(`http://localhost:${p}/status`, {
64
+ signal: controller.signal,
65
+ });
66
+ clearTimeout(timeoutId);
67
+ const text = await res.text();
68
+ const latency = Date.now() - startTime;
69
+ if (res.ok && text.includes('packager-status:running')) {
70
+ results[p] = { port: p, status: 'active', latencyMs: latency };
71
+ if (!foundActivePort)
72
+ foundActivePort = p;
73
+ }
74
+ else {
75
+ results[p] = { port: p, status: 'inactive' };
76
+ }
77
+ }
78
+ catch {
79
+ results[p] = { port: p, status: 'inactive' };
80
+ }
81
+ setPortsStatus({ ...results });
82
+ }
83
+ if (foundActivePort) {
84
+ setSelectedPort(foundActivePort);
85
+ showToast(`Detected Metro running on Port ${foundActivePort}!`);
86
+ }
87
+ setIsAutoDetecting(false);
88
+ }, []);
89
+ useEffect(() => {
90
+ probePorts();
91
+ }, [probePorts]);
92
+ const metroBundleUrl = `http://${hostIp}:${selectedPort}/index.bundle?platform=android&dev=true`;
93
+ const apkDownloadUrl = `http://${hostIp}:${selectedPort}/${apkPath}`;
94
+ const devSettingsHost = `${hostIp}:${selectedPort}`;
95
+ const currentQrValue = useMemo(() => {
96
+ if (activeQrTab === 'apk')
97
+ return apkDownloadUrl;
98
+ if (activeQrTab === 'host')
99
+ return devSettingsHost;
100
+ return metroBundleUrl;
101
+ }, [activeQrTab, apkDownloadUrl, devSettingsHost, metroBundleUrl]);
102
+ const activePortInfo = portsStatus[selectedPort];
103
+ return (<ScrollView style={styles.container} contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
104
+
105
+ <View style={styles.serverCard}>
106
+ <View style={styles.serverCardTop}>
107
+ <View style={styles.serverStatusLeft}>
108
+ <View style={[
109
+ styles.statusDot,
110
+ {
111
+ backgroundColor: activePortInfo?.status === 'active'
112
+ ? AppColors.greenColor
113
+ : activePortInfo?.status === 'checking'
114
+ ? AppColors.warningIconGold
115
+ : AppColors.errorColor,
116
+ },
117
+ ]}/>
118
+ <View>
119
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
120
+ <Text style={styles.serverTitle}>Metro Dev Server</Text>
121
+ <View style={styles.portPill}>
122
+ <Text style={styles.portPillText}>Port {selectedPort}</Text>
123
+ </View>
124
+ </View>
125
+ <Text style={styles.serverSubtitle}>
126
+ {activePortInfo?.status === 'active'
127
+ ? `🟢 Active & Bundling (${activePortInfo.latencyMs}ms)`
128
+ : activePortInfo?.status === 'checking'
129
+ ? '🟡 Probing Metro Ports...'
130
+ : '🔴 Standby / Select active port'}
131
+ </Text>
132
+ </View>
133
+ </View>
134
+
135
+ <TouchableScale onPress={probePorts} style={styles.scanBtn} hitSlop={8}>
136
+ <RepeatIcon size={12} color={AppColors.purple}/>
137
+ <Text style={styles.scanBtnText}>Re-scan</Text>
138
+ </TouchableScale>
139
+ </View>
140
+
141
+
142
+ <View style={styles.portsRow}>
143
+ {COMMON_PORTS.map(p => {
144
+ const isSelected = selectedPort === p;
145
+ const pInfo = portsStatus[p];
146
+ const isActive = pInfo?.status === 'active';
147
+ return (<TouchableOpacity key={p} activeOpacity={0.7} onPress={() => setSelectedPort(p)} style={[
148
+ styles.portChip,
149
+ isSelected && styles.portChipSelected,
150
+ isActive && { borderColor: AppColors.greenColor },
151
+ ]}>
152
+ <View style={[
153
+ styles.portMiniDot,
154
+ {
155
+ backgroundColor: isActive
156
+ ? AppColors.greenColor
157
+ : AppColors.grayTextWeak,
158
+ },
159
+ ]}/>
160
+ <Text style={[
161
+ styles.portChipText,
162
+ isSelected && styles.portChipTextSelected,
163
+ ]}>
164
+ {p}
165
+ </Text>
166
+ {isActive && (<View style={styles.liveBadge}>
167
+ <Text style={styles.liveBadgeText}>LIVE</Text>
168
+ </View>)}
169
+ </TouchableOpacity>);
170
+ })}
171
+ </View>
172
+ </View>
173
+
174
+
175
+ <View style={styles.card}>
176
+ <View style={styles.cardHeader}>
177
+ <Text style={styles.cardTitle}>HOST IP & BUNDLE LOCATION</Text>
178
+ <TouchableOpacity onPress={() => {
179
+ if (detectedConfig.host !== 'localhost' &&
180
+ detectedConfig.host !== '127.0.0.1' &&
181
+ detectedConfig.host !== '10.0.2.2') {
182
+ setHostIp(detectedConfig.host);
183
+ showToast(`Reset to detected IP: ${detectedConfig.host}`);
184
+ }
185
+ }}>
186
+ <Text style={styles.headerActionText}>Reset to Detected</Text>
187
+ </TouchableOpacity>
188
+ </View>
189
+
190
+ <View style={styles.inputContainer}>
191
+ <Text style={styles.inputPrefix}>http://</Text>
192
+ <TextInput style={styles.input} value={hostIp} onChangeText={setHostIp} placeholder="192.168.1.15" placeholderTextColor={AppColors.grayTextWeak} autoCapitalize="none" autoCorrect={false}/>
193
+ <Text style={styles.inputSuffix}>:{selectedPort}</Text>
194
+ </View>
195
+ <Text style={styles.inputHint}>
196
+ 💡 Ensure your physical Android device and Mac are connected to the same Wi-Fi network.
197
+ </Text>
198
+ </View>
199
+
200
+
201
+ <View style={styles.qrCard}>
202
+
203
+ <View style={styles.qrTabsRow}>
204
+ <TouchableOpacity activeOpacity={0.7} onPress={() => setActiveQrTab('metro')} style={[
205
+ styles.qrTab,
206
+ activeQrTab === 'metro' && styles.qrTabActive,
207
+ ]}>
208
+ <BoltIcon size={13} color={activeQrTab === 'metro' ? AppColors.white : AppColors.grayText}/>
209
+ <Text style={[
210
+ styles.qrTabText,
211
+ activeQrTab === 'metro' && styles.qrTabTextActive,
212
+ ]}>
213
+ Metro Live Reload
214
+ </Text>
215
+ </TouchableOpacity>
216
+
217
+ <TouchableOpacity activeOpacity={0.7} onPress={() => setActiveQrTab('apk')} style={[
218
+ styles.qrTab,
219
+ activeQrTab === 'apk' && styles.qrTabActive,
220
+ ]}>
221
+ <PackageIcon size={13} color={activeQrTab === 'apk' ? AppColors.white : AppColors.grayText}/>
222
+ <Text style={[
223
+ styles.qrTabText,
224
+ activeQrTab === 'apk' && styles.qrTabTextActive,
225
+ ]}>
226
+ Install Debug APK
227
+ </Text>
228
+ </TouchableOpacity>
229
+
230
+ <TouchableOpacity activeOpacity={0.7} onPress={() => setActiveQrTab('host')} style={[
231
+ styles.qrTab,
232
+ activeQrTab === 'host' && styles.qrTabActive,
233
+ ]}>
234
+ <SmartphoneIcon size={13} color={activeQrTab === 'host' ? AppColors.white : AppColors.grayText}/>
235
+ <Text style={[
236
+ styles.qrTabText,
237
+ activeQrTab === 'host' && styles.qrTabTextActive,
238
+ ]}>
239
+ Dev Host Info
240
+ </Text>
241
+ </TouchableOpacity>
242
+ </View>
243
+
244
+
245
+ <View style={styles.qrWrapper}>
246
+ <QRCodeView value={currentQrValue} size={190} color={AppColors.primaryBlack} backgroundColor={AppColors.white}/>
247
+
248
+ <View style={styles.qrInfoBox}>
249
+ <Text style={styles.qrTargetTitle}>
250
+ {activeQrTab === 'apk'
251
+ ? '📦 Direct APK Download URL'
252
+ : activeQrTab === 'host'
253
+ ? '📱 Dev Settings Bundle Host'
254
+ : '⚡ Live Metro Fast-Refresh Endpoint'}
255
+ </Text>
256
+ <Text style={styles.qrTargetUrl} numberOfLines={2} ellipsizeMode="middle">
257
+ {currentQrValue}
258
+ </Text>
259
+ </View>
260
+ </View>
261
+
262
+
263
+ <View style={styles.qrActionsRow}>
264
+ <TouchableScale onPress={() => copyToClipboard(currentQrValue, 'QR Link')} style={styles.actionBtn}>
265
+ <Text style={styles.actionBtnText}>Copy Link</Text>
266
+ </TouchableScale>
267
+
268
+ <TouchableScale onPress={() => {
269
+ Share.share({
270
+ message: currentQrValue,
271
+ title: 'Metro Debug Link',
272
+ });
273
+ }} style={[styles.actionBtn, styles.actionBtnPrimary]}>
274
+ <Text style={styles.actionBtnPrimaryText}>Share to Device</Text>
275
+ </TouchableScale>
276
+ </View>
277
+ </View>
278
+
279
+
280
+ <View style={styles.guideCard}>
281
+ <Text style={styles.guideHeading}>📱 HOW TO CONNECT ANDROID DEVICE:</Text>
282
+
283
+ <View style={styles.stepRow}>
284
+ <View style={styles.stepNumberBadge}>
285
+ <Text style={styles.stepNumberText}>1</Text>
286
+ </View>
287
+ <View style={{ flex: 1 }}>
288
+ <Text style={styles.stepTitle}>Connect to Same Wi-Fi</Text>
289
+ <Text style={styles.stepDesc}>
290
+ Make sure your Android phone is connected to the same Wi-Fi router or Mac Mobile Hotspot.
291
+ </Text>
292
+ </View>
293
+ </View>
294
+
295
+ <View style={styles.stepRow}>
296
+ <View style={styles.stepNumberBadge}>
297
+ <Text style={styles.stepNumberText}>2</Text>
298
+ </View>
299
+ <View style={{ flex: 1 }}>
300
+ <Text style={styles.stepTitle}>Scan to Install / Connect</Text>
301
+ <Text style={styles.stepDesc}>
302
+ Open Android Camera or browser, scan the QR code above to download the APK or connect Metro.
303
+ </Text>
304
+ </View>
305
+ </View>
306
+
307
+ <View style={styles.stepRow}>
308
+ <View style={styles.stepNumberBadge}>
309
+ <Text style={styles.stepNumberText}>3</Text>
310
+ </View>
311
+ <View style={{ flex: 1 }}>
312
+ <Text style={styles.stepTitle}>Simultaneous Live Fast-Refresh ⚡</Text>
313
+ <Text style={styles.stepDesc}>
314
+ Any changes saved in VS Code will immediately update <Text style={{ fontWeight: '700' }}>both the iOS Simulator and physical Android phone</Text> in real-time!
315
+ </Text>
316
+ </View>
317
+ </View>
318
+ </View>
319
+ </ScrollView>);
320
+ };
321
+ const styles = StyleSheet.create({
322
+ container: {
323
+ flex: 1,
324
+ backgroundColor: AppColors.contentBg,
325
+ },
326
+ content: {
327
+ padding: 12,
328
+ gap: 12,
329
+ paddingBottom: 40,
330
+ },
331
+ serverCard: {
332
+ backgroundColor: AppColors.primaryLight,
333
+ borderRadius: 14,
334
+ padding: 14,
335
+ borderWidth: 1,
336
+ borderColor: AppColors.grayBorderSecondary,
337
+ shadowColor: AppColors.black,
338
+ shadowOffset: { width: 0, height: 2 },
339
+ shadowOpacity: 0.05,
340
+ shadowRadius: 4,
341
+ elevation: 2,
342
+ gap: 12,
343
+ },
344
+ serverCardTop: {
345
+ flexDirection: 'row',
346
+ alignItems: 'center',
347
+ justifyContent: 'space-between',
348
+ },
349
+ serverStatusLeft: {
350
+ flexDirection: 'row',
351
+ alignItems: 'center',
352
+ gap: 10,
353
+ flex: 1,
354
+ },
355
+ statusDot: {
356
+ width: 12,
357
+ height: 12,
358
+ borderRadius: 6,
359
+ },
360
+ serverTitle: {
361
+ fontFamily: AppFonts.interBold,
362
+ fontSize: 15,
363
+ color: AppColors.primaryBlack,
364
+ },
365
+ serverSubtitle: {
366
+ fontFamily: AppFonts.interMedium,
367
+ fontSize: 11,
368
+ color: AppColors.grayText,
369
+ marginTop: 2,
370
+ },
371
+ portPill: {
372
+ backgroundColor: `${AppColors.purple}18`,
373
+ paddingHorizontal: 6,
374
+ paddingVertical: 2,
375
+ borderRadius: 6,
376
+ },
377
+ portPillText: {
378
+ fontFamily: AppFonts.interBold,
379
+ fontSize: 10,
380
+ color: AppColors.purple,
381
+ },
382
+ scanBtn: {
383
+ flexDirection: 'row',
384
+ alignItems: 'center',
385
+ gap: 4,
386
+ paddingHorizontal: 8,
387
+ paddingVertical: 4,
388
+ borderRadius: 6,
389
+ backgroundColor: `${AppColors.purple}14`,
390
+ borderWidth: 1,
391
+ borderColor: `${AppColors.purple}30`,
392
+ },
393
+ scanBtnText: {
394
+ fontFamily: AppFonts.interBold,
395
+ fontSize: 11,
396
+ color: AppColors.purple,
397
+ },
398
+ portsRow: {
399
+ flexDirection: 'row',
400
+ gap: 8,
401
+ },
402
+ portChip: {
403
+ flex: 1,
404
+ flexDirection: 'row',
405
+ alignItems: 'center',
406
+ justifyContent: 'center',
407
+ gap: 4,
408
+ paddingVertical: 7,
409
+ borderRadius: 8,
410
+ backgroundColor: AppColors.grayBackground,
411
+ borderWidth: 1,
412
+ borderColor: AppColors.grayBorderSecondary,
413
+ },
414
+ portChipSelected: {
415
+ backgroundColor: `${AppColors.purple}18`,
416
+ borderColor: AppColors.purple,
417
+ },
418
+ portMiniDot: {
419
+ width: 5,
420
+ height: 5,
421
+ borderRadius: 2.5,
422
+ },
423
+ portChipText: {
424
+ fontFamily: AppFonts.interBold,
425
+ fontSize: 11,
426
+ color: AppColors.grayTextStrong,
427
+ },
428
+ portChipTextSelected: {
429
+ color: AppColors.purple,
430
+ },
431
+ liveBadge: {
432
+ backgroundColor: AppColors.greenColor,
433
+ paddingHorizontal: 3,
434
+ paddingVertical: 0.5,
435
+ borderRadius: 3,
436
+ },
437
+ liveBadgeText: {
438
+ fontFamily: AppFonts.interBold,
439
+ fontSize: 7.5,
440
+ color: AppColors.white,
441
+ },
442
+ card: {
443
+ backgroundColor: AppColors.primaryLight,
444
+ borderRadius: 14,
445
+ padding: 14,
446
+ borderWidth: 1,
447
+ borderColor: AppColors.grayBorderSecondary,
448
+ shadowColor: AppColors.black,
449
+ shadowOffset: { width: 0, height: 2 },
450
+ shadowOpacity: 0.05,
451
+ shadowRadius: 4,
452
+ elevation: 2,
453
+ gap: 8,
454
+ },
455
+ cardHeader: {
456
+ flexDirection: 'row',
457
+ alignItems: 'center',
458
+ justifyContent: 'space-between',
459
+ },
460
+ cardTitle: {
461
+ fontFamily: AppFonts.interBold,
462
+ fontSize: 11,
463
+ color: AppColors.grayTextWeak,
464
+ letterSpacing: 0.6,
465
+ },
466
+ headerActionText: {
467
+ fontFamily: AppFonts.interBold,
468
+ fontSize: 11,
469
+ color: AppColors.purple,
470
+ },
471
+ inputContainer: {
472
+ flexDirection: 'row',
473
+ alignItems: 'center',
474
+ backgroundColor: AppColors.grayBackground,
475
+ borderRadius: 10,
476
+ borderWidth: 1,
477
+ borderColor: AppColors.grayBorderSecondary,
478
+ paddingHorizontal: 10,
479
+ height: 40,
480
+ },
481
+ inputPrefix: {
482
+ fontFamily: AppFonts.interMedium,
483
+ fontSize: 12.5,
484
+ color: AppColors.grayTextWeak,
485
+ },
486
+ input: {
487
+ flex: 1,
488
+ fontFamily: AppFonts.interBold,
489
+ fontSize: 13,
490
+ color: AppColors.primaryBlack,
491
+ paddingVertical: 0,
492
+ },
493
+ inputSuffix: {
494
+ fontFamily: AppFonts.interBold,
495
+ fontSize: 12.5,
496
+ color: AppColors.purple,
497
+ },
498
+ inputHint: {
499
+ fontFamily: AppFonts.interRegular,
500
+ fontSize: 11,
501
+ color: AppColors.grayTextWeak,
502
+ lineHeight: 15,
503
+ },
504
+ qrCard: {
505
+ backgroundColor: AppColors.primaryLight,
506
+ borderRadius: 14,
507
+ padding: 14,
508
+ borderWidth: 1,
509
+ borderColor: AppColors.grayBorderSecondary,
510
+ shadowColor: AppColors.black,
511
+ shadowOffset: { width: 0, height: 2 },
512
+ shadowOpacity: 0.05,
513
+ shadowRadius: 4,
514
+ elevation: 2,
515
+ alignItems: 'center',
516
+ gap: 14,
517
+ },
518
+ qrTabsRow: {
519
+ flexDirection: 'row',
520
+ backgroundColor: AppColors.grayBackground,
521
+ borderRadius: 10,
522
+ padding: 3,
523
+ width: '100%',
524
+ gap: 4,
525
+ },
526
+ qrTab: {
527
+ flex: 1,
528
+ flexDirection: 'row',
529
+ alignItems: 'center',
530
+ justifyContent: 'center',
531
+ gap: 4,
532
+ paddingVertical: 7,
533
+ borderRadius: 8,
534
+ },
535
+ qrTabActive: {
536
+ backgroundColor: AppColors.purple,
537
+ },
538
+ qrTabText: {
539
+ fontFamily: AppFonts.interMedium,
540
+ fontSize: 11,
541
+ color: AppColors.grayText,
542
+ },
543
+ qrTabTextActive: {
544
+ color: AppColors.white,
545
+ fontFamily: AppFonts.interBold,
546
+ },
547
+ qrWrapper: {
548
+ alignItems: 'center',
549
+ gap: 12,
550
+ },
551
+ qrInfoBox: {
552
+ alignItems: 'center',
553
+ maxWidth: 280,
554
+ gap: 2,
555
+ },
556
+ qrTargetTitle: {
557
+ fontFamily: AppFonts.interBold,
558
+ fontSize: 11.5,
559
+ color: AppColors.primaryBlack,
560
+ },
561
+ qrTargetUrl: {
562
+ fontFamily: AppFonts.interRegular,
563
+ fontSize: 10.5,
564
+ color: AppColors.skyBlue,
565
+ textAlign: 'center',
566
+ },
567
+ qrActionsRow: {
568
+ flexDirection: 'row',
569
+ gap: 10,
570
+ width: '100%',
571
+ },
572
+ actionBtn: {
573
+ flex: 1,
574
+ paddingVertical: 10,
575
+ borderRadius: 10,
576
+ borderWidth: 1,
577
+ borderColor: AppColors.grayBorderSecondary,
578
+ backgroundColor: AppColors.grayBackground,
579
+ alignItems: 'center',
580
+ justifyContent: 'center',
581
+ },
582
+ actionBtnText: {
583
+ fontFamily: AppFonts.interBold,
584
+ fontSize: 12,
585
+ color: AppColors.grayTextStrong,
586
+ },
587
+ actionBtnPrimary: {
588
+ backgroundColor: AppColors.purple,
589
+ borderColor: AppColors.purple,
590
+ },
591
+ actionBtnPrimaryText: {
592
+ fontFamily: AppFonts.interBold,
593
+ fontSize: 12,
594
+ color: AppColors.white,
595
+ },
596
+ guideCard: {
597
+ backgroundColor: AppColors.primaryLight,
598
+ borderRadius: 14,
599
+ padding: 14,
600
+ borderWidth: 1,
601
+ borderColor: AppColors.grayBorderSecondary,
602
+ gap: 10,
603
+ },
604
+ guideHeading: {
605
+ fontFamily: AppFonts.interBold,
606
+ fontSize: 11,
607
+ color: AppColors.grayTextWeak,
608
+ letterSpacing: 0.6,
609
+ },
610
+ stepRow: {
611
+ flexDirection: 'row',
612
+ alignItems: 'flex-start',
613
+ gap: 10,
614
+ },
615
+ stepNumberBadge: {
616
+ width: 22,
617
+ height: 22,
618
+ borderRadius: 11,
619
+ backgroundColor: `${AppColors.purple}18`,
620
+ alignItems: 'center',
621
+ justifyContent: 'center',
622
+ marginTop: 1,
623
+ },
624
+ stepNumberText: {
625
+ fontFamily: AppFonts.interBold,
626
+ fontSize: 11,
627
+ color: AppColors.purple,
628
+ },
629
+ stepTitle: {
630
+ fontFamily: AppFonts.interBold,
631
+ fontSize: 12.5,
632
+ color: AppColors.primaryBlack,
633
+ },
634
+ stepDesc: {
635
+ fontFamily: AppFonts.interRegular,
636
+ fontSize: 11,
637
+ color: AppColors.grayText,
638
+ lineHeight: 16,
639
+ marginTop: 1,
640
+ },
641
+ });
642
+ export default DebuggingTab;
@@ -19,6 +19,7 @@ import CrashTab from './CrashTab';
19
19
  import CrashDetail from './CrashDetail';
20
20
  import DeviceInfoTab from './DeviceInfoTab';
21
21
  import StorageTab from './StorageTab';
22
+ import DebuggingTab from './DebuggingTab';
22
23
  import SettingsPanel from './SettingsPanel';
23
24
  import NpmUpdateToast from './NpmUpdateToast';
24
25
  import Toast from '../Toast';
@@ -118,6 +119,7 @@ const MainScreen = () => {
118
119
  {activeTab === 'crash' && <CrashTab />}
119
120
  {activeTab === 'device' && <DeviceInfoTab />}
120
121
  {activeTab === 'storage' && <StorageTab />}
122
+ {activeTab === 'debugging' && <DebuggingTab />}
121
123
  </Animated.View>
122
124
 
123
125
 
@@ -17,7 +17,7 @@ import { clearPerformanceEvents } from '../../customHooks/performanceTracker';
17
17
  import { isReduxConnected } from '../../customHooks/reduxLogger';
18
18
  import { isAnalyticsConnected } from '../../customHooks/analyticsLogger';
19
19
  import { useTranslation } from '../../i18n';
20
- import { SignalIcon, TerminalIcon, AnalyticsIcon, SettingsIcon, SunIcon, MoonIcon, ScreenIcon, MotionIcon, LayersIcon, EyeIcon, CheckIcon, TrashIcon, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, ShieldAlertIcon, ForwardChevronIcon, ChevronDownIcon, NpmIcon, BoltIcon, BrainIcon, SmartphoneIcon, DatabaseIcon, } from '../NetworkIcons';
20
+ import { SignalIcon, TerminalIcon, AnalyticsIcon, SettingsIcon, SunIcon, MoonIcon, ScreenIcon, MotionIcon, LayersIcon, EyeIcon, CheckIcon, TrashIcon, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, ShieldAlertIcon, ForwardChevronIcon, ChevronDownIcon, NpmIcon, BoltIcon, BrainIcon, SmartphoneIcon, DatabaseIcon, QrCodeIcon, } from '../NetworkIcons';
21
21
  import { LIB_VERSION } from '../../constants';
22
22
  import { copyToClipboard } from '../../helpers';
23
23
  import { showToast } from '../../helpers/toast';
@@ -97,6 +97,13 @@ const SettingsPanel = () => {
97
97
  icon: 'storage',
98
98
  desc: 'AsyncStorage & MMKV key-value store viewer with full CRUD support',
99
99
  },
100
+ {
101
+ key: 'debugging',
102
+ label: 'Multi-Device Debugging',
103
+ category: 'diagnostic',
104
+ icon: 'debugging',
105
+ desc: 'QR Code bridge for direct Debug APK download & Metro live-reload sync',
106
+ },
100
107
  ];
101
108
  const [stagedTabVisibility, setStagedTabVisibility] = useState(() => ({
102
109
  apis: true,
@@ -108,6 +115,7 @@ const SettingsPanel = () => {
108
115
  crash: Boolean(tabVisibility?.crash),
109
116
  device: Boolean(tabVisibility?.device),
110
117
  storage: Boolean(tabVisibility?.storage),
118
+ debugging: Boolean(tabVisibility?.debugging ?? true),
111
119
  }));
112
120
  useEffect(() => {
113
121
  setStagedTabVisibility({
@@ -120,6 +128,7 @@ const SettingsPanel = () => {
120
128
  crash: Boolean(tabVisibility?.crash),
121
129
  device: Boolean(tabVisibility?.device),
122
130
  storage: Boolean(tabVisibility?.storage),
131
+ debugging: Boolean(tabVisibility?.debugging ?? true),
123
132
  });
124
133
  }, [tabVisibility]);
125
134
  const hasUnsavedChanges = useMemo(() => {
@@ -507,6 +516,9 @@ const SettingsPanel = () => {
507
516
  {moduleItem.icon === 'storage' && (<DatabaseIcon color={isChecked
508
517
  ? AppColors.purple
509
518
  : AppColors.grayTextWeak} size={16}/>)}
519
+ {moduleItem.icon === 'debugging' && (<QrCodeIcon color={isChecked
520
+ ? AppColors.purple
521
+ : AppColors.grayTextWeak} size={16}/>)}
510
522
  </View>
511
523
 
512
524