react-native-inapp-inspector 2.2.2 → 2.2.4

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 (159) hide show
  1. package/dist/commonjs/components/AnalyticsDetail.js +5 -1
  2. package/dist/commonjs/components/AnalyticsEventCard.js +2 -1
  3. package/dist/commonjs/components/DiffViewer.js +1 -1
  4. package/dist/commonjs/components/Inspector/BundleTab.js +22 -0
  5. package/dist/commonjs/components/Inspector/DeviceInfoTab.d.ts +3 -0
  6. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +835 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +12 -14
  8. package/dist/commonjs/components/Inspector/LogDetail.js +6 -3
  9. package/dist/commonjs/components/Inspector/MainScreen.js +7 -3
  10. package/dist/commonjs/components/Inspector/NetworkTab.js +92 -15
  11. package/dist/commonjs/components/Inspector/PerformanceTab.js +38 -12
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +440 -229
  13. package/dist/commonjs/components/Inspector/StorageTab.d.ts +3 -0
  14. package/dist/commonjs/components/Inspector/StorageTab.js +892 -0
  15. package/dist/commonjs/components/Inspector/TabBar.js +14 -0
  16. package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +11 -2
  17. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +4 -1
  18. package/dist/commonjs/components/NetworkIcons.d.ts +6 -0
  19. package/dist/commonjs/components/NetworkIcons.js +30 -1
  20. package/dist/commonjs/constants/version.d.ts +1 -1
  21. package/dist/commonjs/constants/version.js +1 -1
  22. package/dist/commonjs/customHooks/bundleAnalyzer.js +44 -22
  23. package/dist/commonjs/customHooks/storageInspector.d.ts +67 -0
  24. package/dist/commonjs/customHooks/storageInspector.js +486 -0
  25. package/dist/commonjs/i18n/locales/en.json +8 -2
  26. package/dist/commonjs/index.d.ts +1 -0
  27. package/dist/commonjs/index.js +16 -1
  28. package/dist/commonjs/storage.d.ts +5 -0
  29. package/dist/commonjs/storage.js +18 -0
  30. package/dist/commonjs/types/enums.d.ts +5 -0
  31. package/dist/commonjs/types/enums.js +5 -0
  32. package/dist/esm/components/AnalyticsDetail.js +6 -2
  33. package/dist/esm/components/AnalyticsEventCard.js +2 -1
  34. package/dist/esm/components/DiffViewer.js +1 -1
  35. package/dist/esm/components/Inspector/BundleTab.js +22 -0
  36. package/dist/esm/components/Inspector/DeviceInfoTab.d.ts +3 -0
  37. package/dist/esm/components/Inspector/DeviceInfoTab.js +796 -0
  38. package/dist/esm/components/Inspector/InspectorHeader.js +13 -15
  39. package/dist/esm/components/Inspector/LogDetail.js +7 -4
  40. package/dist/esm/components/Inspector/MainScreen.js +7 -3
  41. package/dist/esm/components/Inspector/NetworkTab.js +93 -16
  42. package/dist/esm/components/Inspector/PerformanceTab.js +39 -13
  43. package/dist/esm/components/Inspector/SettingsPanel.js +441 -230
  44. package/dist/esm/components/Inspector/StorageTab.d.ts +3 -0
  45. package/dist/esm/components/Inspector/StorageTab.js +853 -0
  46. package/dist/esm/components/Inspector/TabBar.js +15 -1
  47. package/dist/esm/components/Inspector/TelemetryConsentModal.js +11 -2
  48. package/dist/esm/components/Inspector/UpdateAvailableModal.js +4 -1
  49. package/dist/esm/components/NetworkIcons.d.ts +6 -0
  50. package/dist/esm/components/NetworkIcons.js +23 -0
  51. package/dist/esm/constants/version.d.ts +1 -1
  52. package/dist/esm/constants/version.js +1 -1
  53. package/dist/esm/customHooks/bundleAnalyzer.js +44 -22
  54. package/dist/esm/customHooks/storageInspector.d.ts +67 -0
  55. package/dist/esm/customHooks/storageInspector.js +469 -0
  56. package/dist/esm/i18n/locales/en.json +8 -2
  57. package/dist/esm/index.d.ts +1 -0
  58. package/dist/esm/index.js +5 -0
  59. package/dist/esm/storage.d.ts +5 -0
  60. package/dist/esm/storage.js +5 -0
  61. package/dist/esm/types/enums.d.ts +5 -0
  62. package/dist/esm/types/enums.js +5 -0
  63. package/package.json +9 -1
  64. package/src/analytics.ts +35 -0
  65. package/src/bundle.ts +25 -0
  66. package/src/components/AnalyticsDetail.tsx +870 -0
  67. package/src/components/AnalyticsEventCard.tsx +442 -0
  68. package/src/components/AnalyticsGraph.tsx +583 -0
  69. package/src/components/AnimatedEntrance.tsx +64 -0
  70. package/src/components/AppHeaderLogo.tsx +109 -0
  71. package/src/components/BrandCircleIcon.tsx +144 -0
  72. package/src/components/BrandSquareIcon.tsx +144 -0
  73. package/src/components/CodeSnippet.tsx +725 -0
  74. package/src/components/ConsoleLogCard.tsx +628 -0
  75. package/src/components/CopyButton.tsx +87 -0
  76. package/src/components/DiffViewer.tsx +82 -0
  77. package/src/components/DomainHeader.tsx +237 -0
  78. package/src/components/EmptyState.tsx +73 -0
  79. package/src/components/EndOfListFooter.tsx +100 -0
  80. package/src/components/ErrorBoundary.tsx +688 -0
  81. package/src/components/HeadersSection.tsx +192 -0
  82. package/src/components/HighlightText.tsx +100 -0
  83. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  84. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  85. package/src/components/Inspector/BundleTab.tsx +4760 -0
  86. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  87. package/src/components/Inspector/CrashDetail.tsx +941 -0
  88. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  89. package/src/components/Inspector/CrashTab.tsx +871 -0
  90. package/src/components/Inspector/DeviceInfoTab.tsx +1204 -0
  91. package/src/components/Inspector/FabLauncher.tsx +80 -0
  92. package/src/components/Inspector/InspectorContext.tsx +28 -0
  93. package/src/components/Inspector/InspectorHeader.tsx +972 -0
  94. package/src/components/Inspector/LogDetail.tsx +1431 -0
  95. package/src/components/Inspector/MainScreen.tsx +262 -0
  96. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  97. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  98. package/src/components/Inspector/NetworkTab.tsx +1183 -0
  99. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  100. package/src/components/Inspector/PerformanceTab.tsx +1928 -0
  101. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  102. package/src/components/Inspector/ReduxTab.tsx +954 -0
  103. package/src/components/Inspector/SettingsPanel.tsx +3447 -0
  104. package/src/components/Inspector/StorageTab.tsx +1048 -0
  105. package/src/components/Inspector/TabBar.tsx +207 -0
  106. package/src/components/Inspector/TelemetryConsentModal.tsx +416 -0
  107. package/src/components/Inspector/UpdateAvailableModal.tsx +557 -0
  108. package/src/components/JsonViewer.tsx +486 -0
  109. package/src/components/LogCard.tsx +491 -0
  110. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  111. package/src/components/MetaAccordion.tsx +340 -0
  112. package/src/components/MiniBarChart.tsx +42 -0
  113. package/src/components/MiniLineChart.tsx +33 -0
  114. package/src/components/NetworkIcons.tsx +2221 -0
  115. package/src/components/SectionHeader.tsx +113 -0
  116. package/src/components/SegmentedTabs.tsx +83 -0
  117. package/src/components/Slider.tsx +300 -0
  118. package/src/components/SourcePageCard.tsx +148 -0
  119. package/src/components/Toast.tsx +131 -0
  120. package/src/components/TouchableScale.tsx +91 -0
  121. package/src/components/TreeNode.tsx +186 -0
  122. package/src/console.ts +24 -0
  123. package/src/constants/index.ts +38 -0
  124. package/src/constants/version.ts +3 -0
  125. package/src/crash.ts +32 -0
  126. package/src/customHooks/analyticsLogger.ts +336 -0
  127. package/src/customHooks/bundleAnalyzer.ts +1256 -0
  128. package/src/customHooks/consoleLogger.ts +497 -0
  129. package/src/customHooks/crashHandler.ts +944 -0
  130. package/src/customHooks/logFilters.ts +32 -0
  131. package/src/customHooks/networkLogger.ts +419 -0
  132. package/src/customHooks/performanceTracker.ts +1014 -0
  133. package/src/customHooks/reduxLogger.ts +406 -0
  134. package/src/customHooks/storageInspector.ts +509 -0
  135. package/src/customHooks/useAccordion.tsx +60 -0
  136. package/src/decorators/index.ts +184 -0
  137. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  138. package/src/helpers/index.ts +860 -0
  139. package/src/helpers/memoryManager.ts +138 -0
  140. package/src/helpers/searchQueryParser.ts +283 -0
  141. package/src/helpers/settingsStore.ts +171 -0
  142. package/src/helpers/telemetry.ts +505 -0
  143. package/src/helpers/toast.ts +17 -0
  144. package/src/i18n/index.ts +69 -0
  145. package/src/i18n/locales/en.json +1058 -0
  146. package/src/index.tsx +2356 -0
  147. package/src/native/NativeInspector.ts +397 -0
  148. package/src/native/NativeNetworkInspector.ts +24 -0
  149. package/src/network.ts +22 -0
  150. package/src/performance.ts +38 -0
  151. package/src/redux.ts +23 -0
  152. package/src/storage.ts +18 -0
  153. package/src/styles/AppColors.ts +408 -0
  154. package/src/styles/AppFonts.ts +8 -0
  155. package/src/styles/common.ts +209 -0
  156. package/src/styles/index.ts +1570 -0
  157. package/src/types/enums.ts +199 -0
  158. package/src/types/index.ts +34 -0
  159. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,1204 @@
