react-native-inapp-inspector 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/commonjs/constants/version.d.ts +1 -1
  2. package/dist/commonjs/constants/version.js +1 -1
  3. package/dist/esm/constants/version.d.ts +1 -1
  4. package/dist/esm/constants/version.js +1 -1
  5. package/package.json +2 -1
  6. package/src/analytics.ts +35 -0
  7. package/src/bundle.ts +25 -0
  8. package/src/components/AnalyticsDetail.tsx +865 -0
  9. package/src/components/AnalyticsEventCard.tsx +441 -0
  10. package/src/components/AnalyticsGraph.tsx +583 -0
  11. package/src/components/AnimatedEntrance.tsx +64 -0
  12. package/src/components/AppHeaderLogo.tsx +109 -0
  13. package/src/components/BrandCircleIcon.tsx +144 -0
  14. package/src/components/BrandSquareIcon.tsx +144 -0
  15. package/src/components/CodeSnippet.tsx +725 -0
  16. package/src/components/ConsoleLogCard.tsx +628 -0
  17. package/src/components/CopyButton.tsx +87 -0
  18. package/src/components/DiffViewer.tsx +82 -0
  19. package/src/components/DomainHeader.tsx +237 -0
  20. package/src/components/EmptyState.tsx +73 -0
  21. package/src/components/EndOfListFooter.tsx +100 -0
  22. package/src/components/ErrorBoundary.tsx +688 -0
  23. package/src/components/HeadersSection.tsx +192 -0
  24. package/src/components/HighlightText.tsx +100 -0
  25. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  26. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  27. package/src/components/Inspector/BundleTab.tsx +4731 -0
  28. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  29. package/src/components/Inspector/CrashDetail.tsx +941 -0
  30. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  31. package/src/components/Inspector/CrashTab.tsx +871 -0
  32. package/src/components/Inspector/FabLauncher.tsx +80 -0
  33. package/src/components/Inspector/InspectorContext.tsx +28 -0
  34. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  35. package/src/components/Inspector/LogDetail.tsx +1427 -0
  36. package/src/components/Inspector/MainScreen.tsx +258 -0
  37. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  38. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  39. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  40. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  41. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  42. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  43. package/src/components/Inspector/ReduxTab.tsx +954 -0
  44. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  45. package/src/components/Inspector/TabBar.tsx +187 -0
  46. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  47. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  48. package/src/components/JsonViewer.tsx +486 -0
  49. package/src/components/LogCard.tsx +491 -0
  50. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  51. package/src/components/MetaAccordion.tsx +340 -0
  52. package/src/components/MiniBarChart.tsx +42 -0
  53. package/src/components/MiniLineChart.tsx +33 -0
  54. package/src/components/NetworkIcons.tsx +2118 -0
  55. package/src/components/SectionHeader.tsx +113 -0
  56. package/src/components/SegmentedTabs.tsx +83 -0
  57. package/src/components/Slider.tsx +300 -0
  58. package/src/components/SourcePageCard.tsx +148 -0
  59. package/src/components/Toast.tsx +131 -0
  60. package/src/components/TouchableScale.tsx +91 -0
  61. package/src/components/TreeNode.tsx +186 -0
  62. package/src/console.ts +24 -0
  63. package/src/constants/index.ts +38 -0
  64. package/src/constants/version.ts +3 -0
  65. package/src/crash.ts +32 -0
  66. package/src/customHooks/analyticsLogger.ts +336 -0
  67. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  68. package/src/customHooks/consoleLogger.ts +497 -0
  69. package/src/customHooks/crashHandler.ts +944 -0
  70. package/src/customHooks/logFilters.ts +32 -0
  71. package/src/customHooks/networkLogger.ts +419 -0
  72. package/src/customHooks/performanceTracker.ts +1014 -0
  73. package/src/customHooks/reduxLogger.ts +406 -0
  74. package/src/customHooks/useAccordion.tsx +60 -0
  75. package/src/decorators/index.ts +184 -0
  76. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  77. package/src/helpers/index.ts +860 -0
  78. package/src/helpers/memoryManager.ts +138 -0
  79. package/src/helpers/searchQueryParser.ts +283 -0
  80. package/src/helpers/settingsStore.ts +171 -0
  81. package/src/helpers/telemetry.ts +505 -0
  82. package/src/helpers/toast.ts +17 -0
  83. package/src/i18n/index.ts +69 -0
  84. package/src/i18n/locales/en.json +1052 -0
  85. package/src/index.tsx +2336 -0
  86. package/src/native/NativeInspector.ts +397 -0
  87. package/src/native/NativeNetworkInspector.ts +24 -0
  88. package/src/network.ts +22 -0
  89. package/src/performance.ts +38 -0
  90. package/src/redux.ts +23 -0
  91. package/src/styles/AppColors.ts +408 -0
  92. package/src/styles/AppFonts.ts +8 -0
  93. package/src/styles/common.ts +209 -0
  94. package/src/styles/index.ts +1570 -0
  95. package/src/types/enums.ts +194 -0
  96. package/src/types/index.ts +34 -0
  97. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,973 @@
