react-native-inapp-inspector 1.1.15 → 1.1.17

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 (106) hide show
  1. package/README.md +150 -130
  2. package/dist/commonjs/components/AnimatedEntrance.js +3 -0
  3. package/dist/commonjs/components/AppHeaderLogo.d.ts +8 -0
  4. package/dist/commonjs/components/AppHeaderLogo.js +85 -0
  5. package/dist/commonjs/components/CopyButton.d.ts +1 -1
  6. package/dist/commonjs/components/CopyButton.js +4 -4
  7. package/dist/commonjs/components/EndOfListFooter.js +2 -3
  8. package/dist/commonjs/components/Inspector/AnalyticsTab.d.ts +1 -1
  9. package/dist/commonjs/components/Inspector/AnalyticsTab.js +7 -8
  10. package/dist/commonjs/components/Inspector/BundleTab.d.ts +41 -0
  11. package/dist/commonjs/components/Inspector/BundleTab.js +2309 -0
  12. package/dist/commonjs/components/Inspector/ConsoleTab.d.ts +1 -1
  13. package/dist/commonjs/components/Inspector/ConsoleTab.js +9 -9
  14. package/dist/commonjs/components/Inspector/InspectorHeader.d.ts +1 -1
  15. package/dist/commonjs/components/Inspector/InspectorHeader.js +187 -54
  16. package/dist/commonjs/components/Inspector/LogDetail.d.ts +1 -1
  17. package/dist/commonjs/components/Inspector/LogDetail.js +2 -2
  18. package/dist/commonjs/components/Inspector/MainScreen.js +86 -7
  19. package/dist/commonjs/components/Inspector/NetworkDetail.d.ts +1 -1
  20. package/dist/commonjs/components/Inspector/NetworkDetail.js +2 -2
  21. package/dist/commonjs/components/Inspector/NetworkTab.d.ts +1 -1
  22. package/dist/commonjs/components/Inspector/NetworkTab.js +10 -9
  23. package/dist/commonjs/components/Inspector/ReduxDetail.d.ts +3 -0
  24. package/dist/commonjs/components/Inspector/ReduxDetail.js +576 -0
  25. package/dist/commonjs/components/Inspector/ReduxTab.d.ts +1 -1
  26. package/dist/commonjs/components/Inspector/ReduxTab.js +435 -51
  27. package/dist/commonjs/components/Inspector/SettingsPanel.js +8 -2
  28. package/dist/commonjs/components/Inspector/TabBar.d.ts +1 -1
  29. package/dist/commonjs/components/Inspector/TabBar.js +11 -8
  30. package/dist/commonjs/components/JsonViewer.d.ts +2 -2
  31. package/dist/commonjs/components/JsonViewer.js +93 -32
  32. package/dist/commonjs/components/LogCard.js +3 -4
  33. package/dist/commonjs/components/NetworkIcons.d.ts +8 -0
  34. package/dist/commonjs/components/NetworkIcons.js +54 -1
  35. package/dist/commonjs/components/ReduxTreeView.d.ts +2 -2
  36. package/dist/commonjs/components/ReduxTreeView.js +2 -3
  37. package/dist/commonjs/components/SegmentedTabs.d.ts +1 -1
  38. package/dist/commonjs/components/SegmentedTabs.js +2 -2
  39. package/dist/commonjs/constants/version.d.ts +1 -1
  40. package/dist/commonjs/constants/version.js +1 -1
  41. package/dist/commonjs/customHooks/consoleLogger.js +37 -9
  42. package/dist/commonjs/customHooks/reduxLogger.js +4 -2
  43. package/dist/commonjs/helpers/index.js +6 -2
  44. package/dist/commonjs/index.js +15 -1
  45. package/dist/commonjs/styles/AppColors.js +163 -163
  46. package/dist/commonjs/styles/index.d.ts +14 -0
  47. package/dist/commonjs/styles/index.js +14 -0
  48. package/dist/commonjs/types/enums.d.ts +2 -2
  49. package/dist/commonjs/types/interfaces.d.ts +11 -0
  50. package/dist/esm/components/AnimatedEntrance.js +4 -1
  51. package/dist/esm/components/AppHeaderLogo.d.ts +8 -0
  52. package/dist/esm/components/AppHeaderLogo.js +45 -0
  53. package/dist/esm/components/CopyButton.d.ts +1 -1
  54. package/dist/esm/components/CopyButton.js +5 -5
  55. package/dist/esm/components/EndOfListFooter.js +2 -2
  56. package/dist/esm/components/Inspector/AnalyticsTab.d.ts +1 -1
  57. package/dist/esm/components/Inspector/AnalyticsTab.js +7 -8
  58. package/dist/esm/components/Inspector/BundleTab.d.ts +41 -0
  59. package/dist/esm/components/Inspector/BundleTab.js +2271 -0
  60. package/dist/esm/components/Inspector/ConsoleTab.d.ts +1 -1
  61. package/dist/esm/components/Inspector/ConsoleTab.js +9 -9
  62. package/dist/esm/components/Inspector/InspectorHeader.d.ts +1 -1
  63. package/dist/esm/components/Inspector/InspectorHeader.js +153 -53
  64. package/dist/esm/components/Inspector/LogDetail.d.ts +1 -1
  65. package/dist/esm/components/Inspector/LogDetail.js +2 -2
  66. package/dist/esm/components/Inspector/MainScreen.js +53 -7
  67. package/dist/esm/components/Inspector/NetworkDetail.d.ts +1 -1
  68. package/dist/esm/components/Inspector/NetworkDetail.js +2 -2
  69. package/dist/esm/components/Inspector/NetworkTab.d.ts +1 -1
  70. package/dist/esm/components/Inspector/NetworkTab.js +10 -9
  71. package/dist/esm/components/Inspector/ReduxDetail.d.ts +3 -0
  72. package/dist/esm/components/Inspector/ReduxDetail.js +538 -0
  73. package/dist/esm/components/Inspector/ReduxTab.d.ts +1 -1
  74. package/dist/esm/components/Inspector/ReduxTab.js +404 -53
  75. package/dist/esm/components/Inspector/SettingsPanel.js +9 -3
  76. package/dist/esm/components/Inspector/TabBar.d.ts +1 -1
  77. package/dist/esm/components/Inspector/TabBar.js +12 -9
  78. package/dist/esm/components/JsonViewer.d.ts +2 -2
  79. package/dist/esm/components/JsonViewer.js +94 -33
  80. package/dist/esm/components/LogCard.js +3 -4
  81. package/dist/esm/components/NetworkIcons.d.ts +8 -0
  82. package/dist/esm/components/NetworkIcons.js +47 -1
  83. package/dist/esm/components/ReduxTreeView.d.ts +2 -2
  84. package/dist/esm/components/ReduxTreeView.js +2 -2
  85. package/dist/esm/components/SegmentedTabs.d.ts +1 -1
  86. package/dist/esm/components/SegmentedTabs.js +2 -2
  87. package/dist/esm/constants/version.d.ts +1 -1
  88. package/dist/esm/constants/version.js +1 -1
  89. package/dist/esm/customHooks/consoleLogger.js +37 -9
  90. package/dist/esm/customHooks/reduxLogger.js +4 -2
  91. package/dist/esm/helpers/index.js +6 -2
  92. package/dist/esm/index.js +15 -1
  93. package/dist/esm/styles/AppColors.js +163 -163
  94. package/dist/esm/styles/index.d.ts +14 -0
  95. package/dist/esm/styles/index.js +14 -0
  96. package/dist/esm/types/enums.d.ts +2 -2
  97. package/dist/esm/types/interfaces.d.ts +11 -0
  98. package/example/App.tsx +62 -2
  99. package/example/README.md +33 -77
  100. package/example/assets/app_icon.png +0 -0
  101. package/example/ios/Podfile.lock +3 -3
  102. package/example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json +18 -9
  103. package/example/ios/example/Images.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  104. package/example/package-lock.json +824 -10
  105. package/example/package.json +6 -2
  106. package/package.json +4 -2
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- declare const ConsoleTab: () => React.JSX.Element;
2
+ declare const ConsoleTab: React.MemoExoticComponent<() => React.JSX.Element>;
3
3
  export default ConsoleTab;
