react-native-inapp-inspector 1.1.2 → 1.1.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 (37) hide show
  1. package/README.md +30 -0
  2. package/dist/commonjs/components/ConsoleLogCard.js +18 -0
  3. package/dist/commonjs/components/LogCard.js +19 -2
  4. package/dist/commonjs/components/NetworkIcons.d.ts +9 -2
  5. package/dist/commonjs/components/NetworkIcons.js +59 -3
  6. package/dist/commonjs/constants/version.d.ts +1 -1
  7. package/dist/commonjs/constants/version.js +1 -1
  8. package/dist/commonjs/customHooks/reduxLogger.d.ts +21 -7
  9. package/dist/commonjs/customHooks/reduxLogger.js +147 -48
  10. package/dist/commonjs/customHooks/webViewLogger.js +13 -8
  11. package/dist/commonjs/helpers/settingsStore.d.ts +28 -0
  12. package/dist/commonjs/helpers/settingsStore.js +92 -0
  13. package/dist/commonjs/index.d.ts +1 -1
  14. package/dist/commonjs/index.js +893 -185
  15. package/dist/commonjs/styles/index.d.ts +17 -1
  16. package/dist/commonjs/styles/index.js +25 -3
  17. package/dist/commonjs/types/index.d.ts +4 -0
  18. package/dist/esm/components/ConsoleLogCard.js +18 -0
  19. package/dist/esm/components/LogCard.js +19 -2
  20. package/dist/esm/components/NetworkIcons.d.ts +9 -2
  21. package/dist/esm/components/NetworkIcons.js +51 -2
  22. package/dist/esm/constants/version.d.ts +1 -1
  23. package/dist/esm/constants/version.js +1 -1
  24. package/dist/esm/customHooks/reduxLogger.d.ts +21 -7
  25. package/dist/esm/customHooks/reduxLogger.js +145 -47
  26. package/dist/esm/customHooks/webViewLogger.js +13 -8
  27. package/dist/esm/helpers/settingsStore.d.ts +28 -0
  28. package/dist/esm/helpers/settingsStore.js +84 -0
  29. package/dist/esm/index.d.ts +1 -1
  30. package/dist/esm/index.js +892 -187
  31. package/dist/esm/styles/index.d.ts +17 -1
  32. package/dist/esm/styles/index.js +25 -3
  33. package/dist/esm/types/index.d.ts +4 -0
  34. package/example/ios/example.xcodeproj/project.pbxproj +0 -8
  35. package/example/package-lock.json +75 -33
  36. package/example/package.json +1 -1
  37. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
2
- import { Alert, Animated, StyleSheet, FlatList, LayoutAnimation, Modal, PanResponder, Platform, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, UIManager, LogBox, } from 'react-native';
2
+ import { Alert, Animated, StyleSheet, FlatList, LayoutAnimation, Modal, PanResponder, Platform, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, UIManager, LogBox, SafeAreaView, } from 'react-native';
3
3
  import Svg, { Circle, Path } from 'react-native-svg';
4
4
  import LinearGradient from 'react-native-linear-gradient';
5
5
  import { useNavigationState, NavigationContext } from '@react-navigation/native';
@@ -23,8 +23,10 @@ import CodeSnippet from './components/CodeSnippet';
23
23
  import AnimatedEntrance from './components/AnimatedEntrance';
24
24
  // Helpers
25
25
  import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
26
+ // #5 — settings persistence
27
+ import { loadSettings, saveSettings, setCustomStorage, isPersistentStorageAvailable, clearPersistedSettings, } from './helpers/settingsStore';
26
28
  // Assets
27
- import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, BrandSquareIcon, HtmlIcon, CssIcon, JsIcon, ClockIcon, EyeIcon, CheckIcon, SettingsIcon, RequestIcon, ResponseIcon, HeadersIcon, StatusIcon, ChevronIcon, } from './components/NetworkIcons';
29
+ import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, BrandSquareIcon, HtmlIcon, CssIcon, JsIcon, ClockIcon, EyeIcon, CheckIcon, SettingsIcon, RequestIcon, ResponseIcon, HeadersIcon, StatusIcon, ChevronIcon, WipeIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, TrendingUpIcon, } from './components/NetworkIcons';
28
30
  import ErrorBoundary from './components/ErrorBoundary';
29
31
  // Stylesheet
30
32
  import { AppColors } from './styles/AppColors';