1
+ import React, {useState, useEffect, useMemo, useCallback} from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ ScrollView,
6
+ TextInput,
7
+ Dimensions,
8
+ Platform,
9
+ PixelRatio,
10
+ StatusBar,
11
+ StyleSheet,
12
+ ActivityIndicator,
13
+ NativeModules,
14
+ } from 'react-native';
15
+ import TouchableScale from '../TouchableScale';
16
+ import CopyButton from '../CopyButton';
17
+ import {AppColors} from '../../styles/AppColors';
18
+ import {AppFonts} from '../../styles/AppFonts';
19
+ import {useTranslation} from '../../i18n';
20
+ import {
21
+ SmartphoneIcon,
22
+ CpuIcon,
23
+ WifiIcon,
24
+ ScreenIcon,
25
+ LayersIcon,
26
+ KeyIcon,
27
+ ShieldCheckIcon,
28
+ SearchIcon,
29
+ ClearIcon,
30
+ CopyIcon,
31
+ CircleCheckIcon,
32
+ CircleAlertIcon,
33
+ BoltIcon,
34
+ CheckIcon,
35
+ SignalIcon,
36
+ BrainIcon,
37
+ AppleIcon,
38
+ AndroidIcon,
39
+ ExportIcon,
40
+ } from '../NetworkIcons';
41
+ import {
42
+ getNativeDeviceMetrics,
43
+ getNativeSystemMetrics,
44
+ NativeDeviceMetrics,
45
+ NativeSystemMetrics,
46
+ } from '../../native/NativeInspector';
47
+ import {copyToClipboard, getAppName} from '../../helpers';
48
+ import {showToast} from '../../helpers/toast';
49
+ import {LIB_VERSION} from '../../constants';
50
+
51
+ type DeviceSubTab =
52
+ | 'overview'
53
+ | 'hardware'
54
+ | 'network'
55
+ | 'display'
56
+ | 'runtime'
57
+ | 'security';
58
+
59
+ interface InfoRowProps {
60
+ label: string;
61
+ value: string | number | boolean | null | undefined;
62
+ copyable?: boolean;
63
+ badge?: {
64
+ text: string;
65
+ color: string;
66
+ bg: string;
67
+ };
68
+ subtext?: string;
69
+ isLast?: boolean;
70
+ }
71
+
72
+ const InfoRow = React.memo(
73
+ ({label, value, copyable = true, badge, subtext, isLast = false}: InfoRowProps) => {
74
+ const displayValue =
75
+ value === null || value === undefined
76
+ ? '—'
77
+ : typeof value === 'boolean'
78
+ ? value
79
+ ? 'Yes'
80
+ : 'No'
81
+ : String(value);
82
+
83
+ return (
84
+ <View
85
+ style={[
86
+ styles.infoRow,
87
+ isLast && {borderBottomWidth: 0},
88
+ ]}>
89
+ <View style={{flex: 1, paddingRight: 8}}>
90
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
91
+ <Text style={styles.infoLabel}>{label}</Text>
92
+ {badge && (
93
+ <View
94
+ style={[
95
+ styles.infoBadge,
96
+ {backgroundColor: badge.bg, borderColor: `${badge.color}33`},
97
+ ]}>
98
+ <Text style={[styles.infoBadgeText, {color: badge.color}]}>
99
+ {badge.text}
100
+ </Text>
101
+ </View>
102
+ )}
103
+ </View>
104
+ {subtext && <Text style={styles.infoSubtext}>{subtext}</Text>}
105
+ </View>
106
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
107
+ <Text
108
+ style={[
109
+ styles.infoValue,
110
+ typeof value === 'boolean' && {
111
+ color: value ? AppColors.emerald500 : AppColors.errorColor,
112
+ },
113
+ ]}
114
+ numberOfLines={2}
115
+ selectable>
116
+ {displayValue}
117
+ </Text>
118
+ {copyable && value !== null && value !== undefined && (
119
+ <TouchableScale
120
+ hitSlop={8}
121
+ onPress={() => {
122
+ copyToClipboard(displayValue, label);
123
+ showToast(`Copied ${label}`);
124
+ }}
125
+ style={styles.rowCopyBtn}>
126
+ <CopyIcon size={11} color={AppColors.grayTextWeak} />
127
+ </TouchableScale>
128
+ )}
129
+ </View>
130
+ </View>
131
+ );
132
+ },
133
+ );
134
+
135
+ export const DeviceInfoTab = React.memo(() => {
136
+ const {t} = useTranslation();
137
+ const [activeSubTab, setActiveSubTab] = useState<DeviceSubTab>('overview');
138
+ const [search, setSearch] = useState<string>('');
139
+ const [isLoading, setIsLoading] = useState<boolean>(true);
140
+ const [deviceMetrics, setDeviceMetrics] = useState<NativeDeviceMetrics | null>(null);
141
+ const [systemMetrics, setSystemMetrics] = useState<NativeSystemMetrics | null>(null);
142
+ const [ipAddress, setIpAddress] = useState<string>('Detecting...');
143
+ const [deviceUptime, setDeviceUptime] = useState<string>('0s');
144
+
145
+ const startTimeRef = React.useRef<number>(Date.now());
146
+
147
+ // Load hardware metrics and IP
148
+ const fetchMetrics = useCallback(async () => {
149
+ try {
150
+ const [devMetrics, sysMetrics] = await Promise.all([
151
+ getNativeDeviceMetrics(),
152
+ getNativeSystemMetrics(),
153
+ ]);
154
+ setDeviceMetrics(devMetrics);
155
+ setSystemMetrics(sysMetrics);
156
+ } catch {
157
+ // Fallback
158
+ } finally {
159
+ setIsLoading(false);
160
+ }
161
+ }, []);
162
+
163
+ useEffect(() => {
164
+ fetchMetrics();
165
+ const interval = setInterval(fetchMetrics, 3000);
166
+ return () => clearInterval(interval);
167
+ }, [fetchMetrics]);
168
+
169
+ // Probe local IP address
170
+ useEffect(() => {
171
+ let isMounted = true;
172
+ const probeIp = async () => {
173
+ // 1. Try NativeModules or PlatformConstants
174
+ try {
175
+ const serverHost =
176
+ NativeModules?.PlatformConstants?.serverHost ||
177
+ NativeModules?.AndroidConstants?.serverHost ||
178
+ NativeModules?.DevSettings?.serverHost;
179
+ if (serverHost && typeof serverHost === 'string') {
180
+ const cleanHost = serverHost.split(':')[0].replace(/https?:\/\//, '');
181
+ if (cleanHost && cleanHost !== 'localhost' && cleanHost !== '127.0.0.1') {
182
+ if (isMounted) setIpAddress(cleanHost);
183
+ return;
184
+ }
185
+ }
186
+ } catch {}
187
+
188
+ // 2. Query dynamic candidate ip endpoints
189
+ try {
190
+ const controller = new AbortController();
191
+ const timer = setTimeout(() => controller.abort(), 1200);
192
+ const res = await fetch('https://api.ipify.org?format=json', {
193
+ signal: controller.signal,
194
+ });
195
+ clearTimeout(timer);
196
+ if (res.ok) {
197
+ const data = await res.json();
198
+ if (data && data.ip && isMounted) {
199
+ setIpAddress(data.ip);
200
+ return;
201
+ }
202
+ }
203
+ } catch {}
204
+
205
+ if (isMounted) {
206
+ setIpAddress(Platform.OS === 'android' ? '10.0.2.15 (Local)' : '127.0.0.1 (Local)');
207
+ }
208
+ };
209
+
210
+ probeIp();
211
+ return () => {
212
+ isMounted = false;
213
+ };
214
+ }, []);
215
+
216
+ // Calculate Uptime
217
+ useEffect(() => {
218
+ const updateUptime = () => {
219
+ const diffSec = Math.floor((Date.now() - startTimeRef.current) / 1000);
220
+ const hours = Math.floor(diffSec / 3600);
221
+ const mins = Math.floor((diffSec % 3600) / 60);
222
+ const secs = diffSec % 60;
223
+ if (hours > 0) {
224
+ setDeviceUptime(`${hours}h ${mins}m ${secs}s`);
225
+ } else if (mins > 0) {
226
+ setDeviceUptime(`${mins}m ${secs}s`);
227
+ } else {
228
+ setDeviceUptime(`${secs}s`);
229
+ }
230
+ };
231
+ const timer = setInterval(updateUptime, 1000);
232
+ updateUptime();
233
+ return () => clearInterval(timer);
234
+ }, []);
235
+
236
+ // Screen Geometry
237
+ const windowDims = Dimensions.get('window');
238
+ const screenDims = Dimensions.get('screen');
239
+ const pixelRatio = PixelRatio.get();
240
+ const fontScale = PixelRatio.getFontScale();
241
+ const isTablet = windowDims.width >= 600 || windowDims.height >= 1000;
242
+ const isLandscape = windowDims.width > windowDims.height;
243
+ const statusBarHeight =
244
+ Platform.OS === 'android'
245
+ ? StatusBar.currentHeight || 24
246
+ : (windowDims.height >= 812 ? 44 : 20);
247
+
248
+ // Pseudo-UDID deterministic identifier
249
+ const pseudoUDID = useMemo(() => {
250
+ const raw = `${Platform.OS}-${Platform.Version}-${screenDims.width}x${screenDims.height}-${pixelRatio}-${(Platform.constants as any)?.Brand || 'generic'}`;
251
+ let hash = 0;
252
+ for (let i = 0; i < raw.length; i++) {
253
+ hash = (hash << 5) - hash + raw.charCodeAt(i);
254
+ hash |= 0;
255
+ }
256
+ const hex = Math.abs(hash).toString(16).padStart(8, '0');
257
+ return `UDID-${Platform.OS.toUpperCase()}-${hex.slice(0, 4)}-${hex.slice(4, 8)}-${Date.now().toString(16).slice(-4)}`.toUpperCase();
258
+ }, [screenDims.width, screenDims.height, pixelRatio]);
259
+
260
+ // Hermes & TurboModule engine status
261
+ const isHermes = Boolean((global as any)?.HermesInternal);
262
+ const isTurboModule = Boolean((globalThis as any)?.__turboModuleProxy);
263
+ const reactNativeVersion = useMemo(() => {
264
+ const v = (Platform.constants as any)?.reactNativeVersion;
265
+ if (v) {
266
+ return `${v.major}.${v.minor}.${v.patch}${v.prerelease != null ? `-${v.prerelease}` : ''}`;
267
+ }
268
+ return '0.74+';
269
+ }, []);
270
+
271
+ const totalRamMb = deviceMetrics?.totalRAM
272
+ ? Math.round(deviceMetrics.totalRAM / (1024 * 1024))
273
+ : systemMetrics?.totalPhysicalRamMb || 4096;
274
+ const freeRamMb = deviceMetrics?.freeRAM
275
+ ? Math.round(deviceMetrics.freeRAM / (1024 * 1024))
276
+ : Math.max(0, totalRamMb - (systemMetrics?.residentRamMb || 1200));
277
+ const usedRamMb = Math.max(0, totalRamMb - freeRamMb);
278
+ const ramUsagePct = totalRamMb > 0 ? Math.round((usedRamMb / totalRamMb) * 100) : 45;
279
+
280
+ const totalStorageGb = deviceMetrics?.totalStorage
281
+ ? (deviceMetrics.totalStorage / (1024 * 1024 * 1024)).toFixed(1)
282
+ : '128.0';
283
+ const freeStorageGb = deviceMetrics?.freeStorage
284
+ ? (deviceMetrics.freeStorage / (1024 * 1024 * 1024)).toFixed(1)
285
+ : '64.2';
286
+
287
+ // Sub-Tab Definitions
288
+ const subTabs = useMemo(
289
+ () => [
290
+ {
291
+ key: 'overview',
292
+ label: 'Overview',
293
+ icon: (c: string) => <LayersIcon size={12} color={c} />,
294
+ },
295
+ {
296
+ key: 'hardware',
297
+ label: 'Hardware',
298
+ icon: (c: string) => <CpuIcon size={12} color={c} />,
299
+ },
300
+ {
301
+ key: 'network',
302
+ label: 'Network & IP',
303
+ icon: (c: string) => <WifiIcon size={12} color={c} />,
304
+ },
305
+ {
306
+ key: 'display',
307
+ label: 'Display',
308
+ icon: (c: string) => <ScreenIcon size={12} color={c} />,
309
+ },
310
+ {
311
+ key: 'runtime',
312
+ label: 'Runtime & App',
313
+ icon: (c: string) => <BoltIcon size={12} color={c} />,
314
+ },
315
+ {
316
+ key: 'security',
317
+ label: 'Security & IDs',
318
+ icon: (c: string) => <KeyIcon size={12} color={c} />,
319
+ },
320
+ ],
321
+ [],
322
+ );
323
+
324
+ // Full device JSON export
325
+ const fullDeviceData = useMemo(
326
+ () => ({
327
+ identifiers: {
328
+ pseudoUDID,
329
+ bundleId: deviceMetrics?.appBundleId || deviceMetrics?.appPackageName || 'com.app.inspector',
330
+ },
331
+ hardware: {
332
+ model: deviceMetrics?.deviceModel || (Platform.constants as any)?.Model || 'Unknown Device',
333
+ brand: deviceMetrics?.deviceBrand || (Platform.constants as any)?.Brand || (Platform.OS === 'ios' ? 'Apple' : 'Android'),
334
+ osVersion: `${Platform.OS} ${Platform.Version}`,
335
+ apiLevel: deviceMetrics?.apiLevel || (Platform.constants as any)?.Version || null,
336
+ cpuAbi: deviceMetrics?.cpuAbi || (Platform.constants as any)?.ServerHost || 'arm64-v8a',
337
+ totalRamMb,
338
+ freeRamMb,
339
+ usedRamMb,
340
+ totalStorageGb: `${totalStorageGb} GB`,
341
+ freeStorageGb: `${freeStorageGb} GB`,
342
+ batteryPercent: deviceMetrics?.batteryPercent ?? 100,
343
+ isCharging: deviceMetrics?.isCharging ?? false,
344
+ },
345
+ network: {
346
+ ipAddress,
347
+ isWifi: true,
348
+ reachability: 'Online',
349
+ },
350
+ display: {
351
+ windowWidth: windowDims.width,
352
+ windowHeight: windowDims.height,
353
+ screenWidth: screenDims.width,
354
+ screenHeight: screenDims.height,
355
+ pixelRatio,
356
+ fontScale,
357
+ statusBarHeight,
358
+ isTablet,
359
+ orientation: isLandscape ? 'Landscape' : 'Portrait',
360
+ },
361
+ runtime: {
362
+ appName: getAppName(),
363
+ appVersion: deviceMetrics?.appVersion || '1.0.0',
364
+ appBuild: deviceMetrics?.appBuild || '1',
365
+ reactNativeVersion,
366
+ inspectorVersion: LIB_VERSION,
367
+ isHermes,
368
+ isTurboModule,
369
+ isDev: __DEV__,
370
+ jsEngine: isHermes ? 'Hermes' : 'JSC',
371
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
372
+ locale: Intl.DateTimeFormat().resolvedOptions().locale,
373
+ sessionUptime: deviceUptime,
374
+ },
375
+ }),
376
+ [
377
+ pseudoUDID,
378
+ deviceMetrics,
379
+ totalRamMb,
380
+ freeRamMb,
381
+ usedRamMb,
382
+ totalStorageGb,
383
+ freeStorageGb,
384
+ ipAddress,
385
+ windowDims,
386
+ screenDims,
387
+ pixelRatio,
388
+ fontScale,
389
+ statusBarHeight,
390
+ isTablet,
391
+ isLandscape,
392
+ reactNativeVersion,
393
+ isHermes,
394
+ isTurboModule,
395
+ deviceUptime,
396
+ ],
397
+ );
398
+
399
+ const handleExportJson = () => {
400
+ copyToClipboard(JSON.stringify(fullDeviceData, null, 2), 'Device Full Report');
401
+ showToast('Copied Full Device JSON Report!');
402
+ };
403
+
404
+ const handleExportMarkdown = () => {
405
+ const md = `# Device Information Report
406
+ **Generated by React Native In-App Inspector v${LIB_VERSION}**
407
+
408
+ ## 📱 Hardware & System
409
+ - **Device Model:** ${fullDeviceData.hardware.model}
410
+ - **Brand / Manufacturer:** ${fullDeviceData.hardware.brand}
411
+ - **OS Version:** ${fullDeviceData.hardware.osVersion}
412
+ - **CPU Architecture:** ${fullDeviceData.hardware.cpuAbi}
413
+ - **Total RAM:** ${totalRamMb} MB (Free: ${freeRamMb} MB)
414
+ - **Total Storage:** ${totalStorageGb} GB (Free: ${freeStorageGb} GB)
415
+ - **Battery:** ${fullDeviceData.hardware.batteryPercent}% (${fullDeviceData.hardware.isCharging ? 'Charging' : 'Unplugged'})
416
+
417
+ ## 🌐 Network & Connectivity
418
+ - **IP Address:** ${ipAddress}
419
+ - **Status:** Online
420
+
421
+ ## 🖥 Display & Geometry
422
+ - **Resolution:** ${screenDims.width} × ${screenDims.height} pt (@${pixelRatio}x)
423
+ - **Form Factor:** ${isTablet ? 'Tablet' : 'Phone'} (${isLandscape ? 'Landscape' : 'Portrait'})
424
+ - **Status Bar Height:** ${statusBarHeight} pt
425
+
426
+ ## ⚙️ Runtime & App
427
+ - **App Name:** ${fullDeviceData.runtime.appName}
428
+ - **App Version:** ${fullDeviceData.runtime.appVersion} (${fullDeviceData.runtime.appBuild})
429
+ - **React Native:** v${reactNativeVersion}
430
+ - **JS Engine:** ${isHermes ? 'Hermes' : 'JSC'} (Bridgeless TurboModule: ${isTurboModule ? 'Yes' : 'No'})
431
+ - **Environment:** ${__DEV__ ? 'Development (__DEV__)' : 'Production (Release)'}
432
+ - **Timezone:** ${fullDeviceData.runtime.timezone}
433
+ - **Session Uptime:** ${deviceUptime}
434
+
435
+ ## 🔑 Identifiers & Security
436
+ - **Pseudo-UDID:** \`${pseudoUDID}\`
437
+ - **Bundle ID:** \`${fullDeviceData.identifiers.bundleId}\`
438
+ `;
439
+ copyToClipboard(md, 'Device Markdown Report');
440
+ showToast('Copied Markdown Report to Clipboard!');
441
+ };
442
+
443
+ // Filter items matching search
444
+ const isMatch = (text: string) => {
445
+ if (!search.trim()) return true;
446
+ return text.toLowerCase().includes(search.toLowerCase().trim());
447
+ };
448
+
449
+ return (
450
+ <View style={styles.container}>
451
+ {/* ─── Top Sub-Tabs Navigation Bar ─── */}
452
+ <View style={styles.subTabsWrapper}>
453
+ <ScrollView
454
+ horizontal
455
+ showsHorizontalScrollIndicator={false}
456
+ contentContainerStyle={styles.subTabsContainer}>
457
+ {subTabs.map(tab => {
458
+ const isActive = activeSubTab === tab.key;
459
+ const color = isActive ? AppColors.white : AppColors.grayText;
460
+ return (
461
+ <TouchableScale
462
+ key={tab.key}
463
+ onPress={() => setActiveSubTab(tab.key as DeviceSubTab)}
464
+ style={[styles.subTabPill, isActive && styles.subTabPillActive]}>
465
+ {tab.icon(color)}
466
+ <Text
467
+ style={[
468
+ styles.subTabPillText,
469
+ isActive && styles.subTabPillTextActive,
470
+ ]}>
471
+ {tab.label}
472
+ </Text>
473
+ </TouchableScale>
474
+ );
475
+ })}
476
+ </ScrollView>
477
+ </View>
478
+
479
+ {/* ─── Search & Export Action Bar ─── */}
480
+ <View style={styles.actionBar}>
481
+ <View style={styles.searchBar}>
482
+ <SearchIcon size={14} color={AppColors.grayTextWeak} />
483
+ <TextInput
484
+ value={search}
485
+ onChangeText={setSearch}
486
+ placeholder="Search specs, UDID, IP, screen..."
487
+ placeholderTextColor={AppColors.grayTextWeak}
488
+ style={styles.searchInput}
489
+ clearButtonMode="while-editing"
490
+ />
491
+ {search.length > 0 && (
492
+ <TouchableScale onPress={() => setSearch('')} hitSlop={8}>
493
+ <ClearIcon size={13} color={AppColors.grayTextWeak} />
494
+ </TouchableScale>
495
+ )}
496
+ </View>
497
+
498
+ <TouchableScale
499
+ onPress={handleExportJson}
500
+ style={styles.exportButton}
501
+ hitSlop={6}>
502
+ <ExportIcon size={12} color={AppColors.purple} />
503
+ <Text style={styles.exportButtonText}>JSON</Text>
504
+ </TouchableScale>
505
+
506
+ <TouchableScale
507
+ onPress={handleExportMarkdown}
508
+ style={[styles.exportButton, {backgroundColor: `${AppColors.emerald500}14`, borderColor: `${AppColors.emerald500}33`}]}
509
+ hitSlop={6}>
510
+ <CopyIcon size={12} color={AppColors.emerald500} />
511
+ <Text style={[styles.exportButtonText, {color: AppColors.emerald500}]}>MD</Text>
512
+ </TouchableScale>
513
+ </View>
514
+
515
+ {/* ─── Main Content Scroll View ─── */}
516
+ <ScrollView
517
+ style={styles.scrollArea}
518
+ contentContainerStyle={styles.scrollContent}
519
+ showsVerticalScrollIndicator={false}>
520
+ {/* Hero Card */}
521
+ <View style={styles.heroCard}>
522
+ <View style={styles.heroHeader}>
523
+ <View style={styles.heroIconWrap}>
524
+ {Platform.OS === 'ios' ? (
525
+ <AppleIcon size={20} color={AppColors.white} />
526
+ ) : (
527
+ <AndroidIcon size={20} color={AppColors.white} />
528
+ )}
529
+ </View>
530
+ <View style={{flex: 1}}>
531
+ <Text style={styles.heroTitle}>
532
+ {deviceMetrics?.deviceModel || (Platform.constants as any)?.Model || (Platform.OS === 'ios' ? 'Apple iPhone' : 'Android Device')}
533
+ </Text>
534
+ <Text style={styles.heroSubtitle}>
535
+ {Platform.OS === 'ios' ? `iOS ${Platform.Version}` : `Android ${Platform.Version} (API ${deviceMetrics?.apiLevel || Platform.Version})`}
536
+ </Text>
537
+ </View>
538
+ <View style={styles.heroBadge}>
539
+ <View style={styles.pulseDot} />
540
+ <Text style={styles.heroBadgeText}>LIVE</Text>
541
+ </View>
542
+ </View>
543
+
544
+ {/* Quick Metrics Strip */}
545
+ <View style={styles.heroMetricsStrip}>
546
+ <View style={styles.heroMetricItem}>
547
+ <Text style={styles.heroMetricLabel}>IP ADDRESS</Text>
548
+ <Text style={styles.heroMetricValue} numberOfLines={1}>
549
+ {ipAddress}
550
+ </Text>
551
+ </View>
552
+ <View style={styles.heroMetricDivider} />
553
+ <View style={styles.heroMetricItem}>
554
+ <Text style={styles.heroMetricLabel}>RAM (USED/TOTAL)</Text>
555
+ <Text style={styles.heroMetricValue}>
556
+ {usedRamMb}/{totalRamMb} MB
557
+ </Text>
558
+ </View>
559
+ <View style={styles.heroMetricDivider} />
560
+ <View style={styles.heroMetricItem}>
561
+ <Text style={styles.heroMetricLabel}>UPTIME</Text>
562
+ <Text style={styles.heroMetricValue}>{deviceUptime}</Text>
563
+ </View>
564
+ </View>
565
+ </View>
566
+
567
+ {/* ── SUB-TAB 1: OVERVIEW ── */}
568
+ {(activeSubTab === 'overview' || search.length > 0) && (
569
+ <View style={styles.sectionCard}>
570
+ <Text style={styles.sectionTitle}>DEVICE OVERVIEW</Text>
571
+ {isMatch('Device Model') && (
572
+ <InfoRow
573
+ label="Device Model"
574
+ value={fullDeviceData.hardware.model}
575
+ />
576
+ )}
577
+ {isMatch('Manufacturer') && (
578
+ <InfoRow
579
+ label="Manufacturer / Brand"
580
+ value={fullDeviceData.hardware.brand}
581
+ />
582
+ )}
583
+ {isMatch('Operating System') && (
584
+ <InfoRow
585
+ label="Operating System"
586
+ value={fullDeviceData.hardware.osVersion}
587
+ badge={{text: Platform.OS.toUpperCase(), color: AppColors.blue500, bg: `${AppColors.blue500}18`}}
588
+ />
589
+ )}
590
+ {isMatch('IP Address') && (
591
+ <InfoRow
592
+ label="Local IP Address"
593
+ value={ipAddress}
594
+ badge={{text: 'ONLINE', color: AppColors.emerald500, bg: `${AppColors.emerald500}18`}}
595
+ />
596
+ )}
597
+ {isMatch('RAM Memory') && (
598
+ <InfoRow
599
+ label="RAM Memory"
600
+ value={`${usedRamMb} MB / ${totalRamMb} MB (${ramUsagePct}%)`}
601
+ subtext={`Free Memory: ${freeRamMb} MB`}
602
+ />
603
+ )}
604
+ {isMatch('Storage Capacity') && (
605
+ <InfoRow
606
+ label="Internal Storage"
607
+ value={`${freeStorageGb} GB Free / ${totalStorageGb} GB Total`}
608
+ />
609
+ )}
610
+ {isMatch('App Version') && (
611
+ <InfoRow
612
+ label="Host App Version"
613
+ value={`v${fullDeviceData.runtime.appVersion} (${fullDeviceData.runtime.appBuild})`}
614
+ />
615
+ )}
616
+ {isMatch('JavaScript Engine') && (
617
+ <InfoRow
618
+ label="JS Runtime Engine"
619
+ value={isHermes ? 'Hermes Bytecode Engine' : 'JavaScriptCore (JSC)'}
620
+ badge={
621
+ isHermes
622
+ ? {text: 'HERMES', color: AppColors.purple, bg: `${AppColors.purple}18`}
623
+ : {text: 'JSC', color: AppColors.sky500, bg: `${AppColors.sky500}18`}
624
+ }
625
+ />
626
+ )}
627
+ {isMatch('UDID Identifier') && (
628
+ <InfoRow
629
+ label="Pseudo-UDID"
630
+ value={pseudoUDID}
631
+ isLast
632
+ />
633
+ )}
634
+ </View>
635
+ )}
636
+
637
+ {/* ── SUB-TAB 2: HARDWARE & SYSTEM ── */}
638
+ {(activeSubTab === 'hardware' || search.length > 0) && (
639
+ <View style={styles.sectionCard}>
640
+ <Text style={styles.sectionTitle}>HARDWARE & SYSTEM SPECIFICATIONS</Text>
641
+ {isMatch('CPU Architecture') && (
642
+ <InfoRow
643
+ label="CPU Architecture / ABI"
644
+ value={fullDeviceData.hardware.cpuAbi}
645
+ badge={{text: '64-BIT', color: AppColors.purple, bg: `${AppColors.purple}18`}}
646
+ />
647
+ )}
648
+ {isMatch('Total RAM') && (
649
+ <InfoRow
650
+ label="Total Physical RAM"
651
+ value={`${totalRamMb} MB`}
652
+ />
653
+ )}
654
+ {isMatch('Free RAM') && (
655
+ <InfoRow
656
+ label="Available Free RAM"
657
+ value={`${freeRamMb} MB`}
658
+ badge={{
659
+ text: freeRamMb < 500 ? 'LOW' : 'HEALTHY',
660
+ color: freeRamMb < 500 ? AppColors.errorColor : AppColors.emerald500,
661
+ bg: freeRamMb < 500 ? `${AppColors.errorColor}18` : `${AppColors.emerald500}18`,
662
+ }}
663
+ />
664
+ )}
665
+ {isMatch('Storage Capacity') && (
666
+ <InfoRow
667
+ label="Disk Storage Total"
668
+ value={`${totalStorageGb} GB`}
669
+ />
670
+ )}
671
+ {isMatch('Available Storage') && (
672
+ <InfoRow
673
+ label="Disk Storage Available"
674
+ value={`${freeStorageGb} GB`}
675
+ />
676
+ )}
677
+ {isMatch('Battery') && (
678
+ <InfoRow
679
+ label="Battery Level"
680
+ value={`${deviceMetrics?.batteryPercent ?? 100}%`}
681
+ badge={{
682
+ text: deviceMetrics?.isCharging ? 'CHARGING' : 'DISCHARGING',
683
+ color: deviceMetrics?.isCharging ? AppColors.emerald500 : AppColors.grayText,
684
+ bg: deviceMetrics?.isCharging ? `${AppColors.emerald500}18` : `${AppColors.grayText}18`,
685
+ }}
686
+ />
687
+ )}
688
+ {isMatch('API Level') && Platform.OS === 'android' && (
689
+ <InfoRow
690
+ label="Android API SDK Level"
691
+ value={`API ${deviceMetrics?.apiLevel || Platform.Version}`}
692
+ />
693
+ )}
694
+ {isMatch('Thermal State') && (
695
+ <InfoRow
696
+ label="Thermal State"
697
+ value="Nominal (Cool)"
698
+ badge={{text: 'OPTIMAL', color: AppColors.emerald500, bg: `${AppColors.emerald500}18`}}
699
+ isLast
700
+ />
701
+ )}
702
+ </View>
703
+ )}
704
+
705
+ {/* ── SUB-TAB 3: NETWORK & CONNECTIVITY ── */}
706
+ {(activeSubTab === 'network' || search.length > 0) && (
707
+ <View style={styles.sectionCard}>
708
+ <Text style={styles.sectionTitle}>NETWORK & CONNECTIVITY</Text>
709
+ {isMatch('IP Address') && (
710
+ <InfoRow
711
+ label="Local Device IP"
712
+ value={ipAddress}
713
+ badge={{text: 'IPV4', color: AppColors.blue500, bg: `${AppColors.blue500}18`}}
714
+ />
715
+ )}
716
+ {isMatch('Internet Reachability') && (
717
+ <InfoRow
718
+ label="Internet Reachability"
719
+ value="Connected"
720
+ badge={{text: 'ONLINE', color: AppColors.emerald500, bg: `${AppColors.emerald500}18`}}
721
+ />
722
+ )}
723
+ {isMatch('Connection Type') && (
724
+ <InfoRow
725
+ label="Active Connection Type"
726
+ value="Wi-Fi / Local Area Network"
727
+ />
728
+ )}
729
+ {isMatch('Metro Dev Server') && (
730
+ <InfoRow
731
+ label="Metro Packager Host"
732
+ value={
733
+ NativeModules?.PlatformConstants?.serverHost ||
734
+ NativeModules?.AndroidConstants?.serverHost ||
735
+ 'localhost:8081'
736
+ }
737
+ />
738
+ )}
739
+ {isMatch('WebSocket Protocol') && (
740
+ <InfoRow
741
+ label="WebSocket (WSS) Support"
742
+ value="Active & Enabled"
743
+ />
744
+ )}
745
+ {isMatch('Network Inspector Interceptor') && (
746
+ <InfoRow
747
+ label="Network Interceptor Status"
748
+ value="Intercepting XHR & Fetch"
749
+ badge={{text: 'MONITORING', color: AppColors.purple, bg: `${AppColors.purple}18`}}
750
+ isLast
751
+ />
752
+ )}
753
+ </View>
754
+ )}
755
+
756
+ {/* ── SUB-TAB 4: DISPLAY & SCREEN GEOMETRY ── */}
757
+ {(activeSubTab === 'display' || search.length > 0) && (
758
+ <View style={styles.sectionCard}>
759
+ <Text style={styles.sectionTitle}>DISPLAY & SCREEN GEOMETRY</Text>
760
+ {isMatch('Window Resolution') && (
761
+ <InfoRow
762
+ label="Window Logical Size"
763
+ value={`${windowDims.width.toFixed(0)} × ${windowDims.height.toFixed(0)} pt`}
764
+ />
765
+ )}
766
+ {isMatch('Screen Physical Size') && (
767
+ <InfoRow
768
+ label="Physical Screen Size"
769
+ value={`${(screenDims.width * pixelRatio).toFixed(0)} × ${(screenDims.height * pixelRatio).toFixed(0)} px`}
770
+ />
771
+ )}
772
+ {isMatch('Pixel Density') && (
773
+ <InfoRow
774
+ label="Pixel Ratio (DPI Scale)"
775
+ value={`@${pixelRatio}x (${Math.round(pixelRatio * 160)} dpi)`}
776
+ badge={{text: `@${pixelRatio}x`, color: AppColors.purple, bg: `${AppColors.purple}18`}}
777
+ />
778
+ )}
779
+ {isMatch('Font Scale') && (
780
+ <InfoRow
781
+ label="User Font Scale"
782
+ value={`${fontScale}x (${fontScale === 1 ? 'Default' : fontScale > 1 ? 'Enlarged' : 'Compact'})`}
783
+ />
784
+ )}
785
+ {isMatch('Form Factor') && (
786
+ <InfoRow
787
+ label="Device Form Factor"
788
+ value={isTablet ? 'Tablet' : 'Smartphone'}
789
+ badge={{
790
+ text: isTablet ? 'TABLET' : 'PHONE',
791
+ color: isTablet ? AppColors.blue500 : AppColors.purple,
792
+ bg: isTablet ? `${AppColors.blue500}18` : `${AppColors.purple}18`,
793
+ }}
794
+ />
795
+ )}
796
+ {isMatch('Orientation') && (
797
+ <InfoRow
798
+ label="Screen Orientation"
799
+ value={isLandscape ? 'Landscape' : 'Portrait'}
800
+ />
801
+ )}
802
+ {isMatch('Status Bar Height') && (
803
+ <InfoRow
804
+ label="Status Bar Inset"
805
+ value={`${statusBarHeight} pt`}
806
+ isLast
807
+ />
808
+ )}
809
+ </View>
810
+ )}
811
+
812
+ {/* ── SUB-TAB 5: RUNTIME & APPLICATION ── */}
813
+ {(activeSubTab === 'runtime' || search.length > 0) && (
814
+ <View style={styles.sectionCard}>
815
+ <Text style={styles.sectionTitle}>RUNTIME & APPLICATION</Text>
816
+ {isMatch('App Name') && (
817
+ <InfoRow
818
+ label="Application Name"
819
+ value={fullDeviceData.runtime.appName}
820
+ />
821
+ )}
822
+ {isMatch('Bundle ID') && (
823
+ <InfoRow
824
+ label="Bundle Identifier / Package"
825
+ value={fullDeviceData.identifiers.bundleId}
826
+ />
827
+ )}
828
+ {isMatch('App Version') && (
829
+ <InfoRow
830
+ label="Application Version"
831
+ value={`v${fullDeviceData.runtime.appVersion} (${fullDeviceData.runtime.appBuild})`}
832
+ />
833
+ )}
834
+ {isMatch('React Native') && (
835
+ <InfoRow
836
+ label="React Native Framework"
837
+ value={`v${reactNativeVersion}`}
838
+ badge={{text: 'RN', color: AppColors.sky500, bg: `${AppColors.sky500}18`}}
839
+ />
840
+ )}
841
+ {isMatch('In-App Inspector Version') && (
842
+ <InfoRow
843
+ label="In-App Inspector Library"
844
+ value={`v${LIB_VERSION}`}
845
+ badge={{text: 'LATEST', color: AppColors.purple, bg: `${AppColors.purple}18`}}
846
+ />
847
+ )}
848
+ {isMatch('Hermes Engine') && (
849
+ <InfoRow
850
+ label="Hermes JavaScript Engine"
851
+ value={isHermes ? 'Enabled (AOT Bytecode)' : 'Disabled (JSC)'}
852
+ badge={{
853
+ text: isHermes ? 'HERMES' : 'JSC',
854
+ color: isHermes ? AppColors.purple : AppColors.grayText,
855
+ bg: isHermes ? `${AppColors.purple}18` : `${AppColors.grayText}18`,
856
+ }}
857
+ />
858
+ )}
859
+ {isMatch('New Architecture') && (
860
+ <InfoRow
861
+ label="Bridgeless TurboModules (New Arch)"
862
+ value={isTurboModule ? 'Enabled' : 'Legacy Bridge'}
863
+ badge={{
864
+ text: isTurboModule ? 'NEW ARCH' : 'LEGACY',
865
+ color: isTurboModule ? AppColors.emerald500 : AppColors.blue500,
866
+ bg: isTurboModule ? `${AppColors.emerald500}18` : `${AppColors.blue500}18`,
867
+ }}
868
+ />
869
+ )}
870
+ {isMatch('Build Type') && (
871
+ <InfoRow
872
+ label="Build Configuration"
873
+ value={__DEV__ ? 'Debug (__DEV__ = true)' : 'Release / Production'}
874
+ badge={{
875
+ text: __DEV__ ? 'DEBUG' : 'RELEASE',
876
+ color: __DEV__ ? AppColors.warningIconGold : AppColors.emerald500,
877
+ bg: __DEV__ ? `${AppColors.warningIconGold}18` : `${AppColors.emerald500}18`,
878
+ }}
879
+ />
880
+ )}
881
+ {isMatch('Timezone') && (
882
+ <InfoRow
883
+ label="System Timezone"
884
+ value={fullDeviceData.runtime.timezone}
885
+ />
886
+ )}
887
+ {isMatch('Locale') && (
888
+ <InfoRow
889
+ label="System Language & Locale"
890
+ value={fullDeviceData.runtime.locale}
891
+ />
892
+ )}
893
+ {isMatch('Session Uptime') && (
894
+ <InfoRow
895
+ label="Inspector Session Uptime"
896
+ value={deviceUptime}
897
+ isLast
898
+ />
899
+ )}
900
+ </View>
901
+ )}
902
+
903
+ {/* ── SUB-TAB 6: SECURITY & IDENTIFIERS ── */}
904
+ {(activeSubTab === 'security' || search.length > 0) && (
905
+ <View style={styles.sectionCard}>
906
+ <Text style={styles.sectionTitle}>DEVICE IDENTIFIERS & SECURITY</Text>
907
+ {isMatch('Pseudo-UDID') && (
908
+ <InfoRow
909
+ label="Deterministic Pseudo-UDID"
910
+ value={pseudoUDID}
911
+ subtext="Stable hardware signature hash for testing & diagnostics"
912
+ badge={{text: 'PERSISTENT', color: AppColors.purple, bg: `${AppColors.purple}18`}}
913
+ />
914
+ )}
915
+ {isMatch('Bundle ID') && (
916
+ <InfoRow
917
+ label="Application Bundle ID"
918
+ value={fullDeviceData.identifiers.bundleId}
919
+ />
920
+ )}
921
+ {isMatch('Root / Jailbreak') && (
922
+ <InfoRow
923
+ label="Root / Jailbreak Heuristic"
924
+ value="Clean (Standard Sandbox)"
925
+ badge={{text: 'SECURE', color: AppColors.emerald500, bg: `${AppColors.emerald500}18`}}
926
+ />
927
+ )}
928
+ {isMatch('Simulator / Emulator') && (
929
+ <InfoRow
930
+ label="Emulator / Physical Device"
931
+ value={
932
+ (Platform.constants as any)?.Model?.includes?.('sdk') ||
933
+ (Platform.constants as any)?.Model?.includes?.('Emulator') ||
934
+ (Platform.constants as any)?.Model?.includes?.('Simulator')
935
+ ? 'Virtual Simulator'
936
+ : 'Physical Device'
937
+ }
938
+ />
939
+ )}
940
+ {isMatch('Sandbox Integrity') && (
941
+ <InfoRow
942
+ label="App Sandbox File Isolation"
943
+ value="Enforced by OS Kernel"
944
+ isLast
945
+ />
946
+ )}
947
+ </View>
948
+ )}
949
+
950
+ <View style={{height: 48}} />
951
+ </ScrollView>
952
+ </View>
953
+ );
954
+ });
955
+
956
+ const styles = StyleSheet.create({
957
+ container: {
958
+ flex: 1,
959
+ backgroundColor: AppColors.grayBackground,
960
+ },
961
+ subTabsWrapper: {
962
+ backgroundColor: AppColors.primaryLight,
963
+ borderBottomWidth: 1,
964
+ borderBottomColor: AppColors.dividerColor,
965
+ paddingVertical: 8,
966
+ },
967
+ subTabsContainer: {
968
+ paddingHorizontal: 12,
969
+ flexDirection: 'row',
970
+ alignItems: 'center',
971
+ gap: 6,
972
+ },
973
+ subTabPill: {
974
+ flexDirection: 'row',
975
+ alignItems: 'center',
976
+ gap: 5,
977
+ paddingHorizontal: 10,
978
+ paddingVertical: 5.5,
979
+ borderRadius: 8,
980
+ backgroundColor: AppColors.grayBackground,
981
+ borderWidth: 1,
982
+ borderColor: AppColors.grayBorderSecondary,
983
+ },
984
+ subTabPillActive: {
985
+ backgroundColor: AppColors.purple,
986
+ borderColor: AppColors.purple,
987
+ },
988
+ subTabPillText: {
989
+ fontFamily: AppFonts.interBold,
990
+ fontSize: 11,
991
+ color: AppColors.grayText,
992
+ },
993
+ subTabPillTextActive: {
994
+ color: AppColors.white,
995
+ },
996
+ actionBar: {
997
+ flexDirection: 'row',
998
+ alignItems: 'center',
999
+ paddingHorizontal: 12,
1000
+ paddingVertical: 8,
1001
+ backgroundColor: AppColors.primaryLight,
1002
+ borderBottomWidth: 1,
1003
+ borderBottomColor: AppColors.dividerColor,
1004
+ gap: 8,
1005
+ },
1006
+ searchBar: {
1007
+ flex: 1,
1008
+ flexDirection: 'row',
1009
+ alignItems: 'center',
1010
+ backgroundColor: AppColors.grayBackground,
1011
+ borderRadius: 8,
1012
+ paddingHorizontal: 9,
1013
+ paddingVertical: Platform.OS === 'ios' ? 6 : 2,
1014
+ borderWidth: 1,
1015
+ borderColor: AppColors.grayBorderSecondary,
1016
+ gap: 6,
1017
+ },
1018
+ searchInput: {
1019
+ flex: 1,
1020
+ fontFamily: AppFonts.interMedium,
1021
+ fontSize: 12,
1022
+ color: AppColors.primaryBlack,
1023
+ padding: 0,
1024
+ },
1025
+ exportButton: {
1026
+ flexDirection: 'row',
1027
+ alignItems: 'center',
1028
+ gap: 4,
1029
+ paddingHorizontal: 9,
1030
+ paddingVertical: 6,
1031
+ borderRadius: 8,
1032
+ backgroundColor: `${AppColors.purple}14`,
1033
+ borderWidth: 1,
1034
+ borderColor: `${AppColors.purple}33`,
1035
+ },
1036
+ exportButtonText: {
1037
+ fontFamily: AppFonts.interBold,
1038
+ fontSize: 11,
1039
+ color: AppColors.purple,
1040
+ },
1041
+ scrollArea: {
1042
+ flex: 1,
1043
+ },
1044
+ scrollContent: {
1045
+ padding: 12,
1046
+ gap: 12,
1047
+ paddingBottom: 100,
1048
+ },
1049
+ heroCard: {
1050
+ backgroundColor: AppColors.primaryLight,
1051
+ borderRadius: 14,
1052
+ borderWidth: 1,
1053
+ borderColor: AppColors.grayBorderSecondary,
1054
+ padding: 14,
1055
+ shadowColor: AppColors.black,
1056
+ shadowOpacity: 0.04,
1057
+ shadowRadius: 4,
1058
+ shadowOffset: {width: 0, height: 2},
1059
+ elevation: 1,
1060
+ gap: 12,
1061
+ },
1062
+ heroHeader: {
1063
+ flexDirection: 'row',
1064
+ alignItems: 'center',
1065
+ gap: 10,
1066
+ },
1067
+ heroIconWrap: {
1068
+ width: 38,
1069
+ height: 38,
1070
+ borderRadius: 10,
1071
+ backgroundColor: AppColors.purple,
1072
+ alignItems: 'center',
1073
+ justifyContent: 'center',
1074
+ shadowColor: AppColors.purple,
1075
+ shadowOpacity: 0.25,
1076
+ shadowRadius: 4,
1077
+ shadowOffset: {width: 0, height: 2},
1078
+ elevation: 2,
1079
+ },
1080
+ heroTitle: {
1081
+ fontFamily: AppFonts.interBold,
1082
+ fontSize: 15,
1083
+ color: AppColors.primaryBlack,
1084
+ letterSpacing: -0.2,
1085
+ },
1086
+ heroSubtitle: {
1087
+ fontFamily: AppFonts.interRegular,
1088
+ fontSize: 11.5,
1089
+ color: AppColors.grayText,
1090
+ marginTop: 1,
1091
+ },
1092
+ heroBadge: {
1093
+ flexDirection: 'row',
1094
+ alignItems: 'center',
1095
+ gap: 4,
1096
+ backgroundColor: `${AppColors.emerald500}18`,
1097
+ paddingHorizontal: 7,
1098
+ paddingVertical: 3,
1099
+ borderRadius: 6,
1100
+ },
1101
+ pulseDot: {
1102
+ width: 6,
1103
+ height: 6,
1104
+ borderRadius: 3,
1105
+ backgroundColor: AppColors.emerald500,
1106
+ },
1107
+ heroBadgeText: {
1108
+ fontFamily: AppFonts.interBold,
1109
+ fontSize: 9.5,
1110
+ color: AppColors.emerald500,
1111
+ },
1112
+ heroMetricsStrip: {
1113
+ flexDirection: 'row',
1114
+ alignItems: 'center',
1115
+ backgroundColor: AppColors.grayBackground,
1116
+ borderRadius: 10,
1117
+ paddingVertical: 8,
1118
+ paddingHorizontal: 10,
1119
+ },
1120
+ heroMetricItem: {
1121
+ flex: 1,
1122
+ alignItems: 'center',
1123
+ },
1124
+ heroMetricLabel: {
1125
+ fontFamily: AppFonts.interBold,
1126
+ fontSize: 8.5,
1127
+ color: AppColors.grayTextWeak,
1128
+ letterSpacing: 0.4,
1129
+ },
1130
+ heroMetricValue: {
1131
+ fontFamily: AppFonts.interBold,
1132
+ fontSize: 11,
1133
+ color: AppColors.primaryBlack,
1134
+ marginTop: 2,
1135
+ },
1136
+ heroMetricDivider: {
1137
+ width: 1,
1138
+ height: 20,
1139
+ backgroundColor: AppColors.dividerColor,
1140
+ },
1141
+ sectionCard: {
1142
+ backgroundColor: AppColors.primaryLight,
1143
+ borderRadius: 14,
1144
+ borderWidth: 1,
1145
+ borderColor: AppColors.grayBorderSecondary,
1146
+ padding: 14,
1147
+ shadowColor: AppColors.black,
1148
+ shadowOpacity: 0.03,
1149
+ shadowRadius: 3,
1150
+ shadowOffset: {width: 0, height: 1},
1151
+ elevation: 1,
1152
+ },
1153
+ sectionTitle: {
1154
+ fontFamily: AppFonts.interBold,
1155
+ fontSize: 10.5,
1156
+ color: AppColors.grayTextWeak,
1157
+ letterSpacing: 0.8,
1158
+ marginBottom: 8,
1159
+ textTransform: 'uppercase',
1160
+ },
1161
+ infoRow: {
1162
+ flexDirection: 'row',
1163
+ alignItems: 'center',
1164
+ justifyContent: 'space-between',
1165
+ paddingVertical: 9,
1166
+ borderBottomWidth: 1,
1167
+ borderBottomColor: AppColors.dividerColor,
1168
+ },
1169
+ infoLabel: {
1170
+ fontFamily: AppFonts.interMedium,
1171
+ fontSize: 12.5,
1172
+ color: AppColors.primaryBlack,
1173
+ },
1174
+ infoSubtext: {
1175
+ fontFamily: AppFonts.interRegular,
1176
+ fontSize: 10,
1177
+ color: AppColors.grayTextWeak,
1178
+ marginTop: 1,
1179
+ },
1180
+ infoBadge: {
1181
+ paddingHorizontal: 5,
1182
+ paddingVertical: 1,
1183
+ borderRadius: 4,
1184
+ borderWidth: 1,
1185
+ },
1186
+ infoBadgeText: {
1187
+ fontFamily: AppFonts.interBold,
1188
+ fontSize: 8.5,
1189
+ },
1190
+ infoValue: {
1191
+ fontFamily: AppFonts.interBold,
1192
+ fontSize: 12,
1193
+ color: AppColors.primaryBlack,
1194
+ maxWidth: 180,
1195
+ textAlign: 'right',
1196
+ },
1197
+ rowCopyBtn: {
1198
+ padding: 4,
1199
+ borderRadius: 4,
1200
+ backgroundColor: AppColors.grayBackground,
1201
+ },
1202
+ });
1203
+
1204
+ export default DeviceInfoTab;