@@ -11,14 +11,14 @@ import styles from '../../styles';
11
11
  import { AppColors } from '../../styles/AppColors';
12
12
  import { AppFonts } from '../../styles/AppFonts';
13
13
  import { SearchIcon, ClearIcon, SortArrowIcon, TrashIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, AnalyticsIcon, HeaderPauseIcon, } from '../NetworkIcons';
14
- const ConsoleTab = () => {
14
+ const ConsoleTab = React.memo(() => {
15
15
  const { t } = useTranslation();
16
16
  const { logSearch, setLogSearch, logSortOrder, setLogSortOrder, handleDelete, logFilters, setLogFilters, logCounts, filteredConsoleLogs, visibleConsoleLogs, isConsolePaused, setIsConsolePaused, } = useInspector();
17
17
  const renderItem = useCallback(({ item, index }) => (<AnimatedEntrance index={index} distance={8}>
18
18
  <ConsoleLogCard item={item} searchStr={logSearch}/>
19
19
  </AnimatedEntrance>), [logSearch]);
20
20
  const keyExtractor = useCallback((item, index) => item?.id?.toString() ?? index.toString(), []);
21
- const listHeader = useMemo(() => (() => {
21
+ const listHeader = useMemo(() => {
22
22
  const total = visibleConsoleLogs.length;
23
23
  const filtered = filteredConsoleLogs.length;
24
24
  const isAllSelected = logFilters.has('all') ||
@@ -28,8 +28,8 @@ const ConsoleTab = () => {
28
28
  styles.resultCount,
29
29
  { marginBottom: 4, marginTop: 12 },
30
30
  ]}>
31
- Showing ({filtered}/{total}) logs showing
32
- </Text>);
31
+ Showing ({filtered}/{total}) logs showing
32
+ </Text>);
33
33
  }
34
34
  else {
35
35
  const activeFilterNames = Array.from(logFilters)
@@ -46,11 +46,11 @@ const ConsoleTab = () => {
46
46
  styles.resultCount,
47
47
  { marginBottom: 4, marginTop: 12 },
48
48
  ]}>
49
- Filtering with {activeFilterNames.join(', ')} (
50
- {filtered}/{total}) logs is showing
51
- </Text>);
49
+ Filtering with {activeFilterNames.join(', ')} (
50
+ {filtered}/{total}) logs is showing
51
+ </Text>);
52
52
  }
53
- })(), [visibleConsoleLogs, filteredConsoleLogs, logFilters]);
53
+ }, [visibleConsoleLogs.length, filteredConsoleLogs.length, logFilters]);
54
54
  return (<View style={{ flex: 1 }}>
55
55
  <View style={{
56
56
  backgroundColor: AppColors.white,
@@ -368,5 +368,5 @@ const ConsoleTab = () => {
368
368
  filteredConsoleLogs.length === 0 && { flexGrow: 1 },
369
369
  ]} keyboardShouldPersistTaps="handled"/>
370
370
  </View>);
371
- };
371
+ });
372
372
  export default ConsoleTab;
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- declare const InspectorHeader: () => React.JSX.Element;
2
+ declare const InspectorHeader: React.MemoExoticComponent<() => React.JSX.Element>;
3
3
  export default InspectorHeader;