@@ -111,7 +113,9 @@ const NavigationTracker = ({ onStateChange }) => {
111
113
  const animateNextLayout = () => {
112
114
  LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
113
115
  };
114
- const NetworkInspector = ({ enabled = true, }) => {
116
+ const NetworkInspector = ({ enabled = true, storage, }) => {
117
+ // Set custom storage synchronously during render phase
118
+ setCustomStorage(storage || null);
115
119
  const [isDark, setIsDark] = useState(false);
116
120
  const [reduxState, setReduxState] = useState(null);
117
121
  // Action timeline + per-reducer last action are kept in component state so the
@@ -193,11 +197,11 @@ const NetworkInspector = ({ enabled = true, }) => {
193
197
  const visibleConsoleLogs = useMemo(() => {
194
198
  const filtered = consoleLogs.filter(log => {
195
199
  const type = log.type;
196
- if (type === 'info' && !showConsoleLevels.info)
200
+ if (type === 'info' && !showConsoleLevels?.info)
197
201
  return false;
198
- if (type === 'warn' && !showConsoleLevels.warn)
202
+ if (type === 'warn' && !showConsoleLevels?.warn)
199
203
  return false;
200
- if (type === 'error' && !showConsoleLevels.error)
204
+ if (type === 'error' && !showConsoleLevels?.error)
201
205
  return false;
202
206
  const message = log.message || '';
203
207
  const allPrefixes = [
@@ -227,9 +231,9 @@ const NetworkInspector = ({ enabled = true, }) => {
227
231
  insights: true,
228
232
  apis: true,
229
233
  logs: true,
230
- analytics: true,
231
- webview: true,
232
- redux: true,
234
+ analytics: false,
235
+ webview: false,
236
+ redux: false,
233
237
  });
234
238
  const [maxNetworkLogs, setMaxNetworkLogs] = useState(100);
235
239
  const [webViewCaptureCssJs, setWebViewCaptureCssJs] = useState(true);
@@ -237,6 +241,168 @@ const NetworkInspector = ({ enabled = true, }) => {
237
241
  const [reduxExpandDepth, setReduxExpandDepth] = useState(1);
238
242
  const [slowRequestThreshold, setSlowRequestThreshold] = useState(1000);
239
243
  const [insightsShowConsoleAlerts, setInsightsShowConsoleAlerts] = useState(true);
244
+ // #6 — tab the inspector opens on. Shown with a DEFAULT badge in Settings.
245
+ const [defaultTab, setDefaultTab] = useState('apis');
246
+ const [showDuplicateLogs, setShowDuplicateLogs] = useState(false);
247
+ const resetToDefaults = async () => {
248
+ await clearPersistedSettings();
249
+ setIsDark(false);
250
+ toggleGlobalTheme(false);
251
+ setModalHeightPercent(90);
252
+ setTabVisibility({
253
+ insights: true,
254
+ apis: true,
255
+ logs: true,
256
+ analytics: false,
257
+ webview: false,
258
+ redux: false,
259
+ });
260
+ setDefaultTab('apis');
261
+ setMaxNetworkLogs(100);
262
+ setMaxConsoleLogs(200);
263
+ setShowConsoleLevels({
264
+ info: true,
265
+ warn: true,
266
+ error: true,
267
+ });
268
+ setWebViewCaptureCssJs(true);
269
+ setReduxAutoRefreshState(true);
270
+ setReduxExpandDepth(1);
271
+ setSlowRequestThreshold(1000);
272
+ setInsightsShowConsoleAlerts(true);
273
+ setShowDuplicateLogs(false);
274
+ Alert.alert('Settings Reset', 'All settings have been reset to default values.');
275
+ };
276
+ // #5 — hydrate persisted settings once, then auto-save on any change.
277
+ const settingsHydratedRef = useRef(false);
278
+ useEffect(() => {
279
+ let cancelled = false;
280
+ loadSettings().then(saved => {
281
+ if (cancelled)
282
+ return;
283
+ if (saved.isDark != null) {
284
+ setIsDark(saved.isDark);
285
+ toggleGlobalTheme(saved.isDark);
286
+ }
287
+ if (saved.modalHeightPercent != null)
288
+ setModalHeightPercent(saved.modalHeightPercent);
289
+ if (saved.tabVisibility)
290
+ setTabVisibility(prev => ({
291
+ ...prev,
292
+ ...saved.tabVisibility,
293
+ apis: true, // APIs is always required
294
+ }));
295
+ if (saved.defaultTab)
296
+ setDefaultTab(saved.defaultTab);
297
+ if (saved.maxNetworkLogs != null)
298
+ setMaxNetworkLogs(saved.maxNetworkLogs);
299
+ if (saved.maxConsoleLogs != null)
300
+ setMaxConsoleLogs(saved.maxConsoleLogs);
301
+ if (saved.showConsoleLevels)
302
+ setShowConsoleLevels(saved.showConsoleLevels);
303
+ if (saved.webViewCaptureCssJs != null)
304
+ setWebViewCaptureCssJs(saved.webViewCaptureCssJs);
305
+ if (saved.reduxAutoRefresh != null)
306
+ setReduxAutoRefreshState(saved.reduxAutoRefresh);
307
+ if (saved.reduxExpandDepth != null)
308
+ setReduxExpandDepth(saved.reduxExpandDepth);
309
+ if (saved.slowRequestThreshold != null)
310
+ setSlowRequestThreshold(saved.slowRequestThreshold);
311
+ if (saved.insightsShowConsoleAlerts != null)
312
+ setInsightsShowConsoleAlerts(saved.insightsShowConsoleAlerts);
313
+ if (saved.showDuplicateLogs != null)
314
+ setShowDuplicateLogs(saved.showDuplicateLogs);
315
+ if (saved.defaultTab) {
316
+ const dt = saved.defaultTab;
317
+ const vis = {
318
+ ...{
319
+ insights: true,
320
+ apis: true,
321
+ logs: true,
322
+ analytics: false,
323
+ webview: false,
324
+ redux: false,
325
+ },
326
+ ...(saved.tabVisibility || {}),
327
+ apis: true,
328
+ };
329
+ setActiveTab(vis[dt] ? dt : 'apis');
330
+ }
331
+ settingsHydratedRef.current = true;
332
+ });
333
+ return () => {
334
+ cancelled = true;
335
+ };
336
+ }, []);
337
+ useEffect(() => {
338
+ if (!settingsHydratedRef.current)
339
+ return;
340
+ saveSettings({
341
+ isDark,
342
+ modalHeightPercent,
343
+ tabVisibility,
344
+ defaultTab,
345
+ maxNetworkLogs,
346
+ maxConsoleLogs,
347
+ showConsoleLevels,
348
+ webViewCaptureCssJs,
349
+ reduxAutoRefresh,
350
+ reduxExpandDepth,
351
+ slowRequestThreshold,
352
+ insightsShowConsoleAlerts,
353
+ showDuplicateLogs,
354
+ });
355
+ }, [
356
+ isDark,
357
+ modalHeightPercent,
358
+ tabVisibility,
359
+ defaultTab,
360
+ maxNetworkLogs,
361
+ maxConsoleLogs,
362
+ showConsoleLevels,
363
+ webViewCaptureCssJs,
364
+ reduxAutoRefresh,
365
+ reduxExpandDepth,
366
+ slowRequestThreshold,
367
+ insightsShowConsoleAlerts,
368
+ showDuplicateLogs,
369
+ ]);
370
+ // #1 — check NPM for a newer published version; surfaces an animated dot
371
+ // in the header next to the npm chip when an update is available.
372
+ const [latestNpmVersion, setLatestNpmVersion] = useState(null);
373
+ const updateAvailable = useMemo(() => {
374
+ if (!latestNpmVersion)
375
+ return false;
376
+ const parse = (v) => v
377
+ .replace(/^v/, '')
378
+ .split('.')
379
+ .map(n => parseInt(n, 10) || 0);
380
+ const cur = parse(LIB_VERSION);
381
+ const latest = parse(latestNpmVersion);
382
+ for (let i = 0; i < 3; i++) {
383
+ if ((latest[i] || 0) > (cur[i] || 0))
384
+ return true;
385
+ if ((latest[i] || 0) < (cur[i] || 0))
386
+ return false;
387
+ }
388
+ return false;
389
+ }, [latestNpmVersion]);
390
+ useEffect(() => {
391
+ let cancelled = false;
392
+ fetch('https://registry.npmjs.org/react-native-inapp-inspector/latest')
393
+ .then(res => (res.ok ? res.json() : null))
394
+ .then(data => {
395
+ if (!cancelled && data && typeof data.version === 'string') {
396
+ setLatestNpmVersion(data.version);
397
+ }
398
+ })
399
+ .catch(() => {
400
+ // Offline / blocked — silently skip the update check.
401
+ });
402
+ return () => {
403
+ cancelled = true;
404
+ };
405
+ }, []);
240
406
  useEffect(() => {
241
407
  setReduxAutoRefresh(reduxAutoRefresh);
242
408
  }, [reduxAutoRefresh]);
@@ -250,6 +416,10 @@ const NetworkInspector = ({ enabled = true, }) => {
250
416
  animateNextLayout();
251
417
  setActiveTab('apis');
252
418
  }
419
+ // #6 — a hidden module can't be the default landing tab.
420
+ if (!nextVal && defaultTab === key) {
421
+ setDefaultTab('apis');
422
+ }
253
423
  return newVisibility;
254
424
  });
255
425
  };
@@ -331,6 +501,8 @@ const NetworkInspector = ({ enabled = true, }) => {
331
501
  const badgeAnim = useRef(new Animated.Value(1)).current;
332
502
  const activePulseAnim = useRef(new Animated.Value(0.4)).current;
333
503
  const unreadPulseAnim = useRef(new Animated.Value(1)).current;
504
+ // #4 — diagonal light streak sweeping across the floating launcher
505
+ const fabShineAnim = useRef(new Animated.Value(0)).current;
334
506
  // #11 — header "clear all" icon spin/scale animation
335
507
  const clearAnim = useRef(new Animated.Value(0)).current;
336
508
  // #4 — draggable floating launcher (drag anywhere on screen)
@@ -373,9 +545,9 @@ const NetworkInspector = ({ enabled = true, }) => {
373
545
  fabDraggedRef.current = false;
374
546
  },
375
547
  })).current;
376
- // #10 — scroll-to-top affordance for the main APIs list
548
+ // #2 — scroll-to-top button for the main APIs list, always visible at the
549
+ // bottom right of the list.
377
550
  const apisListRef = useRef(null);
378
- const [showScrollTop, setShowScrollTop] = useState(false);
379
551
  const runClearAllWithAnimation = useCallback(() => {
380
552
  Animated.sequence([
381
553
  Animated.timing(clearAnim, {
@@ -412,6 +584,24 @@ const NetworkInspector = ({ enabled = true, }) => {
412
584
  loop.start();
413
585
  return () => loop.stop();
414
586
  }, [pulseAnim]);
587
+ // #4 — sweep the shine streak across the launcher, pause, repeat.
588
+ useEffect(() => {
589
+ const loop = Animated.loop(Animated.sequence([
590
+ Animated.timing(fabShineAnim, {
591
+ toValue: 1,
592
+ duration: 1100,
593
+ useNativeDriver: true,
594
+ }),
595
+ Animated.delay(1600),
596
+ Animated.timing(fabShineAnim, {
597
+ toValue: 0,
598
+ duration: 0,
599
+ useNativeDriver: true,
600
+ }),
601
+ ]));
602
+ loop.start();
603
+ return () => loop.stop();
604
+ }, [fabShineAnim]);
415
605
  useEffect(() => {
416
606
  const loop = Animated.loop(Animated.sequence([
417
607
  Animated.timing(activePulseAnim, {
@@ -455,6 +645,16 @@ const NetworkInspector = ({ enabled = true, }) => {
455
645
  }).start();
456
646
  }
457
647
  }, [newLogIds]);
648
+ // #6 — every time the inspector is opened, land on the chosen default tab.
649
+ useEffect(() => {
650
+ if (visible) {
651
+ const target = defaultTab === 'apis' || tabVisibility?.[defaultTab]
652
+ ? defaultTab
653
+ : 'apis';
654
+ setActiveTab(target);
655
+ }
656
+ // eslint-disable-next-line react-hooks/exhaustive-deps
657
+ }, [visible]);
458
658
  useEffect(() => {
459
659
  if (visible) {
460
660
  const task = InteractionManager.runAfterInteractions(() => {
@@ -608,8 +808,38 @@ const NetworkInspector = ({ enabled = true, }) => {
608
808
  if (sortOrder === 'oldest') {
609
809
  result = [...result].reverse();
610
810
  }
811
+ // #9 — collapse consecutive identical requests (same method + url +
812
+ // status) into one row carrying a ×N counter, unless the user opted in
813
+ // to seeing every duplicate via Settings → "Show Duplicate Logs".
814
+ if (!showDuplicateLogs) {
815
+ const collapsed = [];
816
+ for (const log of result) {
817
+ const last = collapsed[collapsed.length - 1];
818
+ if (last &&
819
+ last.method === log.method &&
820
+ last.url === log.url &&
821
+ last.status === log.status) {
822
+ collapsed[collapsed.length - 1] = {
823
+ ...last,
824
+ duplicateCount: (last.duplicateCount || 1) + 1,
825
+ };
826
+ }
827
+ else {
828
+ collapsed.push({ ...log, duplicateCount: 1 });
829
+ }
830
+ }
831
+ result = collapsed;
832
+ }
611
833
  return result.slice(0, maxNetworkLogs);
612
- }, [logs, search, statusFilters, methodFilters, sortOrder, maxNetworkLogs]);
834
+ }, [
835
+ logs,
836
+ search,
837
+ statusFilters,
838
+ methodFilters,
839
+ sortOrder,
840
+ maxNetworkLogs,
841
+ showDuplicateLogs,
842
+ ]);
613
843
  const availableMethods = useMemo(() => {
614
844
  const methods = new Set();
615
845
  logs.forEach(log => {
@@ -819,8 +1049,35 @@ const NetworkInspector = ({ enabled = true, }) => {
819
1049
  result = [...result].sort((a, b) => logSortOrder === 'newest'
820
1050
  ? b.timestamp - a.timestamp
821
1051
  : a.timestamp - b.timestamp);
1052
+ // #9 — collapse consecutive identical messages into one row with a ×N
1053
+ // counter unless duplicates are explicitly enabled in Settings.
1054
+ if (!showDuplicateLogs) {
1055
+ const collapsed = [];
1056
+ for (const log of result) {
1057
+ const last = collapsed[collapsed.length - 1];
1058
+ if (last &&
1059
+ last.type === log.type &&
1060
+ last.sourceMethod === log.sourceMethod &&
1061
+ last.message === log.message) {
1062
+ collapsed[collapsed.length - 1] = {
1063
+ ...last,
1064
+ duplicateCount: (last.duplicateCount || 1) + 1,
1065
+ };
1066
+ }
1067
+ else {
1068
+ collapsed.push({ ...log, duplicateCount: 1 });
1069
+ }
1070
+ }
1071
+ result = collapsed;
1072
+ }
822
1073
  return result;
823
- }, [visibleConsoleLogs, logFilters, logSearch, logSortOrder]);
1074
+ }, [
1075
+ visibleConsoleLogs,
1076
+ logFilters,
1077
+ logSearch,
1078
+ logSortOrder,
1079
+ showDuplicateLogs,
1080
+ ]);
824
1081
  const filteredWebViewLogs = useMemo(() => {
825
1082
  let result = webViewLogs;
826
1083
  if (webViewSearch) {
@@ -969,7 +1226,11 @@ const NetworkInspector = ({ enabled = true, }) => {
969
1226
  else {
970
1227
  Alert.alert('Clear Logs', 'Are you sure you want to clear all network logs?', [
971
1228
  { text: 'Cancel', style: 'cancel' },
972
- { text: 'Clear All', onPress: clearNetworkOnly, style: 'destructive' },
1229
+ {
1230
+ text: 'Clear All',
1231
+ onPress: clearNetworkOnly,
1232
+ style: 'destructive',
1233
+ },
973
1234
  ]);
974
1235
  }
975
1236
  }
@@ -1057,7 +1318,8 @@ const NetworkInspector = ({ enabled = true, }) => {
1057
1318
  return (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
1058
1319
  {/* Settings Header with back button */}
1059
1320
  <LinearGradient colors={[AppColors.purple, '#6B4EFF']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={styles.headerGradient}>
1060
- <View style={[styles.header, { paddingHorizontal: 16, gap: 12 }]}>
1321
+ <SafeAreaView style={{ width: '100%' }}>
1322
+ <View style={[styles.header, { paddingHorizontal: 16, gap: 12 }]}>
1061
1323
  <TouchableScale onPress={() => {
1062
1324
  animateNextLayout();
1063
1325
  setSettingsPage(null);
@@ -1105,7 +1367,8 @@ const NetworkInspector = ({ enabled = true, }) => {
1105
1367
  </Text>
1106
1368
  </View>
1107
1369
  </View>
1108
- </LinearGradient>
1370
+ </SafeAreaView>
1371
+ </LinearGradient>
1109
1372
 
1110
1373
  <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1111
1374
  {/* Tab list */}
@@ -1150,7 +1413,7 @@ const NetworkInspector = ({ enabled = true, }) => {
1150
1413
  </View>
1151
1414
 
1152
1415
  {settingsTabs.map((tab, idx) => {
1153
- const isVisible = tab.key === 'apis' || tabVisibility[tab.key];
1416
+ const isVisible = tab.key === 'apis' || tabVisibility?.[tab.key];
1154
1417
  const isLast = idx === settingsTabs.length - 1;
1155
1418
  const isLocked = tab.key === 'apis';
1156
1419
  return (<View key={tab.key} style={{
@@ -1213,7 +1476,8 @@ const NetworkInspector = ({ enabled = true, }) => {
1213
1476
  }}>
1214
1477
  {tab.label}
1215
1478
  </Text>
1216
- {isLocked && (<View style={{
1479
+ {/* #6 badge marks the configured default tab */}
1480
+ {tab.key === defaultTab && (<View style={{
1217
1481
  flexDirection: 'row',
1218
1482
  alignItems: 'center',
1219
1483
  backgroundColor: 'rgba(104,75,155,0.08)',
@@ -1481,73 +1745,316 @@ const NetworkInspector = ({ enabled = true, }) => {
1481
1745
  })}
1482
1746
  </View>
1483
1747
  </View>
1484
- </View>
1485
- </View>
1486
- </ScrollView>
1487
- </View>);
1488
- }
1489
- const goBackToMain = () => {
1490
- animateNextLayout();
1491
- setSettingsPage('main');
1492
- };
1493
- const renderSubHeader = (title, icon, rightInfo) => (<View style={{
1494
- flexDirection: 'row',
1495
- alignItems: 'center',
1496
- gap: 12,
1497
- paddingBottom: 16,
1498
- borderBottomWidth: 1,
1499
- borderBottomColor: AppColors.dividerColor,
1500
- marginBottom: 16,
1501
- }}>
1502
- <TouchableScale onPress={goBackToMain} style={{
1503
- width: 36,
1504
- height: 36,
1505
- borderRadius: 10,
1506
- backgroundColor: AppColors.purpleShade50,
1507
- borderWidth: 1,
1508
- borderColor: 'rgba(104,75,155,0.2)',
1509
- alignItems: 'center',
1510
- justifyContent: 'center',
1511
- }}>
1512
- <WhiteBackNavigation color={AppColors.purple} size={16}/>
1513
- </TouchableScale>
1514
- {icon && (<View style={{
1515
- width: 36,
1516
- height: 36,
1517
- borderRadius: 10,
1748
+
1749
+ {/* Divider */}
1750
+ <View style={{
1751
+ height: 1,
1752
+ backgroundColor: AppColors.dividerColor,
1753
+ }}/>
1754
+
1755
+ {/* #6 — Default Tab */}
1756
+ <View style={{
1757
+ paddingVertical: 12,
1758
+ paddingHorizontal: 14,
1759
+ }}>
1760
+ <View style={{
1761
+ flexDirection: 'row',
1762
+ alignItems: 'center',
1763
+ gap: 8,
1764
+ }}>
1765
+ <View style={{
1766
+ width: 20,
1767
+ height: 20,
1768
+ borderRadius: 6,
1518
1769
  backgroundColor: AppColors.purpleShade50,
1519
1770
  borderWidth: 1,
1520
1771
  borderColor: 'rgba(104,75,155,0.2)',
1521
1772
  alignItems: 'center',
1522
1773
  justifyContent: 'center',
1523
1774
  }}>
1524
- {icon}
1525
- </View>)}
1526
- <Text style={{
1527
- fontFamily: AppFonts.interBold,
1528
- fontSize: 18,
1529
- color: AppColors.primaryBlack,
1530
- flex: 1,
1531
- }}>
1532
- {title}
1533
- </Text>
1534
- {rightInfo ? (<View style={{
1535
- backgroundColor: 'rgba(104,75,155,0.08)',
1775
+ <LayersIcon color={AppColors.purple} size={11}/>
1776
+ </View>
1777
+ <View style={{ flex: 1 }}>
1778
+ <Text style={{
1779
+ fontFamily: AppFonts.interBold,
1780
+ fontSize: 13,
1781
+ color: AppColors.primaryBlack,
1782
+ }}>
1783
+ Default Tab
1784
+ </Text>
1785
+ <Text style={{
1786
+ fontFamily: AppFonts.interRegular,
1787
+ fontSize: 11,
1788
+ color: AppColors.grayText,
1789
+ marginTop: 1,
1790
+ }}>
1791
+ Tab shown when the inspector opens
1792
+ </Text>
1793
+ </View>
1794
+ </View>
1795
+
1796
+ {/* Grid of Default Tab Cards */}
1797
+ <View style={{
1798
+ flexDirection: 'row',
1799
+ flexWrap: 'wrap',
1800
+ gap: 8,
1801
+ marginTop: 12,
1802
+ }}>
1803
+ {settingsTabs
1804
+ .filter(tab => tab.key === 'apis' || tabVisibility?.[tab.key])
1805
+ .map(tab => {
1806
+ const isActive = defaultTab === tab.key;
1807
+ return (<TouchableScale key={tab.key} onPress={() => setDefaultTab(tab.key)} style={{
1808
+ flexDirection: 'row',
1809
+ alignItems: 'center',
1810
+ gap: 8,
1811
+ paddingVertical: 10,
1812
+ paddingHorizontal: 12,
1813
+ borderRadius: 10,
1814
+ borderWidth: 1.5,
1815
+ borderColor: isActive ? AppColors.purple : AppColors.grayBorderSecondary,
1816
+ backgroundColor: isActive ? 'rgba(104,75,155,0.06)' : AppColors.primaryLight,
1817
+ minWidth: '47%',
1818
+ flex: 1,
1819
+ }}>
1820
+ <View style={{
1821
+ width: 22,
1822
+ height: 22,
1823
+ borderRadius: 6,
1824
+ backgroundColor: isActive ? AppColors.purple : AppColors.purpleShade50,
1825
+ alignItems: 'center',
1826
+ justifyContent: 'center',
1827
+ }}>
1828
+ {tab.icon === 'insights' && (<InsightsIcon color={isActive ? '#FFFFFF' : AppColors.purple} size={11}/>)}
1829
+ {tab.icon === 'apis' && (<SignalIcon color={isActive ? '#FFFFFF' : AppColors.purple} size={11}/>)}
1830
+ {tab.icon === 'logs' && (<TerminalIcon color={isActive ? '#FFFFFF' : AppColors.purple} size={11}/>)}
1831
+ {tab.icon === 'analytics' && (<AnalyticsIcon color={isActive ? '#FFFFFF' : AppColors.purple} size={11}/>)}
1832
+ {tab.icon === 'webview' && (<GlobeIcon color={isActive ? '#FFFFFF' : AppColors.purple} size={11}/>)}
1833
+ {tab.icon === 'redux' && (<TerminalIcon color={isActive ? '#FFFFFF' : AppColors.purple} size={11}/>)}
1834
+ </View>
1835
+ <Text style={{
1836
+ fontFamily: AppFonts.interBold,
1837
+ fontSize: 13,
1838
+ color: isActive ? AppColors.purple : AppColors.primaryBlack,
1839
+ flex: 1,
1840
+ }}>
1841
+ {tab.label}
1842
+ </Text>
1843
+ {isActive && (<View style={{
1844
+ width: 14,
1845
+ height: 14,
1846
+ borderRadius: 7,
1847
+ backgroundColor: AppColors.purple,
1848
+ alignItems: 'center',
1849
+ justifyContent: 'center',
1850
+ }}>
1851
+ <CheckIcon size={8} color="#FFFFFF"/>
1852
+ </View>)}
1853
+ </TouchableScale>);
1854
+ })}
1855
+ </View>
1856
+ </View>
1857
+
1858
+ {/* Divider */}
1859
+ <View style={{
1860
+ height: 1,
1861
+ backgroundColor: AppColors.dividerColor,
1862
+ }}/>
1863
+
1864
+ {/* #9 — Show Duplicate Logs */}
1865
+ <View style={{
1866
+ flexDirection: 'row',
1867
+ alignItems: 'center',
1868
+ paddingVertical: 12,
1869
+ paddingHorizontal: 14,
1870
+ gap: 12,
1871
+ }}>
1872
+ <View style={{
1873
+ flex: 1,
1874
+ flexDirection: 'row',
1875
+ alignItems: 'center',
1876
+ gap: 8,
1877
+ }}>
1878
+ <View style={{
1879
+ width: 20,
1880
+ height: 20,
1881
+ borderRadius: 6,
1882
+ backgroundColor: AppColors.purpleShade50,
1883
+ borderWidth: 1,
1884
+ borderColor: 'rgba(104,75,155,0.2)',
1885
+ alignItems: 'center',
1886
+ justifyContent: 'center',
1887
+ }}>
1888
+ <EyeIcon color={AppColors.purple} size={11}/>
1889
+ </View>
1890
+ <View style={{ flex: 1 }}>
1891
+ <Text style={{
1892
+ fontFamily: AppFonts.interBold,
1893
+ fontSize: 13,
1894
+ color: AppColors.primaryBlack,
1895
+ }}>
1896
+ Show Duplicate Logs
1897
+ </Text>
1898
+ <Text style={{
1899
+ fontFamily: AppFonts.interRegular,
1900
+ fontSize: 11,
1901
+ color: AppColors.grayText,
1902
+ marginTop: 1,
1903
+ }}>
1904
+ Off: repeated identical entries collapse into one row
1905
+ with a ×N count
1906
+ </Text>
1907
+ </View>
1908
+ </View>
1909
+
1910
+ {/* Toggle Switch */}
1911
+ <TouchableScale onPress={() => setShowDuplicateLogs(prev => !prev)} style={{
1912
+ width: 38,
1913
+ height: 22,
1914
+ borderRadius: 11,
1915
+ backgroundColor: showDuplicateLogs
1916
+ ? AppColors.purple
1917
+ : AppColors.grayBorderSecondary,
1918
+ padding: 2,
1919
+ justifyContent: 'center',
1920
+ alignItems: showDuplicateLogs ? 'flex-end' : 'flex-start',
1921
+ }}>
1922
+ <View style={{
1923
+ width: 18,
1924
+ height: 18,
1925
+ borderRadius: 9,
1926
+ backgroundColor: '#FFFFFF',
1927
+ shadowColor: '#000',
1928
+ shadowOpacity: 0.15,
1929
+ shadowRadius: 1.5,
1930
+ shadowOffset: { width: 0, height: 1 },
1931
+ }}/>
1932
+ </TouchableScale>
1933
+ </View>
1934
+
1935
+ {/* Divider */}
1936
+ <View style={{
1937
+ height: 1,
1938
+ backgroundColor: AppColors.dividerColor,
1939
+ }}/>
1940
+
1941
+ {/* Reset Settings */}
1942
+ <View style={{
1943
+ flexDirection: 'row',
1944
+ alignItems: 'center',
1945
+ paddingVertical: 12,
1946
+ paddingHorizontal: 14,
1947
+ gap: 12,
1948
+ }}>
1949
+ <View style={{
1950
+ flex: 1,
1951
+ flexDirection: 'row',
1952
+ alignItems: 'center',
1953
+ gap: 8,
1954
+ }}>
1955
+ <View style={{
1956
+ width: 20,
1957
+ height: 20,
1958
+ borderRadius: 6,
1959
+ backgroundColor: 'rgba(239,68,68,0.08)',
1960
+ borderWidth: 1,
1961
+ borderColor: 'rgba(239,68,68,0.2)',
1962
+ alignItems: 'center',
1963
+ justifyContent: 'center',
1964
+ }}>
1965
+ <TrashIcon color={AppColors.errorColor} size={11}/>
1966
+ </View>
1967
+ <View style={{ flex: 1 }}>
1968
+ <Text style={{
1969
+ fontFamily: AppFonts.interBold,
1970
+ fontSize: 13,
1971
+ color: AppColors.primaryBlack,
1972
+ }}>
1973
+ Reset Settings
1974
+ </Text>
1975
+ <Text style={{
1976
+ fontFamily: AppFonts.interRegular,
1977
+ fontSize: 11,
1978
+ color: AppColors.grayText,
1979
+ marginTop: 1,
1980
+ }}>
1981
+ Wipe custom configurations and load package defaults
1982
+ </Text>
1983
+ </View>
1984
+ </View>
1985
+
1986
+ <TouchableScale onPress={resetToDefaults} style={{
1536
1987
  paddingHorizontal: 10,
1537
1988
  paddingVertical: 5,
1538
- borderRadius: 8,
1989
+ borderRadius: 7,
1990
+ backgroundColor: 'rgba(255,46,87,0.08)',
1539
1991
  borderWidth: 1,
1540
- borderColor: 'rgba(104,75,155,0.15)',
1992
+ borderColor: 'rgba(255,46,87,0.2)',
1541
1993
  }}>
1542
- <Text style={{
1994
+ <Text style={{
1543
1995
  fontFamily: AppFonts.interBold,
1544
1996
  fontSize: 11,
1545
- color: AppColors.purple,
1997
+ color: AppColors.errorColor,
1546
1998
  }}>
1547
- {rightInfo}
1548
- </Text>
1549
- </View>) : null}
1550
- </View>);
1999
+ Reset
2000
+ </Text>
2001
+ </TouchableScale>
2002
+ </View>
2003
+ </View>
2004
+ </View>
2005
+
2006
+ {/* Storage Status */}
2007
+ <View style={{
2008
+ backgroundColor: isPersistentStorageAvailable()
2009
+ ? 'rgba(74,222,128,0.08)'
2010
+ : 'rgba(234,179,8,0.08)',
2011
+ borderRadius: 12,
2012
+ borderWidth: 1,
2013
+ borderColor: isPersistentStorageAvailable()
2014
+ ? 'rgba(74,222,128,0.2)'
2015
+ : 'rgba(234,179,8,0.2)',
2016
+ padding: 12,
2017
+ marginTop: 16,
2018
+ flexDirection: 'row',
2019
+ alignItems: 'center',
2020
+ gap: 10,
2021
+ }}>
2022
+ <View style={{
2023
+ width: 8,
2024
+ height: 8,
2025
+ borderRadius: 4,
2026
+ backgroundColor: isPersistentStorageAvailable() ? '#22C55E' : '#EAB308',
2027
+ }}/>
2028
+ <View style={{ flex: 1 }}>
2029
+ <Text style={{
2030
+ fontFamily: AppFonts.interBold,
2031
+ fontSize: 12,
2032
+ color: isPersistentStorageAvailable() ? '#15803D' : '#854D0E',
2033
+ }}>
2034
+ {isPersistentStorageAvailable()
2035
+ ? `Storage: Persistent (${storage ? 'Custom' : 'iOS Settings'})`
2036
+ : 'Storage: Temporary (In-Memory)'}
2037
+ </Text>
2038
+ <Text style={{
2039
+ fontFamily: AppFonts.interRegular,
2040
+ fontSize: 10.5,
2041
+ color: isPersistentStorageAvailable() ? '#166534' : '#854D0E',
2042
+ marginTop: 2,
2043
+ opacity: 0.8,
2044
+ }}>
2045
+ {isPersistentStorageAvailable()
2046
+ ? 'Your settings are saved across app restarts.'
2047
+ : 'Settings reset when closed. To persist settings, pass a storage object to <NetworkInspector storage={...} />.'}
2048
+ </Text>
2049
+ </View>
2050
+ </View>
2051
+ </ScrollView>
2052
+ </View>);
2053
+ }
2054
+ const goBackToMain = () => {
2055
+ animateNextLayout();
2056
+ setSettingsPage('main');
2057
+ };
1551
2058
  // Helper: settings row with icon + label + optional description
1552
2059
  const renderSettingRow = (opts) => (<View style={{
1553
2060
  paddingVertical: 12,
@@ -1627,9 +2134,15 @@ const NetworkInspector = ({ enabled = true, }) => {
1627
2134
  })}
1628
2135
  </View>)}
1629
2136
  </View>);
2137
+ let content = null;
2138
+ let title = '';
2139
+ let icon = null;
2140
+ let rightInfo = '';
1630
2141
  if (settingsPage === 'apis') {
1631
- return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1632
- {renderSubHeader('APIs Settings', <SignalIcon color={AppColors.purple} size={16}/>, `Total: ${logs.length}`)}
2142
+ title = 'APIs Settings';
2143
+ icon = <SignalIcon color="#FFFFFF" size={16}/>;
2144
+ rightInfo = `Total: ${logs.length}`;
2145
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
1633
2146
  <View style={{
1634
2147
  backgroundColor: AppColors.primaryLight,
1635
2148
  padding: 16,
@@ -1689,9 +2202,11 @@ const NetworkInspector = ({ enabled = true, }) => {
1689
2202
  </View>
1690
2203
  </ScrollView>);
1691
2204
  }
1692
- if (settingsPage === 'logs') {
1693
- return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1694
- {renderSubHeader('Logs Settings', <TerminalIcon color={AppColors.purple} size={16}/>, `Total: ${consoleLogs.length}`)}
2205
+ else if (settingsPage === 'logs') {
2206
+ title = 'Logs Settings';
2207
+ icon = <TerminalIcon color="#FFFFFF" size={16}/>;
2208
+ rightInfo = `Total: ${consoleLogs.length}`;
2209
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
1695
2210
  <View style={{
1696
2211
  backgroundColor: AppColors.primaryLight,
1697
2212
  padding: 16,
@@ -1720,7 +2235,7 @@ const NetworkInspector = ({ enabled = true, }) => {
1720
2235
  Log Levels
1721
2236
  </Text>
1722
2237
  {['info', 'warn', 'error'].map((level, li) => {
1723
- const isLvlActive = showConsoleLevels[level];
2238
+ const isLvlActive = showConsoleLevels?.[level];
1724
2239
  const levelColor = level === 'error'
1725
2240
  ? AppColors.errorColor
1726
2241
  : level === 'warn'
@@ -1801,9 +2316,11 @@ const NetworkInspector = ({ enabled = true, }) => {
1801
2316
  </View>
1802
2317
  </ScrollView>);
1803
2318
  }
1804
- if (settingsPage === 'analytics') {
1805
- return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1806
- {renderSubHeader('Analytics Settings', <AnalyticsIcon color={AppColors.purple} size={16}/>, `Events: ${analyticsEvents.length}`)}
2319
+ else if (settingsPage === 'analytics') {
2320
+ title = 'Analytics Settings';
2321
+ icon = <AnalyticsIcon color="#FFFFFF" size={16}/>;
2322
+ rightInfo = `Events: ${analyticsEvents.length}`;
2323
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
1807
2324
  <View style={{
1808
2325
  backgroundColor: AppColors.primaryLight,
1809
2326
  padding: 16,
@@ -1857,9 +2374,11 @@ const NetworkInspector = ({ enabled = true, }) => {
1857
2374
  </View>
1858
2375
  </ScrollView>);
1859
2376
  }
1860
- if (settingsPage === 'webview') {
1861
- return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1862
- {renderSubHeader('WebView Settings', <GlobeIcon color={AppColors.purple} size={16}/>, `History: ${webViewNavHistory.length}`)}
2377
+ else if (settingsPage === 'webview') {
2378
+ title = 'WebView Settings';
2379
+ icon = <GlobeIcon color="#FFFFFF" size={16}/>;
2380
+ rightInfo = `History: ${webViewNavHistory.length}`;
2381
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
1863
2382
  <View style={{
1864
2383
  backgroundColor: AppColors.primaryLight,
1865
2384
  padding: 16,
@@ -1929,9 +2448,11 @@ const NetworkInspector = ({ enabled = true, }) => {
1929
2448
  </View>
1930
2449
  </ScrollView>);
1931
2450
  }
1932
- if (settingsPage === 'redux') {
1933
- return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1934
- {renderSubHeader('Redux Settings', <TerminalIcon color={AppColors.purple} size={16}/>, `Reducers: ${Object.keys(reduxState || {}).length}`)}
2451
+ else if (settingsPage === 'redux') {
2452
+ title = 'Redux Settings';
2453
+ icon = <TerminalIcon color="#FFFFFF" size={16}/>;
2454
+ rightInfo = `Reducers: ${Object.keys(reduxState || {}).length}`;
2455
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
1935
2456
  <View style={{
1936
2457
  backgroundColor: AppColors.primaryLight,
1937
2458
  padding: 16,
@@ -2015,53 +2536,108 @@ const NetworkInspector = ({ enabled = true, }) => {
2015
2536
  </View>
2016
2537
  </ScrollView>);
2017
2538
  }
2018
- // Default return page is Insights settings
2019
- return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
2020
- {renderSubHeader('Insights Settings', <InsightsIcon color={AppColors.purple} size={16}/>)}
2021
- <View style={{
2022
- backgroundColor: AppColors.primaryLight,
2023
- padding: 16,
2024
- borderRadius: 12,
2539
+ else {
2540
+ title = 'Insights Settings';
2541
+ icon = <InsightsIcon color="#FFFFFF" size={16}/>;
2542
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
2543
+ <View style={{
2544
+ backgroundColor: AppColors.primaryLight,
2545
+ padding: 16,
2546
+ borderRadius: 12,
2547
+ borderWidth: 1,
2548
+ borderColor: AppColors.grayBorderSecondary,
2549
+ gap: 4,
2550
+ }}>
2551
+ {renderSettingRow({
2552
+ icon: <SignalIcon color={AppColors.purple} size={16}/>,
2553
+ label: 'Slow Latency Warning',
2554
+ description: 'Alert threshold for slow API request duration',
2555
+ picker: {
2556
+ options: [500, 1000, 2000],
2557
+ selectedValue: slowRequestThreshold,
2558
+ onSelect: setSlowRequestThreshold,
2559
+ },
2560
+ })}
2561
+ <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
2562
+ {renderSettingRow({
2563
+ icon: <TerminalIcon color={AppColors.purple} size={16}/>,
2564
+ label: 'Show Console Alerts',
2565
+ description: 'Flags critical warnings or crash events on dashboard',
2566
+ isLast: true,
2567
+ onPress: () => setInsightsShowConsoleAlerts(prev => !prev),
2568
+ right: (<View style={{
2569
+ width: 22,
2570
+ height: 22,
2571
+ borderRadius: 6,
2572
+ borderWidth: 2,
2573
+ borderColor: insightsShowConsoleAlerts
2574
+ ? AppColors.purple
2575
+ : AppColors.grayTextWeak,
2576
+ backgroundColor: insightsShowConsoleAlerts
2577
+ ? 'rgba(104, 75, 155, 0.1)'
2578
+ : 'transparent',
2579
+ alignItems: 'center',
2580
+ justifyContent: 'center',
2581
+ }}>
2582
+ {insightsShowConsoleAlerts && (<CheckIcon size={12} color={AppColors.purple}/>)}
2583
+ </View>),
2584
+ })}
2585
+ </View>
2586
+ </ScrollView>);
2587
+ }
2588
+ return (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
2589
+ <LinearGradient colors={[AppColors.purple, '#6B4EFF']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={styles.headerGradient}>
2590
+ <SafeAreaView style={{ width: '100%' }}>
2591
+ <View style={[styles.header, { paddingHorizontal: 16, gap: 12 }]}>
2592
+ <TouchableScale onPress={goBackToMain} hitSlop={12} style={{
2593
+ padding: 8,
2594
+ borderRadius: 10,
2595
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
2025
2596
  borderWidth: 1,
2026
- borderColor: AppColors.grayBorderSecondary,
2027
- gap: 4,
2597
+ borderColor: 'rgba(255, 255, 255, 0.08)',
2028
2598
  }}>
2029
- {renderSettingRow({
2030
- icon: <SignalIcon color={AppColors.purple} size={16}/>,
2031
- label: 'Slow Latency Warning',
2032
- description: 'Alert threshold for slow API request duration',
2033
- picker: {
2034
- options: [500, 1000, 2000],
2035
- selectedValue: slowRequestThreshold,
2036
- onSelect: setSlowRequestThreshold,
2037
- },
2038
- })}
2039
- <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
2040
- {renderSettingRow({
2041
- icon: <TerminalIcon color={AppColors.purple} size={16}/>,
2042
- label: 'Show Console Alerts',
2043
- description: 'Flags critical warnings or crash events on dashboard',
2044
- isLast: true,
2045
- onPress: () => setInsightsShowConsoleAlerts(prev => !prev),
2046
- right: (<View style={{
2047
- width: 22,
2048
- height: 22,
2049
- borderRadius: 6,
2050
- borderWidth: 2,
2051
- borderColor: insightsShowConsoleAlerts
2052
- ? AppColors.purple
2053
- : AppColors.grayTextWeak,
2054
- backgroundColor: insightsShowConsoleAlerts
2055
- ? 'rgba(104, 75, 155, 0.1)'
2056
- : 'transparent',
2057
- alignItems: 'center',
2058
- justifyContent: 'center',
2059
- }}>
2060
- {insightsShowConsoleAlerts && (<CheckIcon size={12} color={AppColors.purple}/>)}
2061
- </View>),
2062
- })}
2063
- </View>
2064
- </ScrollView>);
2599
+ <WhiteBackNavigation color="#FFFFFF" size={16}/>
2600
+ </TouchableScale>
2601
+ {icon && (<View style={{
2602
+ width: 30,
2603
+ height: 30,
2604
+ borderRadius: 8,
2605
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
2606
+ alignItems: 'center',
2607
+ justifyContent: 'center',
2608
+ }}>
2609
+ {icon}
2610
+ </View>)}
2611
+ <View style={{ flex: 1 }}>
2612
+ <Text style={{
2613
+ fontFamily: AppFonts.interBold,
2614
+ fontSize: 17,
2615
+ color: '#FFFFFF',
2616
+ }}>
2617
+ {title}
2618
+ </Text>
2619
+ </View>
2620
+ {rightInfo ? (<View style={{
2621
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
2622
+ paddingHorizontal: 8,
2623
+ paddingVertical: 4,
2624
+ borderRadius: 8,
2625
+ borderWidth: 1,
2626
+ borderColor: 'rgba(255, 255, 255, 0.1)',
2627
+ }}>
2628
+ <Text style={{
2629
+ fontFamily: AppFonts.interBold,
2630
+ fontSize: 11,
2631
+ color: '#FFFFFF',
2632
+ }}>
2633
+ {rightInfo}
2634
+ </Text>
2635
+ </View>) : null}
2636
+ </View>
2637
+ </SafeAreaView>
2638
+ </LinearGradient>
2639
+ {content}
2640
+ </View>);
2065
2641
  };
2066
2642
  const renderInsightsDashboard = () => {
2067
2643
  const apiTotal = logs.length;
@@ -2097,11 +2673,11 @@ const NetworkInspector = ({ enabled = true, }) => {
2097
2673
  const totalSignals = apiTotal + logTotal + analyticsTotal + webviewTotal;
2098
2674
  const totalIssues = apiErrors + logErrors;
2099
2675
  const activeModules = [
2100
- tabVisibility.apis,
2101
- tabVisibility.logs,
2102
- tabVisibility.analytics,
2103
- tabVisibility.webview,
2104
- tabVisibility.redux,
2676
+ tabVisibility?.apis,
2677
+ tabVisibility?.logs,
2678
+ tabVisibility?.analytics,
2679
+ tabVisibility?.webview,
2680
+ tabVisibility?.redux,
2105
2681
  ].filter(Boolean).length;
2106
2682
  // Composite health score: success rate penalised by error volume and slow requests.
2107
2683
  const healthScore = totalSignals === 0
@@ -2268,7 +2844,7 @@ const NetworkInspector = ({ enabled = true, }) => {
2268
2844
  </View>
2269
2845
 
2270
2846
  {/* Module 1: APIs */}
2271
- {tabVisibility.apis && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('apis')}>
2847
+ {tabVisibility?.apis && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('apis')}>
2272
2848
  <View style={styles.dashboardModuleHeader}>
2273
2849
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
2274
2850
  <SignalIcon color={AppColors.purple} size={18}/>
@@ -2375,7 +2951,7 @@ const NetworkInspector = ({ enabled = true, }) => {
2375
2951
  </View>)}
2376
2952
  </View>
2377
2953
  </TouchableScale>)}
2378
- {tabVisibility.logs && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('logs')}>
2954
+ {tabVisibility?.logs && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('logs')}>
2379
2955
  <View style={styles.dashboardModuleHeader}>
2380
2956
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
2381
2957
  <TerminalIcon color="#0D9488" size={18}/>
@@ -2416,7 +2992,7 @@ const NetworkInspector = ({ enabled = true, }) => {
2416
2992
  </TouchableScale>)}
2417
2993
 
2418
2994
  {/* Module 3: Analytics */}
2419
- {tabVisibility.analytics && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('analytics')}>
2995
+ {tabVisibility?.analytics && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('analytics')}>
2420
2996
  <View style={styles.dashboardModuleHeader}>
2421
2997
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
2422
2998
  <AnalyticsIcon color="#EA580C" size={18}/>
@@ -2453,7 +3029,7 @@ const NetworkInspector = ({ enabled = true, }) => {
2453
3029
  </TouchableScale>)}
2454
3030
 
2455
3031
  {/* Module 4: WebView */}
2456
- {tabVisibility.webview && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('webview')}>
3032
+ {tabVisibility?.webview && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('webview')}>
2457
3033
  <View style={styles.dashboardModuleHeader}>
2458
3034
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
2459
3035
  <GlobeIcon color="#2563EB" size={18}/>
@@ -2486,7 +3062,7 @@ const NetworkInspector = ({ enabled = true, }) => {
2486
3062
  </TouchableScale>)}
2487
3063
 
2488
3064
  {/* Module 5: Redux Store */}
2489
- {tabVisibility.redux && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('redux')}>
3065
+ {tabVisibility?.redux && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('redux')}>
2490
3066
  <View style={styles.dashboardModuleHeader}>
2491
3067
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
2492
3068
  <TerminalIcon color={AppColors.purple} size={18}/>
@@ -2751,6 +3327,29 @@ const NetworkInspector = ({ enabled = true, }) => {
2751
3327
  }} hitSlop={10}>
2752
3328
  <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
2753
3329
  <BrandCircleIcon size={62}/>
3330
+ {/* #4 — shining sweep, clipped inside the circular launcher */}
3331
+ <View pointerEvents="none" style={styles.fabShineClip}>
3332
+ <Animated.View style={[
3333
+ styles.fabShineStreak,
3334
+ {
3335
+ transform: [
3336
+ {
3337
+ translateX: fabShineAnim.interpolate({
3338
+ inputRange: [0, 1],
3339
+ outputRange: [-48, 96],
3340
+ }),
3341
+ },
3342
+ { rotate: '25deg' },
3343
+ ],
3344
+ },
3345
+ ]}>
3346
+ <LinearGradient colors={[
3347
+ 'rgba(255,255,255,0)',
3348
+ 'rgba(255,255,255,0.55)',
3349
+ 'rgba(255,255,255,0)',
3350
+ ]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
3351
+ </Animated.View>
3352
+ </View>
2754
3353
  {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
2755
3354
  styles.fabBadge,
2756
3355
  hasErrors ? styles.fabBadgeError : styles.fabBadgeNormal,
@@ -2776,7 +3375,8 @@ const NetworkInspector = ({ enabled = true, }) => {
2776
3375
  <StatusBar translucent backgroundColor="transparent" barStyle="light-content"/>
2777
3376
 
2778
3377
  <LinearGradient colors={[AppColors.purple, '#6B4EFF']} style={styles.headerGradient}>
2779
- <View style={styles.header}>
3378
+ <SafeAreaView style={{ width: '100%' }}>
3379
+ <View style={styles.header}>
2780
3380
  <View style={[
2781
3381
  styles.headerLeft,
2782
3382
  {
@@ -2923,6 +3523,29 @@ const NetworkInspector = ({ enabled = true, }) => {
2923
3523
  </Text>
2924
3524
  </View>
2925
3525
  </View>
3526
+
3527
+ {/* #1 — pulsing dot when a newer version is on NPM */}
3528
+ {updateAvailable && (<Pressable hitSlop={10} onPress={() => Alert.alert('Update Available', `react-native-inapp-inspector v${latestNpmVersion} is available on NPM (installed: v${LIB_VERSION}).`, [
3529
+ { text: 'Later', style: 'cancel' },
3530
+ {
3531
+ text: 'View on NPM',
3532
+ onPress: () => Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { }),
3533
+ },
3534
+ ])} style={{
3535
+ alignItems: 'center',
3536
+ justifyContent: 'center',
3537
+ }}>
3538
+ <Animated.View style={{
3539
+ width: 8,
3540
+ height: 8,
3541
+ borderRadius: 4,
3542
+ backgroundColor: '#4ADE80',
3543
+ borderWidth: 1,
3544
+ borderColor: 'rgba(255,255,255,0.9)',
3545
+ opacity: activePulseAnim,
3546
+ transform: [{ scale: unreadPulseAnim }],
3547
+ }}/>
3548
+ </Pressable>)}
2926
3549
  </View>
2927
3550
  </View>
2928
3551
  </View>) : null}
@@ -3071,7 +3694,7 @@ const NetworkInspector = ({ enabled = true, }) => {
3071
3694
  },
3072
3695
  ],
3073
3696
  }}>
3074
- <TrashIcon color="#FFFFFF" size={15}/>
3697
+ <WipeIcon color="#FFFFFF" size={16}/>
3075
3698
  </Animated.View>
3076
3699
  </TouchableScale>)}
3077
3700
 
@@ -3090,7 +3713,8 @@ const NetworkInspector = ({ enabled = true, }) => {
3090
3713
  </TouchableScale>
3091
3714
  </View>
3092
3715
  </View>
3093
- </LinearGradient>
3716
+ </SafeAreaView>
3717
+ </LinearGradient>
3094
3718
 
3095
3719
  {/* ─── Horizontal Scrollable Tab Bar inside Content ─── */}
3096
3720
  {selected == null &&
@@ -3135,7 +3759,7 @@ const NetworkInspector = ({ enabled = true, }) => {
3135
3759
  icon: 'redux',
3136
3760
  },
3137
3761
  ]
3138
- .filter(tab => tabVisibility[tab.key])
3762
+ .filter(tab => tabVisibility?.[tab.key])
3139
3763
  .map(tab => {
3140
3764
  const isActive = activeTab === tab.key;
3141
3765
  const iconColor = isActive
@@ -3251,7 +3875,16 @@ const NetworkInspector = ({ enabled = true, }) => {
3251
3875
  animateNextLayout();
3252
3876
  setAnalyticsSubTab('ga_events');
3253
3877
  }}>
3254
- <Text style={[
3878
+ <View style={{
3879
+ flexDirection: 'row',
3880
+ alignItems: 'center',
3881
+ gap: 6,
3882
+ }}>
3883
+ {/* #7 */}
3884
+ <AnalyticsIcon size={13} color={analyticsSubTab === 'ga_events'
3885
+ ? AppColors.purple
3886
+ : AppColors.grayTextStrong}/>
3887
+ <Text style={[
3255
3888
  {
3256
3889
  fontFamily: AppFonts.interMedium,
3257
3890
  fontSize: 13,
@@ -3262,12 +3895,13 @@ const NetworkInspector = ({ enabled = true, }) => {
3262
3895
  color: AppColors.purple,
3263
3896
  },
3264
3897
  ]}>
3265
- GA Events (
3266
- {analyticsSearch
3898
+ GA Events (
3899
+ {analyticsSearch
3267
3900
  ? filteredAnalyticsEvents.length
3268
3901
  : analyticsEvents.length}
3269
- )
3270
- </Text>
3902
+ )
3903
+ </Text>
3904
+ </View>
3271
3905
  </Pressable>
3272
3906
  <Pressable style={[
3273
3907
  {
@@ -3288,7 +3922,16 @@ const NetworkInspector = ({ enabled = true, }) => {
3288
3922
  animateNextLayout();
3289
3923
  setAnalyticsSubTab('top_events');
3290
3924
  }}>
3291
- <Text style={[
3925
+ <View style={{
3926
+ flexDirection: 'row',
3927
+ alignItems: 'center',
3928
+ gap: 6,
3929
+ }}>
3930
+ {/* #7 */}
3931
+ <TrendingUpIcon size={13} color={analyticsSubTab === 'top_events'
3932
+ ? AppColors.purple
3933
+ : AppColors.grayTextStrong}/>
3934
+ <Text style={[
3292
3935
  {
3293
3936
  fontFamily: AppFonts.interMedium,
3294
3937
  fontSize: 13,
@@ -3299,8 +3942,9 @@ const NetworkInspector = ({ enabled = true, }) => {
3299
3942
  color: AppColors.purple,
3300
3943
  },
3301
3944
  ]}>
3302
- Top Events ({topEventsArray.length})
3303
- </Text>
3945
+ Top Events ({topEventsArray.length})
3946
+ </Text>
3947
+ </View>
3304
3948
  </Pressable>
3305
3949
  </View>
3306
3950
  </View>)}
@@ -3410,7 +4054,7 @@ const NetworkInspector = ({ enabled = true, }) => {
3410
4054
  flexGrow: 1,
3411
4055
  },
3412
4056
  ]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (<View style={{ flex: 1 }}>
3413
- <FlatList ref={apisListRef} onScroll={e => setShowScrollTop(e.nativeEvent.contentOffset.y > 320)} scrollEventThrottle={16} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
4057
+ <FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
3414
4058
  <View style={styles.toolbarRow}>
3415
4059
  <View style={styles.searchContainer}>
3416
4060
  <SearchIcon color={AppColors.grayTextWeak} size={16}/>
@@ -3561,17 +4205,17 @@ const NetworkInspector = ({ enabled = true, }) => {
3561
4205
  styles.listContent,
3562
4206
  filteredLogs.length === 0 && { flexGrow: 1 },
3563
4207
  ]} keyboardShouldPersistTaps="handled"/>
3564
- {showScrollTop && (<TouchableScale onPress={() => {
3565
- apisListRef.current?.scrollToOffset({
3566
- offset: 0,
3567
- animated: true,
3568
- });
3569
- setShowScrollTop(false);
3570
- }} hitSlop={10} style={styles.scrollTopBtn}>
3571
- <View style={{ transform: [{ rotate: '180deg' }] }}>
3572
- <ChevronIcon color="#FFFFFF" size={18}/>
3573
- </View>
3574
- </TouchableScale>)}
4208
+ {/* #2 always-visible scroll-to-top, bottom right */}
4209
+ <TouchableScale onPress={() => {
4210
+ apisListRef.current?.scrollToOffset({
4211
+ offset: 0,
4212
+ animated: true,
4213
+ });
4214
+ }} hitSlop={10} style={styles.scrollTopBtn}>
4215
+ <View style={{ transform: [{ rotate: '180deg' }] }}>
4216
+ <ChevronIcon color="#FFFFFF" size={18}/>
4217
+ </View>
4218
+ </TouchableScale>
3575
4219
  </View>) : activeTab === 'logs' ? (<View style={{ flex: 1 }}>
3576
4220
  <View style={{
3577
4221
  backgroundColor: '#FFFFFF',
@@ -3620,15 +4264,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3620
4264
  backgroundColor: '#F4EBFF',
3621
4265
  },
3622
4266
  ]}>
3623
- <Text numberOfLines={1} style={[
4267
+ {/* #7 */}
4268
+ <View style={{
4269
+ flexDirection: 'row',
4270
+ alignItems: 'center',
4271
+ gap: 5,
4272
+ }}>
4273
+ <LayersIcon size={12} color={active
4274
+ ? AppColors.purpleShade700
4275
+ : AppColors.grayTextStrong}/>
4276
+ <Text numberOfLines={1} style={[
3624
4277
  styles.statusFilterText,
3625
4278
  active && {
3626
4279
  color: AppColors.purpleShade700,
3627
4280
  fontFamily: AppFonts.interBold,
3628
4281
  },
3629
4282
  ]}>
3630
- All ({logCounts.all})
3631
- </Text>
4283
+ All ({logCounts.all})
4284
+ </Text>
4285
+ </View>
3632
4286
  </View>
3633
4287
  </TouchableScale>);
3634
4288
  })()}
@@ -3654,15 +4308,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3654
4308
  backgroundColor: '#F1F5F9',
3655
4309
  },
3656
4310
  ]}>
3657
- <Text numberOfLines={1} style={[
4311
+ {/* #7 */}
4312
+ <View style={{
4313
+ flexDirection: 'row',
4314
+ alignItems: 'center',
4315
+ gap: 5,
4316
+ }}>
4317
+ <UserIcon size={12} color={active
4318
+ ? '#334155'
4319
+ : AppColors.grayTextStrong}/>
4320
+ <Text numberOfLines={1} style={[
3658
4321
  styles.statusFilterText,
3659
4322
  active && {
3660
4323
  color: '#334155',
3661
4324
  fontFamily: AppFonts.interBold,
3662
4325
  },
3663
4326
  ]}>
3664
- User Log ({logCounts['user-log']})
3665
- </Text>
4327
+ User Log ({logCounts['user-log']})
4328
+ </Text>
4329
+ </View>
3666
4330
  </View>
3667
4331
  </TouchableScale>);
3668
4332
  })()}
@@ -3688,15 +4352,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3688
4352
  backgroundColor: AppColors.purpleShade50,
3689
4353
  },
3690
4354
  ]}>
3691
- <Text numberOfLines={1} style={[
4355
+ {/* #7 */}
4356
+ <View style={{
4357
+ flexDirection: 'row',
4358
+ alignItems: 'center',
4359
+ gap: 5,
4360
+ }}>
4361
+ <InfoCircleIcon size={12} color={active
4362
+ ? AppColors.purple
4363
+ : AppColors.grayTextStrong}/>
4364
+ <Text numberOfLines={1} style={[
3692
4365
  styles.statusFilterText,
3693
4366
  active && {
3694
4367
  color: AppColors.purple,
3695
4368
  fontFamily: AppFonts.interBold,
3696
4369
  },
3697
4370
  ]}>
3698
- Info ({logCounts.info})
3699
- </Text>
4371
+ Info ({logCounts.info})
4372
+ </Text>
4373
+ </View>
3700
4374
  </View>
3701
4375
  </TouchableScale>);
3702
4376
  })()}
@@ -3722,7 +4396,17 @@ const NetworkInspector = ({ enabled = true, }) => {
3722
4396
  backgroundColor: '#FFFDF6',
3723
4397
  },
3724
4398
  ]}>
3725
- <Text numberOfLines={1} style={[
4399
+ {/* #7 */}
4400
+ <View style={{
4401
+ flexDirection: 'row',
4402
+ alignItems: 'center',
4403
+ gap: 5,
4404
+ }}>
4405
+ <WarningTriangleIcon size={12} color={active
4406
+ ? AppColors.darkOrange ||
4407
+ AppColors.lightOrange
4408
+ : AppColors.grayTextStrong}/>
4409
+ <Text numberOfLines={1} style={[
3726
4410
  styles.statusFilterText,
3727
4411
  active && {
3728
4412
  color: AppColors.darkOrange ||
@@ -3730,8 +4414,9 @@ const NetworkInspector = ({ enabled = true, }) => {
3730
4414
  fontFamily: AppFonts.interBold,
3731
4415
  },
3732
4416
  ]}>
3733
- Warning ({logCounts.warn})
3734
- </Text>
4417
+ Warning ({logCounts.warn})
4418
+ </Text>
4419
+ </View>
3735
4420
  </View>
3736
4421
  </TouchableScale>);
3737
4422
  })()}
@@ -3757,15 +4442,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3757
4442
  backgroundColor: '#FFF5F6',
3758
4443
  },
3759
4444
  ]}>
3760
- <Text numberOfLines={1} style={[
4445
+ {/* #7 */}
4446
+ <View style={{
4447
+ flexDirection: 'row',
4448
+ alignItems: 'center',
4449
+ gap: 5,
4450
+ }}>
4451
+ <ErrorCircleIcon size={12} color={active
4452
+ ? AppColors.errorColor
4453
+ : AppColors.grayTextStrong}/>
4454
+ <Text numberOfLines={1} style={[
3761
4455
  styles.statusFilterText,
3762
4456
  active && {
3763
4457
  color: AppColors.errorColor,
3764
4458
  fontFamily: AppFonts.interBold,
3765
4459
  },
3766
4460
  ]}>
3767
- Error ({logCounts.error})
3768
- </Text>
4461
+ Error ({logCounts.error})
4462
+ </Text>
4463
+ </View>
3769
4464
  </View>
3770
4465
  </TouchableScale>);
3771
4466
  })()}
@@ -3791,15 +4486,25 @@ const NetworkInspector = ({ enabled = true, }) => {
3791
4486
  backgroundColor: `${AppColors.skyBlue}15`,
3792
4487
  },
3793
4488
  ]}>
3794
- <Text numberOfLines={1} style={[
4489
+ {/* #7 */}
4490
+ <View style={{
4491
+ flexDirection: 'row',
4492
+ alignItems: 'center',
4493
+ gap: 5,
4494
+ }}>
4495
+ <AnalyticsIcon size={12} color={active
4496
+ ? AppColors.skyBlue
4497
+ : AppColors.grayTextStrong}/>
4498
+ <Text numberOfLines={1} style={[
3795
4499
  styles.statusFilterText,
3796
4500
  active && {
3797
4501
  color: AppColors.skyBlue,
3798
4502
  fontFamily: AppFonts.interBold,
3799
4503
  },
3800
4504
  ]}>
3801
- Analytics ({logCounts.analytics})
3802
- </Text>
4505
+ Analytics ({logCounts.analytics})
4506
+ </Text>
4507
+ </View>
3803
4508
  </View>
3804
4509
  </TouchableScale>);
3805
4510
  })()}
@@ -4556,13 +5261,13 @@ const NetworkInspector = ({ enabled = true, }) => {
4556
5261
  <View style={[
4557
5262
  styles.methodBadge,
4558
5263
  {
4559
- backgroundColor: `${METHOD_COLORS[selected.method] ?? METHOD_COLORS.ALL}15`,
5264
+ backgroundColor: METHOD_COLORS[selected.method] ?? METHOD_COLORS.ALL,
4560
5265
  },
4561
5266
  ]}>
4562
5267
  <Text style={[
4563
5268
  styles.methodBadgeText,
4564
5269
  {
4565
- color: METHOD_COLORS[selected.method] ?? METHOD_COLORS.ALL,
5270
+ color: '#FFFFFF',
4566
5271
  },
4567
5272
  ]}>
4568
5273
  {selected.method}
@@ -4872,4 +5577,4 @@ export { setupConsoleLogger, clearConsoleLogs, subscribeConsoleLogs, } from './c
4872
5577
  export { setupAnalyticsLogger, logAnalyticsEvent, subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
4873
5578
  export { WebView, getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
4874
5579
  export { default as ErrorBoundary } from './components/ErrorBoundary';
4875
- export { connectReduxStore, getReduxState, subscribeReduxState, } from './customHooks/reduxLogger';
5580
+ export { connectReduxStore, inspectorReduxMiddleware, getReduxState, subscribeReduxState, getActionHistory, clearActionHistory, } from './customHooks/reduxLogger';