1
+ import React, {useMemo} from 'react';
2
+ import {
3
+ Alert,
4
+ Animated,
5
+ Linking,
6
+ Platform,
7
+ Pressable,
8
+ Text,
9
+ View,
10
+ } from 'react-native';
11
+ import LinearGradient from 'react-native-linear-gradient';
12
+ import {useInspector} from './InspectorContext';
13
+ import TouchableScale from '../TouchableScale';
14
+ import AppHeaderLogo from '../AppHeaderLogo';
15
+ import styles from '../../styles';
16
+ import {AppColors} from '../../styles/AppColors';
17
+ import {AppFonts} from '../../styles/AppFonts';
18
+ import {METHOD_COLORS} from '../../constants';
19
+ import {LIB_VERSION} from '../../constants';
20
+ import {Method} from '../../types';
21
+ import {getStatusColor, getAppName, formatTime, getSize} from '../../helpers';
22
+ import {getTelemetryConsentStatus} from '../../helpers/telemetry';
23
+ import {UpdateAvailableModal} from './UpdateAvailableModal';
24
+ import {
25
+ WhiteBackNavigation,
26
+ TrashIcon,
27
+ SettingsIcon,
28
+ CloseWhite,
29
+ ClockIcon,
30
+ SizeIcon,
31
+ AppleIcon,
32
+ AndroidIcon,
33
+ NpmIcon,
34
+ ResetIcon,
35
+ } from '../NetworkIcons';
36
+
37
+ const InspectorHeader = React.memo(() => {
38
+ const {
39
+ modalHeightPercent,
40
+ appIcon,
41
+ selected,
42
+ setSelected,
43
+ selectedEvent,
44
+ setSelectedEvent,
45
+ selectedLog,
46
+ setSelectedLog,
47
+ selectedReduxSlice,
48
+ setSelectedReduxSlice,
49
+ selectedReduxAction,
50
+ setSelectedReduxAction,
51
+ reduxState,
52
+ reduxLastActionMap,
53
+ showHeaderInfo,
54
+ setShowHeaderInfo,
55
+ updateAvailable,
56
+ latestNpmVersion,
57
+ clearAnim,
58
+ activePulseAnim,
59
+ unreadPulseAnim,
60
+ runClearAllWithAnimation,
61
+ settingsPage,
62
+ setSettingsPage,
63
+ resetToDefaults,
64
+ closeModal,
65
+ detailTitle,
66
+ activeTab,
67
+ environment,
68
+ visible,
69
+ selectedCrash,
70
+ setSelectedCrash,
71
+ } = useInspector();
72
+
73
+ const [showUpdateModal, setShowUpdateModal] = React.useState<boolean>(false);
74
+ const [isTelemetryActive, setIsTelemetryActive] =
75
+ React.useState<boolean>(false);
76
+ const telemetryPulseAnim = React.useRef(new Animated.Value(1)).current;
77
+
78
+ React.useEffect(() => {
79
+ getTelemetryConsentStatus().then(status => {
80
+ setIsTelemetryActive(status === 'granted');
81
+ });
82
+ }, [visible]);
83
+
84
+ React.useEffect(() => {
85
+ if (!isTelemetryActive) return;
86
+ const pulse = Animated.loop(
87
+ Animated.sequence([
88
+ Animated.timing(telemetryPulseAnim, {
89
+ toValue: 2.2,
90
+ duration: 1500,
91
+ useNativeDriver: true,
92
+ }),
93
+ Animated.timing(telemetryPulseAnim, {
94
+ toValue: 1,
95
+ duration: 0,
96
+ useNativeDriver: true,
97
+ }),
98
+ ]),
99
+ );
100
+ pulse.start();
101
+ return () => pulse.stop();
102
+ }, [isTelemetryActive, telemetryPulseAnim]);
103
+
104
+ const envConfig = useMemo(() => {
105
+ const rawEnv = (environment || (__DEV__ ? 'DEV' : 'PROD')).trim();
106
+ const clean = rawEnv.toUpperCase();
107
+
108
+ if (clean === 'DEV' || clean.includes('DEV') || clean === 'LOCAL') {
109
+ return {
110
+ label: rawEnv,
111
+ bg: `${AppColors.emerald500}40`, // emerald
112
+ border: `${AppColors.emerald400}8C`,
113
+ text: AppColors.mintGreenBorder,
114
+ };
115
+ }
116
+ if (clean === 'UAT' || clean === 'QA' || clean === 'TEST') {
117
+ return {
118
+ label: rawEnv,
119
+ bg: `${AppColors.amber500}47`, // amber
120
+ border: `${AppColors.amber400}99`,
121
+ text: AppColors.amberWarmBorder,
122
+ };
123
+ }
124
+ if (clean === 'PREPROD' || clean === 'STAGE' || clean === 'STAGING') {
125
+ return {
126
+ label: rawEnv,
127
+ bg: `${AppColors.purple500}47`, // purple
128
+ border: `${AppColors.purple400}99`,
129
+ text: AppColors.violetSoftBorder,
130
+ };
131
+ }
132
+ // PROD / Live
133
+ return {
134
+ label: rawEnv,
135
+ bg: `${AppColors.rose500}40`, // rose
136
+ border: `${AppColors.roseBorder}8C`,
137
+ text: AppColors.errorBorder,
138
+ };
139
+ }, [environment]);
140
+
141
+ const isDetailView =
142
+ (activeTab === 'apis' && selected != null) ||
143
+ (activeTab === 'analytics' && selectedEvent != null) ||
144
+ (activeTab === 'logs' && selectedLog != null) ||
145
+ (activeTab === 'redux' &&
146
+ (selectedReduxSlice != null || selectedReduxAction != null)) ||
147
+ (activeTab === 'crash' && selectedCrash != null);
148
+
149
+ const isSettingsView = settingsPage !== null;
150
+ const isAnySelected = isDetailView || isSettingsView;
151
+
152
+ const settingsModuleTitle = useMemo(() => {
153
+ switch (settingsPage) {
154
+ case 'apis':
155
+ return 'APIs (Network)';
156
+ case 'logs':
157
+ return 'Console Logs';
158
+ case 'performance':
159
+ return 'Performance Tracker';
160
+ case 'bundle':
161
+ return 'Bundle Analyzer';
162
+ case 'crash':
163
+ return 'Crash Protection';
164
+ case 'analytics':
165
+ return 'Analytics Logger';
166
+ case 'redux':
167
+ return 'Redux Inspector';
168
+ default:
169
+ return 'Settings & Modules';
170
+ }
171
+ }, [settingsPage]);
172
+
173
+ const headerTopPadding =
174
+ Platform.OS === 'ios' && modalHeightPercent >= 95 ? 44 : 0;
175
+
176
+ return (
177
+ <>
178
+ <LinearGradient
179
+ colors={['#4F46E5', '#7C3AED']}
180
+ start={{x: 0, y: 0}}
181
+ end={{x: 1, y: 1}}
182
+ style={styles.headerGradient}>
183
+ <View style={{paddingTop: headerTopPadding, width: '100%'}}>
184
+ <View style={styles.header}>
185
+ <View
186
+ style={[
187
+ styles.headerLeft,
188
+ {
189
+ flexDirection: 'row',
190
+ alignItems: 'center',
191
+ gap: 8,
192
+ flex: !isDetailView ? 1 : 0,
193
+ minWidth: 0,
194
+ },
195
+ ]}>
196
+ <TouchableScale
197
+ onPress={() => {
198
+ if (isSettingsView) {
199
+ if (settingsPage === 'main') {
200
+ setSettingsPage(null);
201
+ } else {
202
+ setSettingsPage('main');
203
+ }
204
+ return;
205
+ }
206
+ requestAnimationFrame(() => {
207
+ setSelected(null);
208
+ setSelectedEvent(null);
209
+ setSelectedLog(null);
210
+ setSelectedReduxSlice(null);
211
+ setSelectedReduxAction(null);
212
+ setSelectedCrash(null);
213
+ });
214
+ }}
215
+ hitSlop={15}
216
+ style={[
217
+ {
218
+ width: 38,
219
+ height: 38,
220
+ borderRadius: 19,
221
+ alignItems: 'center',
222
+ justifyContent: 'center',
223
+ backgroundColor: `${AppColors.white}2E`,
224
+ borderWidth: 1,
225
+ borderColor: `${AppColors.white}4D`,
226
+ },
227
+ !isAnySelected && {display: 'none'},
228
+ ]}>
229
+ {/* Soft outer glow to fake a blurred circle */}
230
+ <View
231
+ style={{
232
+ position: 'absolute',
233
+ width: 48,
234
+ height: 48,
235
+ borderRadius: 24,
236
+ backgroundColor: `${AppColors.white}1A`,
237
+ }}
238
+ />
239
+ <WhiteBackNavigation />
240
+ </TouchableScale>
241
+
242
+ {isSettingsView ? (
243
+ <View style={{gap: 2, flex: 1, minWidth: 0}}>
244
+ <View
245
+ style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
246
+ <Text
247
+ style={{
248
+ fontFamily: AppFonts.interBold,
249
+ fontSize: 16,
250
+ color: AppColors.white,
251
+ letterSpacing: -0.2,
252
+ }}
253
+ numberOfLines={1}>
254
+ {settingsModuleTitle}
255
+ </Text>
256
+ {settingsPage === 'main' && (
257
+ <View
258
+ style={{
259
+ backgroundColor: `${AppColors.white}26`,
260
+ paddingHorizontal: 6,
261
+ paddingVertical: 1.5,
262
+ borderRadius: 10,
263
+ borderWidth: 1,
264
+ borderColor: `${AppColors.white}20`,
265
+ }}>
266
+ <Text
267
+ style={{
268
+ fontFamily: AppFonts.interBold,
269
+ fontSize: 9,
270
+ color: AppColors.white,
271
+ }}>
272
+ v{LIB_VERSION}
273
+ </Text>
274
+ </View>
275
+ )}
276
+ </View>
277
+ <Text
278
+ style={{
279
+ fontFamily: AppFonts.interRegular,
280
+ fontSize: 10.5,
281
+ color: `${AppColors.white}CC`,
282
+ }}
283
+ numberOfLines={1}>
284
+ {settingsPage === 'main'
285
+ ? 'Manage modules and preferences'
286
+ : 'Configure module parameters'}
287
+ </Text>
288
+ </View>
289
+ ) : !isAnySelected ? (
290
+ <View
291
+ style={{
292
+ flexDirection: 'row',
293
+ alignItems: 'center',
294
+ gap: 8,
295
+ flex: 1,
296
+ minWidth: 0,
297
+ marginRight: 6,
298
+ }}>
299
+ <AppHeaderLogo size={46} customIcon={appIcon} />
300
+ <View style={{gap: 2.5, flex: 1, minWidth: 0}}>
301
+ <View
302
+ style={{
303
+ flexDirection: 'row',
304
+ alignItems: 'center',
305
+ gap: 6,
306
+ minWidth: 0,
307
+ }}>
308
+ <Text
309
+ style={[styles.headerTitle, {flexShrink: 1}]}
310
+ numberOfLines={1}
311
+ ellipsizeMode="tail">
312
+ {getAppName()}
313
+ </Text>
314
+ <View
315
+ style={[
316
+ styles.envBadge,
317
+ {
318
+ backgroundColor: envConfig.bg,
319
+ borderColor: envConfig.border,
320
+ flexShrink: 0,
321
+ paddingVertical: 1.5,
322
+ marginBottom: 0,
323
+ },
324
+ ]}>
325
+ <Text
326
+ style={[styles.envBadgeText, {color: envConfig.text}]}>
327
+ {envConfig.label}
328
+ </Text>
329
+ </View>
330
+ {updateAvailable && (
331
+ <Pressable
332
+ hitSlop={10}
333
+ onPress={() => setShowUpdateModal(true)}
334
+ style={{
335
+ flexDirection: 'row',
336
+ alignItems: 'center',
337
+ backgroundColor: '#F59E0B',
338
+ borderRadius: 5,
339
+ paddingHorizontal: 5.5,
340
+ paddingVertical: 1.5,
341
+ gap: 3.5,
342
+ shadowColor: '#F59E0B',
343
+ shadowOffset: {width: 0, height: 1.5},
344
+ shadowOpacity: 0.35,
345
+ shadowRadius: 3,
346
+ elevation: 3,
347
+ flexShrink: 0,
348
+ }}>
349
+ <Animated.View
350
+ style={{
351
+ width: 5,
352
+ height: 5,
353
+ borderRadius: 2.5,
354
+ backgroundColor: '#FFFFFF',
355
+ opacity: activePulseAnim,
356
+ transform: [{scale: unreadPulseAnim}],
357
+ }}
358
+ />
359
+ <Text
360
+ style={{
361
+ fontFamily: AppFonts.interBold,
362
+ fontSize: 9,
363
+ color: '#FFFFFF',
364
+ letterSpacing: 0.3,
365
+ }}>
366
+ UPDATE ⚡
367
+ </Text>
368
+ </Pressable>
369
+ )}
370
+ </View>
371
+
372
+ {/* OS & NPM Version Representation */}
373
+ <View
374
+ style={{
375
+ flexDirection: 'row',
376
+ alignItems: 'center',
377
+ gap: 5,
378
+ minWidth: 0,
379
+ }}>
380
+ <View
381
+ style={{
382
+ flexDirection: 'row',
383
+ alignItems: 'center',
384
+ backgroundColor: `${AppColors.white}1F`,
385
+ borderRadius: 5,
386
+ paddingHorizontal: 6,
387
+ paddingVertical: 2,
388
+ gap: 4,
389
+ borderWidth: 1,
390
+ borderColor: `${AppColors.white}2E`,
391
+ flexShrink: 0,
392
+ }}>
393
+ {Platform.OS === 'ios' ? (
394
+ <AppleIcon color={`${AppColors.white}E6`} size={10} />
395
+ ) : (
396
+ <AndroidIcon color={`${AppColors.white}E6`} size={10} />
397
+ )}
398
+ <Text
399
+ style={{
400
+ fontFamily: AppFonts.interMedium,
401
+ fontSize: 9.5,
402
+ color: `${AppColors.white}EB`,
403
+ letterSpacing: 0.1,
404
+ }}
405
+ numberOfLines={1}>
406
+ {Platform.OS === 'ios'
407
+ ? `iOS ${Platform.Version}`
408
+ : `Android ${Platform.Version}`}
409
+ </Text>
410
+ </View>
411
+
412
+ <Pressable
413
+ onPress={() => {
414
+ if (updateAvailable) {
415
+ setShowUpdateModal(true);
416
+ } else {
417
+ Linking.openURL(
418
+ 'https://www.npmjs.com/package/react-native-inapp-inspector',
419
+ ).catch(() => {});
420
+ }
421
+ }}
422
+ style={{
423
+ backgroundColor: '#FFFFFF',
424
+ borderRadius: 5,
425
+ paddingHorizontal: 6,
426
+ paddingVertical: 2,
427
+ flexDirection: 'row',
428
+ alignItems: 'center',
429
+ gap: 4,
430
+ flexShrink: 0,
431
+ shadowColor: '#000000',
432
+ shadowOffset: {width: 0, height: 1},
433
+ shadowOpacity: 0.15,
434
+ shadowRadius: 2,
435
+ elevation: 2,
436
+ }}>
437
+ <NpmIcon size={11} color="#CB3837" />
438
+ <Text
439
+ style={{
440
+ fontFamily: AppFonts.interBold,
441
+ fontSize: 9.5,
442
+ color: '#CB3837',
443
+ letterSpacing: 0.2,
444
+ }}
445
+ numberOfLines={1}>
446
+ v{LIB_VERSION}
447
+ </Text>
448
+ {updateAvailable && (
449
+ <Text
450
+ style={{
451
+ fontFamily: AppFonts.interBold,
452
+ fontSize: 9,
453
+ color: '#F59E0B',
454
+ }}>
455
+
456
+ </Text>
457
+ )}
458
+ </Pressable>
459
+
460
+ {isTelemetryActive && (
461
+ <Pressable
462
+ onPress={() =>
463
+ Alert.alert(
464
+ 'Anonymous Telemetry Active',
465
+ 'Anonymous diagnostics (such as React Native version, JavaScript engine, and device model) are currently enabled to help improve library tooling.\n\nNo user data or network payloads are captured. You can toggle this anytime in Settings.',
466
+ [{text: 'Got it'}],
467
+ )
468
+ }
469
+ style={{
470
+ flexDirection: 'row',
471
+ alignItems: 'center',
472
+ backgroundColor: 'rgba(16, 185, 129, 0.22)',
473
+ borderRadius: 5,
474
+ paddingHorizontal: 5.5,
475
+ paddingVertical: 2,
476
+ gap: 4.5,
477
+ borderWidth: 1,
478
+ borderColor: 'rgba(52, 211, 153, 0.45)',
479
+ flexShrink: 0,
480
+ }}>
481
+ <View
482
+ style={{
483
+ width: 6,
484
+ height: 6,
485
+ alignItems: 'center',
486
+ justifyContent: 'center',
487
+ }}>
488
+ <Animated.View
489
+ style={{
490
+ position: 'absolute',
491
+ width: 6,
492
+ height: 6,
493
+ borderRadius: 3,
494
+ backgroundColor: '#34D399',
495
+ opacity: telemetryPulseAnim.interpolate({
496
+ inputRange: [1, 2.2],
497
+ outputRange: [0.8, 0],
498
+ }),
499
+ transform: [{scale: telemetryPulseAnim}],
500
+ }}
501
+ />
502
+ <View
503
+ style={{
504
+ width: 4,
505
+ height: 4,
506
+ borderRadius: 2,
507
+ backgroundColor: '#10B981',
508
+ }}
509
+ />
510
+ </View>
511
+ <Text
512
+ style={{
513
+ fontFamily: AppFonts.interBold,
514
+ fontSize: 9,
515
+ color: '#6EE7B7',
516
+ letterSpacing: 0.2,
517
+ }}>
518
+ LIVE
519
+ </Text>
520
+ </Pressable>
521
+ )}
522
+ </View>
523
+ </View>
524
+ </View>
525
+ ) : null}
526
+ </View>
527
+
528
+ {isDetailView && (
529
+ <View style={styles.headerCenter}>
530
+ {activeTab === 'apis' && selected != null ? (
531
+ <View style={styles.headerDetailCenter}>
532
+ <View style={styles.headerDetailRow}>
533
+ <View
534
+ style={[
535
+ styles.headerMethodBadge,
536
+ {
537
+ backgroundColor:
538
+ METHOD_COLORS[selected.method as Method] ??
539
+ AppColors.grayText,
540
+ },
541
+ ]}>
542
+ <Text style={styles.headerMethodText}>
543
+ {selected.method}
544
+ </Text>
545
+ </View>
546
+ <Text
547
+ style={styles.headerDetailTitle}
548
+ numberOfLines={1}
549
+ ellipsizeMode="middle">
550
+ {detailTitle}
551
+ </Text>
552
+ </View>
553
+ <View style={styles.headerDetailSubRow}>
554
+ <View
555
+ style={{
556
+ flexDirection: 'row',
557
+ alignItems: 'center',
558
+ gap: 5,
559
+ paddingHorizontal: 8,
560
+ paddingVertical: 3,
561
+ borderRadius: 20,
562
+ backgroundColor: `${getStatusColor(selected.status)}26`,
563
+ borderWidth: 1,
564
+ borderColor: `${getStatusColor(selected.status)}55`,
565
+ }}>
566
+ <View
567
+ style={[
568
+ styles.headerStatusDot,
569
+ {
570
+ backgroundColor: getStatusColor(selected.status),
571
+ },
572
+ ]}
573
+ />
574
+ <Text
575
+ style={[
576
+ styles.headerSubTitle,
577
+ {fontFamily: AppFonts.interBold},
578
+ ]}>
579
+ {selected.status === 0
580
+ ? 'Failed'
581
+ : selected.status ?? 'Pending'}
582
+ </Text>
583
+ </View>
584
+ <View
585
+ style={{
586
+ flexDirection: 'row',
587
+ alignItems: 'center',
588
+ gap: 4,
589
+ paddingHorizontal: 8,
590
+ paddingVertical: 3,
591
+ borderRadius: 20,
592
+ backgroundColor: `${AppColors.white}29`,
593
+ }}>
594
+ <ClockIcon color={AppColors.white} size={11} />
595
+ <Text style={styles.headerSubTitle}>
596
+ {selected.duration != null
597
+ ? `${selected.duration}ms`
598
+ : '—'}
599
+ </Text>
600
+ </View>
601
+ {selected.response != null && (
602
+ <View
603
+ style={{
604
+ flexDirection: 'row',
605
+ alignItems: 'center',
606
+ gap: 4,
607
+ paddingHorizontal: 8,
608
+ paddingVertical: 3,
609
+ borderRadius: 20,
610
+ backgroundColor: `${AppColors.white}29`,
611
+ }}>
612
+ <SizeIcon color={AppColors.white} size={11} />
613
+ <Text style={styles.headerSubTitle}>
614
+ {getSize(selected.response)}
615
+ </Text>
616
+ </View>
617
+ )}
618
+ </View>
619
+ </View>
620
+ ) : activeTab === 'analytics' && selectedEvent != null ? (
621
+ <View style={styles.headerDetailCenter}>
622
+ <View style={styles.headerDetailRow}>
623
+ <View
624
+ style={[
625
+ styles.headerMethodBadge,
626
+ {
627
+ backgroundColor:
628
+ selectedEvent.source === 'firebase'
629
+ ? `${AppColors.firebaseOrange}4D`
630
+ : `${AppColors.purple}4D`,
631
+ },
632
+ ]}>
633
+ <Text style={styles.headerMethodText}>
634
+ {selectedEvent.source === 'firebase' ? 'FB' : 'MAN'}
635
+ </Text>
636
+ </View>
637
+ <Text
638
+ style={styles.headerDetailTitle}
639
+ numberOfLines={1}
640
+ ellipsizeMode="middle">
641
+ {selectedEvent.name}
642
+ </Text>
643
+ </View>
644
+ <View style={styles.headerDetailSubRow}>
645
+ <View
646
+ style={[
647
+ styles.headerStatusDot,
648
+ {
649
+ backgroundColor:
650
+ selectedEvent.source === 'firebase'
651
+ ? AppColors.firebaseOrange
652
+ : AppColors.purple,
653
+ },
654
+ ]}
655
+ />
656
+ <Text style={styles.headerSubTitle}>
657
+ {Object.keys(selectedEvent.params).length} param
658
+ {Object.keys(selectedEvent.params).length !== 1
659
+ ? 's'
660
+ : ''}
661
+ {' · '}
662
+ {selectedEvent.source}
663
+ </Text>
664
+ </View>
665
+ </View>
666
+ ) : activeTab === 'logs' && selectedLog != null ? (
667
+ <View style={styles.headerDetailCenter}>
668
+ <View style={styles.headerDetailRow}>
669
+ <View
670
+ style={[
671
+ styles.headerMethodBadge,
672
+ {
673
+ backgroundColor:
674
+ selectedLog.type === 'error'
675
+ ? `${AppColors.errorColor}4D`
676
+ : selectedLog.type === 'warn'
677
+ ? `${AppColors.lightOrange}4D`
678
+ : `${AppColors.purple}4D`,
679
+ },
680
+ ]}>
681
+ <Text style={styles.headerMethodText}>
682
+ {selectedLog.type.toUpperCase()}
683
+ </Text>
684
+ </View>
685
+ <Text
686
+ style={styles.headerDetailTitle}
687
+ numberOfLines={1}
688
+ ellipsizeMode="middle">
689
+ console.
690
+ {selectedLog.sourceMethod || selectedLog.type || 'log'}
691
+ </Text>
692
+ </View>
693
+ <View style={styles.headerDetailSubRow}>
694
+ <View
695
+ style={{
696
+ flexDirection: 'row',
697
+ alignItems: 'center',
698
+ gap: 4,
699
+ paddingHorizontal: 8,
700
+ paddingVertical: 3,
701
+ borderRadius: 20,
702
+ backgroundColor: `${AppColors.white}29`,
703
+ }}>
704
+ <ClockIcon color={AppColors.white} size={11} />
705
+ <Text style={styles.headerSubTitle}>
706
+ {formatTime(selectedLog.timestamp)}
707
+ </Text>
708
+ </View>
709
+ </View>
710
+ </View>
711
+ ) : activeTab === 'redux' && selectedReduxSlice != null ? (
712
+ (() => {
713
+ const sliceData = reduxState?.[selectedReduxSlice];
714
+ const keyCount =
715
+ sliceData && typeof sliceData === 'object'
716
+ ? Object.keys(sliceData).length
717
+ : typeof sliceData !== 'undefined'
718
+ ? 1
719
+ : 0;
720
+ const sliceSize = getSize(sliceData);
721
+ const lastAction = reduxLastActionMap[selectedReduxSlice];
722
+
723
+ return (
724
+ <View style={styles.headerDetailCenter}>
725
+ <View style={styles.headerDetailRow}>
726
+ <View
727
+ style={[
728
+ styles.headerMethodBadge,
729
+ {
730
+ backgroundColor: `${AppColors.purple}4D`,
731
+ },
732
+ ]}>
733
+ <Text style={styles.headerMethodText}>SLICE</Text>
734
+ </View>
735
+ <Text
736
+ style={styles.headerDetailTitle}
737
+ numberOfLines={1}
738
+ ellipsizeMode="middle">
739
+ {selectedReduxSlice}
740
+ </Text>
741
+ </View>
742
+ <View style={styles.headerDetailSubRow}>
743
+ <View
744
+ style={[
745
+ styles.headerStatusDot,
746
+ {backgroundColor: AppColors.liveGreen},
747
+ ]}
748
+ />
749
+ <Text style={styles.headerSubTitle}>Live</Text>
750
+ <Text style={[styles.headerSubTitle, {opacity: 0.6}]}>
751
+
752
+ </Text>
753
+ <Text style={styles.headerSubTitle}>
754
+ {keyCount} keys
755
+ </Text>
756
+ <Text style={[styles.headerSubTitle, {opacity: 0.6}]}>
757
+
758
+ </Text>
759
+ <Text style={styles.headerSubTitle}>{sliceSize}</Text>
760
+ {lastAction?.timestamp && (
761
+ <>
762
+ <Text
763
+ style={[styles.headerSubTitle, {opacity: 0.6}]}>
764
+
765
+ </Text>
766
+ <View
767
+ style={{
768
+ flexDirection: 'row',
769
+ alignItems: 'center',
770
+ gap: 3,
771
+ }}>
772
+ <ClockIcon color={AppColors.white} size={10} />
773
+ <Text style={styles.headerSubTitle}>
774
+ {lastAction.timestamp}
775
+ </Text>
776
+ </View>
777
+ </>
778
+ )}
779
+ </View>
780
+ </View>
781
+ );
782
+ })()
783
+ ) : activeTab === 'redux' && selectedReduxAction != null ? (
784
+ <View style={styles.headerDetailCenter}>
785
+ <View style={styles.headerDetailRow}>
786
+ <View
787
+ style={[
788
+ styles.headerMethodBadge,
789
+ {
790
+ backgroundColor: `${AppColors.brandPurple}4D`,
791
+ },
792
+ ]}>
793
+ <Text style={styles.headerMethodText}>ACTION</Text>
794
+ </View>
795
+ <Text
796
+ style={styles.headerDetailTitle}
797
+ numberOfLines={1}
798
+ ellipsizeMode="middle">
799
+ {selectedReduxAction.type}
800
+ </Text>
801
+ </View>
802
+ <View style={styles.headerDetailSubRow}>
803
+ <View
804
+ style={[
805
+ styles.headerStatusDot,
806
+ {backgroundColor: AppColors.purple},
807
+ ]}
808
+ />
809
+ <Text style={styles.headerSubTitle}>
810
+ {selectedReduxAction.timestamp || 'Dispatched'}
811
+ </Text>
812
+ </View>
813
+ </View>
814
+ ) : activeTab === 'crash' && selectedCrash != null ? (
815
+ <View style={styles.headerDetailCenter}>
816
+ <View style={styles.headerDetailRow}>
817
+ <View
818
+ style={[
819
+ styles.headerMethodBadge,
820
+ {
821
+ backgroundColor: selectedCrash.isFatal
822
+ ? AppColors.red600
823
+ : AppColors.amber600,
824
+ },
825
+ ]}>
826
+ <Text style={styles.headerMethodText}>
827
+ {selectedCrash.isFatal
828
+ ? 'FATAL'
829
+ : selectedCrash.type.toUpperCase()}
830
+ </Text>
831
+ </View>
832
+ <Text
833
+ style={styles.headerDetailTitle}
834
+ numberOfLines={1}
835
+ ellipsizeMode="middle">
836
+ {selectedCrash.name || selectedCrash.message}
837
+ </Text>
838
+ </View>
839
+ <View style={styles.headerDetailSubRow}>
840
+ <View
841
+ style={[
842
+ styles.headerStatusDot,
843
+ {
844
+ backgroundColor: selectedCrash.isFatal
845
+ ? AppColors.red600
846
+ : AppColors.amber500,
847
+ },
848
+ ]}
849
+ />
850
+ <Text style={styles.headerSubTitle}>
851
+ {selectedCrash.timeStr ||
852
+ new Date(selectedCrash.timestamp).toLocaleTimeString()}
853
+ </Text>
854
+ {selectedCrash.deviceInfo?.platform && (
855
+ <>
856
+ <Text style={[styles.headerSubTitle, {opacity: 0.6}]}>
857
+
858
+ </Text>
859
+ <Text style={styles.headerSubTitle}>
860
+ {selectedCrash.deviceInfo.platform.toUpperCase()}
861
+ </Text>
862
+ </>
863
+ )}
864
+ </View>
865
+ </View>
866
+ ) : null}
867
+ </View>
868
+ )}
869
+
870
+ <View
871
+ style={[
872
+ styles.headerRight,
873
+ {
874
+ flexDirection: 'row',
875
+ alignItems: 'center',
876
+ justifyContent: 'flex-end',
877
+ flexShrink: 0,
878
+ gap: 8,
879
+ },
880
+ ]}>
881
+ {isSettingsView && (
882
+ <TouchableScale
883
+ onPress={() => {
884
+ Alert.alert(
885
+ 'Reset All Settings',
886
+ 'This restores all module visibility and UI preferences to defaults. Continue?',
887
+ [
888
+ {text: 'Cancel', style: 'cancel'},
889
+ {
890
+ text: 'Reset',
891
+ style: 'destructive',
892
+ onPress: resetToDefaults,
893
+ },
894
+ ],
895
+ );
896
+ }}
897
+ hitSlop={15}
898
+ style={styles.closeButtonSquare}>
899
+ <ResetIcon color={AppColors.white} size={14} />
900
+ </TouchableScale>
901
+ )}
902
+
903
+ {!isAnySelected && (
904
+ <TouchableScale
905
+ onPress={() => {
906
+ Alert.alert(
907
+ 'Clear Everything',
908
+ 'This clears all tabs — APIs, Logs, Analytics, Redux timeline and Crash history. Continue?',
909
+ [
910
+ {text: 'Cancel', style: 'cancel'},
911
+ {
912
+ text: 'Clear All',
913
+ onPress: runClearAllWithAnimation,
914
+ style: 'destructive',
915
+ },
916
+ ],
917
+ );
918
+ }}
919
+ hitSlop={15}
920
+ style={styles.closeButtonSquare}>
921
+ <Animated.View
922
+ style={{
923
+ transform: [
924
+ {
925
+ rotate: clearAnim.interpolate({
926
+ inputRange: [0, 1],
927
+ outputRange: ['0deg', '-25deg'],
928
+ }),
929
+ },
930
+ {
931
+ scale: clearAnim.interpolate({
932
+ inputRange: [0, 0.5, 1],
933
+ outputRange: [1, 1.25, 1],
934
+ }),
935
+ },
936
+ ],
937
+ }}>
938
+ <TrashIcon color={AppColors.white} size={14} />
939
+ </Animated.View>
940
+ </TouchableScale>
941
+ )}
942
+
943
+ {!isAnySelected && (
944
+ <TouchableScale
945
+ onPress={() => setSettingsPage('main')}
946
+ hitSlop={15}
947
+ style={styles.closeButtonSquare}>
948
+ <SettingsIcon color={AppColors.white} size={14} />
949
+ </TouchableScale>
950
+ )}
951
+
952
+ <TouchableScale
953
+ onPress={closeModal}
954
+ hitSlop={15}
955
+ style={styles.closeButtonSquare}>
956
+ <CloseWhite size={14} />
957
+ </TouchableScale>
958
+ </View>
959
+ </View>
960
+ </View>
961
+ </LinearGradient>
962
+
963
+ {/* Dedicated Update Available Details Modal */}
964
+ <UpdateAvailableModal
965
+ visible={showUpdateModal}
966
+ latestVersion={latestNpmVersion}
967
+ onClose={() => setShowUpdateModal(false)}
968
+ />
969
+ </>
970
+ );
971
+ });
972
+
973
+ export default InspectorHeader;