@@ -1,21 +1,58 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { Alert, Animated, Linking, Platform, Pressable, Text, View, } from 'react-native';
3
3
  import LinearGradient from 'react-native-linear-gradient';
4
4
  import { useInspector } from './InspectorContext';
5
5
  import TouchableScale from '../TouchableScale';
6
- import BrandSquareIcon from '../BrandSquareIcon';
6
+ import AppHeaderLogo from '../AppHeaderLogo';
7
7
  import styles from '../../styles';
8
8
  import { AppColors } from '../../styles/AppColors';
9
9
  import { AppFonts } from '../../styles/AppFonts';
10
10
  import { METHOD_COLORS } from '../../constants';
11
11
  import { LIB_VERSION } from '../../constants';
12
- import { getStatusColor } from '../../helpers';
13
- import { getAppName } from '../../helpers';
14
- import { getBundleIdentifier } from '../../helpers';
15
- import { formatTime } from '../../helpers';
12
+ import { getStatusColor, getAppName, getBundleIdentifier, formatTime, getSize } from '../../helpers';
16
13
  import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, } from '../NetworkIcons';
17
- const InspectorHeader = () => {
18
- const { modalHeightPercent, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, setSettingsPage, closeModal, detailTitle, } = useInspector();
14
+ const InspectorHeader = React.memo(() => {
15
+ const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, setSettingsPage, closeModal, detailTitle, activeTab, environment, } = useInspector();
16
+ const envConfig = useMemo(() => {
17
+ const rawEnv = (environment || (__DEV__ ? 'DEV' : 'PROD')).trim();
18
+ const clean = rawEnv.toUpperCase();
19
+ if (clean === 'DEV' || clean.includes('DEV') || clean === 'LOCAL') {
20
+ return {
21
+ label: rawEnv,
22
+ bg: 'rgba(16, 185, 129, 0.25)', // emerald
23
+ border: 'rgba(110, 231, 183, 0.55)',
24
+ text: '#A7F3D0',
25
+ };
26
+ }
27
+ if (clean === 'UAT' || clean === 'QA' || clean === 'TEST') {
28
+ return {
29
+ label: rawEnv,
30
+ bg: 'rgba(245, 158, 11, 0.28)', // amber
31
+ border: 'rgba(252, 211, 77, 0.6)',
32
+ text: '#FDE68A',
33
+ };
34
+ }
35
+ if (clean === 'PREPROD' || clean === 'STAGE' || clean === 'STAGING') {
36
+ return {
37
+ label: rawEnv,
38
+ bg: 'rgba(139, 92, 246, 0.28)', // purple
39
+ border: 'rgba(196, 181, 253, 0.6)',
40
+ text: '#DDD6FE',
41
+ };
42
+ }
43
+ // PROD / Live
44
+ return {
45
+ label: rawEnv,
46
+ bg: 'rgba(244, 63, 94, 0.25)', // rose
47
+ border: 'rgba(253, 164, 175, 0.55)',
48
+ text: '#FECDD3',
49
+ };
50
+ }, [environment]);
51
+ const isDetailView = (activeTab === 'apis' && selected != null) ||
52
+ (activeTab === 'analytics' && selectedEvent != null) ||
53
+ (activeTab === 'logs' && selectedLog != null) ||
54
+ (activeTab === 'redux' && (selectedReduxSlice != null || selectedReduxAction != null));
55
+ const isAnySelected = isDetailView;
19
56
  const headerTopPadding = Platform.OS === 'ios' && modalHeightPercent >= 95 ? 44 : 0;
20
57
  return (<LinearGradient colors={[AppColors.purple, AppColors.brandPurple]} style={styles.headerGradient}>
21
58
  <View style={{ paddingTop: headerTopPadding, width: '100%' }}>
@@ -26,11 +63,7 @@ const InspectorHeader = () => {
26
63
  flexDirection: 'row',
27
64
  alignItems: 'center',
28
65
  gap: 16,
29
- flex: selected == null &&
30
- selectedEvent == null &&
31
- selectedLog == null
32
- ? 5
33
- : 1,
66
+ flex: !isDetailView ? 5 : 1,
34
67
  },
35
68
  ]}>
36
69
  <TouchableScale onPress={() => {
@@ -38,6 +71,8 @@ const InspectorHeader = () => {
38
71
  setSelected(null);
39
72
  setSelectedEvent(null);
40
73
  setSelectedLog(null);
74
+ setSelectedReduxSlice(null);
75
+ setSelectedReduxAction(null);
41
76
  });
42
77
  }} hitSlop={15} style={[
43
78
  {
@@ -50,9 +85,7 @@ const InspectorHeader = () => {
50
85
  borderWidth: 1,
51
86
  borderColor: `${AppColors.white}4D`,
52
87
  },
53
- selected == null &&
54
- selectedEvent == null &&
55
- selectedLog == null && { display: 'none' },
88
+ !isAnySelected && { display: 'none' },
56
89
  ]}>
57
90
  {/* Soft outer glow to fake a blurred circle */}
58
91
  <View style={{
@@ -65,36 +98,32 @@ const InspectorHeader = () => {
65
98
  <WhiteBackNavigation />
66
99
  </TouchableScale>
67
100
 
68
- {selected == null &&
69
- selectedEvent == null &&
70
- selectedLog == null ? (<TouchableScale onPress={() => setShowHeaderInfo(prev => !prev)} style={{
101
+ {!isAnySelected ? (<TouchableScale onPress={() => setShowHeaderInfo(prev => !prev)} style={{
71
102
  flexDirection: 'row',
72
103
  alignItems: 'flex-start',
73
104
  gap: 10,
74
105
  flex: 1,
75
106
  }}>
76
- <View style={{
77
- width: 46,
78
- height: 46,
79
- borderRadius: 12,
80
- alignItems: 'center',
81
- justifyContent: 'center',
82
- backgroundColor: `${AppColors.white}21`,
83
- borderWidth: 1.5,
84
- borderColor: `${AppColors.white}40`,
85
- shadowColor: AppColors.black,
86
- shadowOpacity: 0.15,
87
- shadowRadius: 4,
88
- shadowOffset: { width: 0, height: 2 },
89
- flexShrink: 0,
90
- }}>
91
- <BrandSquareIcon size={42}/>
92
- </View>
107
+ <AppHeaderLogo size={46} customIcon={appIcon}/>
93
108
  <View style={{ gap: 2, flex: 1 }}>
94
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
109
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
95
110
  <Text style={[styles.headerTitle]} numberOfLines={1}>
96
111
  {getAppName()}
97
112
  </Text>
113
+ <View style={[
114
+ styles.envBadge,
115
+ {
116
+ backgroundColor: envConfig.bg,
117
+ borderColor: envConfig.border,
118
+ },
119
+ ]}>
120
+ <Text style={[
121
+ styles.envBadgeText,
122
+ { color: envConfig.text },
123
+ ]}>
124
+ {envConfig.label}
125
+ </Text>
126
+ </View>
98
127
  {updateAvailable && (<Pressable hitSlop={10} onPress={() => Alert.alert('Update Available', `react-native-inapp-inspector v${latestNpmVersion} is available on NPM (installed: v${LIB_VERSION}).`, [
99
128
  { text: 'Later', style: 'cancel' },
100
129
  {
@@ -233,10 +262,8 @@ const InspectorHeader = () => {
233
262
  </TouchableScale>) : null}
234
263
  </View>
235
264
 
236
- {(selected != null ||
237
- selectedEvent != null ||
238
- selectedLog != null) && (<View style={styles.headerCenter}>
239
- {selected != null ? (<View style={styles.headerDetailCenter}>
265
+ {isDetailView && (<View style={styles.headerCenter}>
266
+ {activeTab === 'apis' && selected != null ? (<View style={styles.headerDetailCenter}>
240
267
  <View style={styles.headerDetailRow}>
241
268
  <View style={[
242
269
  styles.headerMethodBadge,
@@ -297,7 +324,7 @@ const InspectorHeader = () => {
297
324
  </Text>
298
325
  </View>
299
326
  </View>
300
- </View>) : selectedEvent != null ? (<View style={styles.headerDetailCenter}>
327
+ </View>) : activeTab === 'analytics' && selectedEvent != null ? (<View style={styles.headerDetailCenter}>
301
328
  <View style={styles.headerDetailRow}>
302
329
  <View style={[
303
330
  styles.headerMethodBadge,
@@ -335,7 +362,7 @@ const InspectorHeader = () => {
335
362
  {selectedEvent.source}
336
363
  </Text>
337
364
  </View>
338
- </View>) : selectedLog != null ? (<View style={styles.headerDetailCenter}>
365
+ </View>) : activeTab === 'logs' && selectedLog != null ? (<View style={styles.headerDetailCenter}>
339
366
  <View style={styles.headerDetailRow}>
340
367
  <View style={[
341
368
  styles.headerMethodBadge,
@@ -368,27 +395,100 @@ const InspectorHeader = () => {
368
395
  borderRadius: 20,
369
396
  backgroundColor: `${AppColors.white}29`,
370
397
  }}>
371
- <ClockIcon color={AppColors.white} size={11}/>
372
- <Text style={styles.headerSubTitle}>
373
- {formatTime(selectedLog.timestamp)}
398
+ <ClockIcon color={AppColors.white} size={11}/>
399
+ <Text style={styles.headerSubTitle}>
400
+ {formatTime(selectedLog.timestamp)}
401
+ </Text>
402
+ </View>
403
+ </View>
404
+ </View>) : activeTab === 'redux' && selectedReduxSlice != null ? ((() => {
405
+ const sliceData = reduxState?.[selectedReduxSlice];
406
+ const keyCount = sliceData && typeof sliceData === 'object'
407
+ ? Object.keys(sliceData).length
408
+ : typeof sliceData !== 'undefined'
409
+ ? 1
410
+ : 0;
411
+ const sliceSize = getSize(sliceData);
412
+ const lastAction = reduxLastActionMap[selectedReduxSlice];
413
+ return (<View style={styles.headerDetailCenter}>
414
+ <View style={styles.headerDetailRow}>
415
+ <View style={[
416
+ styles.headerMethodBadge,
417
+ {
418
+ backgroundColor: `${AppColors.purple}4D`,
419
+ },
420
+ ]}>
421
+ <Text style={styles.headerMethodText}>
422
+ SLICE
423
+ </Text>
424
+ </View>
425
+ <Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
426
+ {selectedReduxSlice}
427
+ </Text>
428
+ </View>
429
+ <View style={styles.headerDetailSubRow}>
430
+ <View style={[
431
+ styles.headerStatusDot,
432
+ { backgroundColor: AppColors.liveGreen },
433
+ ]}/>
434
+ <Text style={styles.headerSubTitle}>
435
+ Live
436
+ </Text>
437
+ <Text style={[styles.headerSubTitle, { opacity: 0.6 }]}>•</Text>
438
+ <Text style={styles.headerSubTitle}>
439
+ {keyCount} keys
440
+ </Text>
441
+ <Text style={[styles.headerSubTitle, { opacity: 0.6 }]}>•</Text>
442
+ <Text style={styles.headerSubTitle}>
443
+ {sliceSize}
444
+ </Text>
445
+ {lastAction?.timestamp && (<>
446
+ <Text style={[styles.headerSubTitle, { opacity: 0.6 }]}>•</Text>
447
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3 }}>
448
+ <ClockIcon color={AppColors.white} size={10}/>
449
+ <Text style={styles.headerSubTitle}>
450
+ {lastAction.timestamp}
451
+ </Text>
452
+ </View>
453
+ </>)}
454
+ </View>
455
+ </View>);
456
+ })()) : activeTab === 'redux' && selectedReduxAction != null ? (<View style={styles.headerDetailCenter}>
457
+ <View style={styles.headerDetailRow}>
458
+ <View style={[
459
+ styles.headerMethodBadge,
460
+ {
461
+ backgroundColor: `${AppColors.brandPurple}4D`,
462
+ },
463
+ ]}>
464
+ <Text style={styles.headerMethodText}>
465
+ ACTION
374
466
  </Text>
375
467
  </View>
468
+ <Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
469
+ {selectedReduxAction.type}
470
+ </Text>
471
+ </View>
472
+ <View style={styles.headerDetailSubRow}>
473
+ <View style={[
474
+ styles.headerStatusDot,
475
+ { backgroundColor: AppColors.purple },
476
+ ]}/>
477
+ <Text style={styles.headerSubTitle}>
478
+ {selectedReduxAction.timestamp || 'Dispatched'}
479
+ </Text>
376
480
  </View>
377
481
  </View>) : null}
378
482
  </View>)}
379
483
 
380
484
  <View style={[
381
485
  styles.headerRight,
382
- selected == null &&
383
- selectedEvent == null &&
384
- selectedLog == null && {
486
+ !isAnySelected && {
385
487
  flexShrink: 0,
386
488
  minWidth: 116,
387
489
  },
388
490
  ]}>
389
- {selected == null &&
390
- selectedEvent == null &&
391
- selectedLog == null && (<TouchableScale onPress={() => {
491
+ {!isAnySelected && (<TouchableScale onPress={() => {
392
492
  Alert.alert('Clear Everything', 'This clears all tabs — APIs, Logs, Analytics and Redux timeline. Continue?', [
393
493
  { text: 'Cancel', style: 'cancel' },
394
494
  {
@@ -443,5 +543,5 @@ const InspectorHeader = () => {
443
543
  </View>
444
544
  </View>
445
545
  </LinearGradient>);
446
- };
546
+ });
447
547
  export default InspectorHeader;
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- declare const LogDetail: () => React.JSX.Element;
2
+ declare const LogDetail: React.MemoExoticComponent<() => React.JSX.Element>;
3
3
  export default LogDetail;
@@ -26,7 +26,7 @@ const getRelativeTime = (timestamp) => {
26
26
  return `${hours}h ago`;
27
27
  return `${days}d ago`;
28
28
  };
29
- const LogDetail = () => {
29
+ const LogDetail = React.memo(() => {
30
30
  const { t } = useTranslation();
31
31
  const { selectedLog } = useInspector();
32
32
  const [activeTab, setActiveTab] = useState('output');
@@ -909,7 +909,7 @@ const LogDetail = () => {
909
909
  </View>)}
910
910
  </ScrollView>
911
911
  </View>);
912
- };
912
+ });
913
913
  const detailStyles = StyleSheet.create({
914
914
  metaDot: {
915
915
  fontSize: 10,
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import { ActivityIndicator, Modal, Pressable, StatusBar, StyleSheet, Text, View, } from 'react-native';
3
3
  import { useInspector } from './InspectorContext';
4
4
  import ErrorBoundary from '../ErrorBoundary';
@@ -10,13 +10,31 @@ import NetworkDetail from './NetworkDetail';
10
10
  import LogDetail from './LogDetail';
11
11
  import ConsoleTab from './ConsoleTab';
12
12
  import AnalyticsTab from './AnalyticsTab';
13
+ import AnalyticsDetail from '../AnalyticsDetail';
13
14
  import ReduxTab from './ReduxTab';
15
+ import ReduxDetail from './ReduxDetail';
16
+ import BundleTab from './BundleTab';
14
17
  import SettingsPanel from './SettingsPanel';
15
18
  import styles from '../../styles';
16
19
  import { AppColors } from '../../styles/AppColors';
17
20
  import NavigationTracker from './NavigationTracker';
18
21
  const MainScreen = () => {
19
- const { visible, modalAnimationType, closeModal, modalHeightPercent, selected, selectedEvent, selectedLog, settingsPage, activeTab, isReady, isEnabled, hasNavigationContext, setNavState, } = useInspector();
22
+ const { visible, modalAnimationType, closeModal, modalHeightPercent, selected, selectedEvent, selectedLog, selectedReduxSlice, selectedReduxAction, settingsPage, activeTab, isReady, isEnabled, hasNavigationContext, setNavState, } = useInspector();
23
+ // Lazy-mount each tab on first visit and cache it in memory for 0ms instantaneous switching
24
+ const [mountedTabs, setMountedTabs] = useState({
25
+ [activeTab]: true,
26
+ });
27
+ useEffect(() => {
28
+ setMountedTabs(prev => {
29
+ if (prev[activeTab])
30
+ return prev;
31
+ return { ...prev, [activeTab]: true };
32
+ });
33
+ }, [activeTab]);
34
+ const isDetailActive = (activeTab === 'apis' && selected != null) ||
35
+ (activeTab === 'analytics' && selectedEvent != null) ||
36
+ (activeTab === 'logs' && selectedLog != null) ||
37
+ (activeTab === 'redux' && (selectedReduxSlice != null || selectedReduxAction != null));
20
38
  return (<>
21
39
  {isEnabled && <FabLauncher />}
22
40
  <Modal visible={visible} animationType={modalAnimationType} transparent>
@@ -32,12 +50,40 @@ const MainScreen = () => {
32
50
  <InspectorHeader />
33
51
 
34
52
  {/* ─── Horizontal Scrollable Tab Bar inside Content ─── */}
35
- {selected == null &&
36
- selectedEvent == null &&
37
- selectedLog == null &&
38
- settingsPage === null ? (<TabBar />) : null}
53
+ {!isDetailActive && settingsPage === null ? (<TabBar />) : null}
39
54
 
40
- {isReady ? (activeTab === 'analytics' ? (<AnalyticsTab />) : activeTab === 'apis' && selected == null ? (<NetworkTab />) : activeTab === 'logs' && selectedLog == null ? (<ConsoleTab />) : activeTab === 'logs' ? (<LogDetail />) : activeTab === 'redux' ? (<ReduxTab />) : (<NetworkDetail />)) : (<View style={styles.empty}>
55
+ {isReady ? (isDetailActive ? (activeTab === 'apis' && selected != null ? (<NetworkDetail />) : activeTab === 'analytics' && selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : activeTab === 'logs' && selectedLog != null ? (<LogDetail />) : activeTab === 'redux' ? (<ReduxDetail />) : null) : (<>
56
+ <View style={{
57
+ flex: 1,
58
+ display: activeTab === 'apis' ? 'flex' : 'none',
59
+ }}>
60
+ {mountedTabs.apis && <NetworkTab />}
61
+ </View>
62
+ <View style={{
63
+ flex: 1,
64
+ display: activeTab === 'logs' ? 'flex' : 'none',
65
+ }}>
66
+ {mountedTabs.logs && <ConsoleTab />}
67
+ </View>
68
+ <View style={{
69
+ flex: 1,
70
+ display: activeTab === 'analytics' ? 'flex' : 'none',
71
+ }}>
72
+ {mountedTabs.analytics && <AnalyticsTab />}
73
+ </View>
74
+ <View style={{
75
+ flex: 1,
76
+ display: activeTab === 'redux' ? 'flex' : 'none',
77
+ }}>
78
+ {mountedTabs.redux && <ReduxTab />}
79
+ </View>
80
+ <View style={{
81
+ flex: 1,
82
+ display: activeTab === 'bundle' ? 'flex' : 'none',
83
+ }}>
84
+ {mountedTabs.bundle && <BundleTab />}
85
+ </View>
86
+ </>)) : (<View style={styles.empty}>
41
87
  <ActivityIndicator size="large" color={AppColors.purple}/>
42
88
  <Text style={[styles.emptySub, { marginTop: 12 }]}>
43
89
  Loading logs...
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- declare const NetworkDetail: () => React.JSX.Element;
2
+ declare const NetworkDetail: React.MemoExoticComponent<() => React.JSX.Element>;
3
3
  export default NetworkDetail;
@@ -18,7 +18,7 @@ import { AppFonts } from '../../styles/AppFonts';
18
18
  import { METHOD_COLORS } from '../../constants';
19
19
  import { getStatusColor, getSize, formatDateTime, getFetchCommand, getCurlCommand, } from '../../helpers';
20
20
  import { FailIcon, GlobeIcon, StatusIcon, HeadersIcon, RequestIcon, ResponseIcon, DownloadIcon, ClearIcon, SizeIcon, } from '../NetworkIcons';
21
- const NetworkDetail = () => {
21
+ const NetworkDetail = React.memo(() => {
22
22
  const { t } = useTranslation();
23
23
  const { selected, detailDisplayUrl, apiDetailActiveTab, setApiDetailActiveTab, detailSearch, setDetailSearch, reqExpanded, setReqExpanded, resExpanded, setResExpanded, showReqDiff, setShowReqDiff, showResDiff, setShowResDiff, prevRequestData, prevResponseData, logRouteMapRef, } = useInspector();
24
24
  if (!selected)
@@ -363,5 +363,5 @@ const NetworkDetail = () => {
363
363
  </>)}
364
364
  </ScrollView>
365
365
  </View>);
366
- };
366
+ });
367
367
  export default NetworkDetail;
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- declare const NetworkTab: () => React.JSX.Element;
2
+ declare const NetworkTab: React.MemoExoticComponent<() => React.JSX.Element>;
3
3
  export default NetworkTab;
@@ -21,7 +21,7 @@ const STATUS_META = {
21
21
  '5xx': { color: AppColors.errorColor, Icon: CircleXIcon },
22
22
  Failed: { color: AppColors.errorColor, Icon: CircleXIcon },
23
23
  };
24
- const FilterChip = ({ label, color, Icon, badge, active, onPress, }) => {
24
+ const FilterChip = React.memo(({ label, color, Icon, badge, active, onPress, }) => {
25
25
  const iconColor = active ? color : AppColors.grayTextWeak;
26
26
  return (<TouchableScale style={styles.statusFilterWrap} onPress={onPress} hitSlop={10}>
27
27
  <View style={[
@@ -43,19 +43,20 @@ const FilterChip = ({ label, color, Icon, badge, active, onPress, }) => {
43
43
  {badge != null ? (<View style={[
44
44
  styles.methodBadgeMini,
45
45
  { backgroundColor: badge },
46
- ]}/>) : (Icon && (<View style={styles.filterChipIcon}>
47
- <Icon color={iconColor} size={13}/>
48
- </View>))}
49
- <Text style={[
46
+ ]}/>) : (Icon && (<Icon color={iconColor} size={13}/>))}
47
+ <Text numberOfLines={1} style={[
50
48
  styles.statusFilterText,
51
- active && { color, fontFamily: AppFonts.interBold },
49
+ active && {
50
+ color,
51
+ fontFamily: AppFonts.interBold,
52
+ },
52
53
  ]}>
53
54
  {label}
54
55
  </Text>
55
56
  </View>
56
57
  </TouchableScale>);
57
- };
58
- const NetworkTab = () => {
58
+ });
59
+ const NetworkTab = React.memo(() => {
59
60
  const { groupedData, search, setSearch, handleDelete, selectedLogs, sortOrder, setSortOrder, statusFilters, setStatusFilters, methodFilters, setMethodFilters, availableMethods, filteredLogs, logs, toggleSectionFilter, toggleSectionCollapse, minStart, totalRange, newLogIds, toggleSelect, setSelected, isNetworkPaused, setIsNetworkPaused, } = useInspector();
60
61
  const filtersAccordion = useAccordion(false, 300, 260);
61
62
  const apisListRef = useRef(null);
@@ -239,5 +240,5 @@ const NetworkTab = () => {
239
240
  </View>
240
241
  </TouchableScale>
241
242
  </View>);
242
- };
243
+ });
243
244
  export default NetworkTab;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const ReduxDetail: React.MemoExoticComponent<() => React.JSX.Element>;
3
+ export default ReduxDetail;