react-native-inapp-inspector 1.1.0 → 1.1.2

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.
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, 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, } 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';
@@ -11,7 +11,7 @@ import CopyButton from './components/CopyButton';
11
11
  import SectionHeader from './components/SectionHeader';
12
12
  import EmptyState from './components/EmptyState';
13
13
  import JsonViewer from './components/JsonViewer';
14
- import { ReduxTreeView, ReduxActionTimeline } from './components/ReduxTreeView';
14
+ import { ReduxTreeView, ReduxActionTimeline, ReduxTimelineIcon, ReduxTreeIcon, } from './components/ReduxTreeView';
15
15
  import DomainHeader from './components/DomainHeader';
16
16
  import DiffViewer from './components/DiffViewer';
17
17
  import LogCard from './components/LogCard';
@@ -24,7 +24,7 @@ import AnimatedEntrance from './components/AnimatedEntrance';
24
24
  // Helpers
25
25
  import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
26
26
  // 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, } from './components/NetworkIcons';
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';
28
28
  import ErrorBoundary from './components/ErrorBoundary';
29
29
  // Stylesheet
30
30
  import { AppColors } from './styles/AppColors';
@@ -100,7 +100,7 @@ const previewInspectScript = `
100
100
  true;
101
101
  `;
102
102
  import { getReduxState, subscribeReduxState, setReduxAutoRefresh, getLastActionForReducer, getActionHistory, clearActionHistory, } from './customHooks/reduxLogger';
103
- import { METHOD_COLORS, STATUS_FILTERS } from './constants';
103
+ import { METHOD_COLORS, STATUS_FILTERS, LIB_VERSION } from './constants';
104
104
  const NavigationTracker = ({ onStateChange }) => {
105
105
  const navState = useNavigationState(state => state);
106
106
  useEffect(() => {
@@ -114,6 +114,12 @@ const animateNextLayout = () => {
114
114
  const NetworkInspector = ({ enabled = true, }) => {
115
115
  const [isDark, setIsDark] = useState(false);
116
116
  const [reduxState, setReduxState] = useState(null);
117
+ // Action timeline + per-reducer last action are kept in component state so the
118
+ // Redux tab re-renders live on every dispatch, independent of the state tree ref.
119
+ const [reduxActionHistory, setReduxActionHistory] = useState([]);
120
+ const [reduxLastActionMap, setReduxLastActionMap] = useState({});
121
+ // Inspector panel height as a percentage of the screen (configurable in Settings).
122
+ const [modalHeightPercent, setModalHeightPercent] = useState(90);
117
123
  const [expandedReducers, setExpandedReducers] = useState({});
118
124
  const [logs, setLogs] = useState([]);
119
125
  const [visible, setVisible] = useState(false);
@@ -144,6 +150,8 @@ const NetworkInspector = ({ enabled = true, }) => {
144
150
  const [showNetworkMenu, setShowNetworkMenu] = useState(false);
145
151
  const [showUiMenu, setShowUiMenu] = useState(false);
146
152
  const [sortOrder, setSortOrder] = useState('newest');
153
+ // #7 — sort order for the Logs (console) tab
154
+ const [logSortOrder, setLogSortOrder] = useState('newest');
147
155
  const [reqExpanded, setReqExpanded] = useState(undefined);
148
156
  const [resExpanded, setResExpanded] = useState(undefined);
149
157
  const [showReqDiff, setShowReqDiff] = useState(false);
@@ -323,6 +331,66 @@ const NetworkInspector = ({ enabled = true, }) => {
323
331
  const badgeAnim = useRef(new Animated.Value(1)).current;
324
332
  const activePulseAnim = useRef(new Animated.Value(0.4)).current;
325
333
  const unreadPulseAnim = useRef(new Animated.Value(1)).current;
334
+ // #11 — header "clear all" icon spin/scale animation
335
+ const clearAnim = useRef(new Animated.Value(0)).current;
336
+ // #4 — draggable floating launcher (drag anywhere on screen)
337
+ const fabPan = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current;
338
+ const fabPanRef = useRef({ x: 0, y: 0 });
339
+ useEffect(() => {
340
+ const idX = fabPan.x.addListener(v => (fabPanRef.current.x = v.value));
341
+ const idY = fabPan.y.addListener(v => (fabPanRef.current.y = v.value));
342
+ return () => {
343
+ fabPan.x.removeListener(idX);
344
+ fabPan.y.removeListener(idY);
345
+ };
346
+ }, [fabPan]);
347
+ const fabDraggedRef = useRef(false);
348
+ const fabPanResponder = useRef(PanResponder.create({
349
+ // Let taps fall through to the launcher; only hijack once the
350
+ // finger actually moves, so onPress still fires on a tap.
351
+ onStartShouldSetPanResponder: () => false,
352
+ onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 4 || Math.abs(g.dy) > 4,
353
+ onPanResponderGrant: () => {
354
+ fabDraggedRef.current = true;
355
+ fabPan.setOffset({
356
+ x: fabPanRef.current.x,
357
+ y: fabPanRef.current.y,
358
+ });
359
+ fabPan.setValue({ x: 0, y: 0 });
360
+ },
361
+ onPanResponderMove: Animated.event([null, { dx: fabPan.x, dy: fabPan.y }], {
362
+ useNativeDriver: false,
363
+ }),
364
+ onPanResponderRelease: () => {
365
+ fabPan.flattenOffset();
366
+ // small delay so the trailing tap (if any) is ignored
367
+ setTimeout(() => {
368
+ fabDraggedRef.current = false;
369
+ }, 50);
370
+ },
371
+ onPanResponderTerminate: () => {
372
+ fabPan.flattenOffset();
373
+ fabDraggedRef.current = false;
374
+ },
375
+ })).current;
376
+ // #10 — scroll-to-top affordance for the main APIs list
377
+ const apisListRef = useRef(null);
378
+ const [showScrollTop, setShowScrollTop] = useState(false);
379
+ const runClearAllWithAnimation = useCallback(() => {
380
+ Animated.sequence([
381
+ Animated.timing(clearAnim, {
382
+ toValue: 1,
383
+ duration: 320,
384
+ useNativeDriver: true,
385
+ }),
386
+ Animated.timing(clearAnim, {
387
+ toValue: 0,
388
+ duration: 0,
389
+ useNativeDriver: true,
390
+ }),
391
+ ]).start();
392
+ handleClearAll();
393
+ }, [clearAnim]);
326
394
  useEffect(() => {
327
395
  if (Platform.OS === 'android') {
328
396
  UIManager.setLayoutAnimationEnabledExperimental?.(true);
@@ -480,8 +548,14 @@ const NetworkInspector = ({ enabled = true, }) => {
480
548
  }, 200);
481
549
  });
482
550
  setReduxState(getReduxState());
551
+ setReduxActionHistory([...getActionHistory()]);
552
+ setReduxLastActionMap({ ...getLastActionForReducer() });
483
553
  const unsubscribeRedux = subscribeReduxState(() => {
554
+ // New references each time guarantee the Redux tab updates live, even when
555
+ // the root state object reference is unchanged or auto-refresh is paused.
484
556
  setReduxState(getReduxState());
557
+ setReduxActionHistory([...getActionHistory()]);
558
+ setReduxLastActionMap({ ...getLastActionForReducer() });
485
559
  });
486
560
  return () => {
487
561
  unsubscribe();
@@ -741,8 +815,12 @@ const NetworkInspector = ({ enabled = true, }) => {
741
815
  result = result.filter(log => log.message.toLowerCase().includes(s) ||
742
816
  (log.caller ?? '').toLowerCase().includes(s));
743
817
  }
818
+ // #7 — apply sort order (newest/oldest first)
819
+ result = [...result].sort((a, b) => logSortOrder === 'newest'
820
+ ? b.timestamp - a.timestamp
821
+ : a.timestamp - b.timestamp);
744
822
  return result;
745
- }, [visibleConsoleLogs, logFilters, logSearch]);
823
+ }, [visibleConsoleLogs, logFilters, logSearch, logSortOrder]);
746
824
  const filteredWebViewLogs = useMemo(() => {
747
825
  let result = webViewLogs;
748
826
  if (webViewSearch) {
@@ -868,6 +946,22 @@ const NetworkInspector = ({ enabled = true, }) => {
868
946
  ]);
869
947
  return;
870
948
  }
949
+ if (activeTab === 'redux') {
950
+ Alert.alert('Clear Redux Timeline', 'Are you sure you want to clear the dispatched action history?', [
951
+ { text: 'Cancel', style: 'cancel' },
952
+ {
953
+ text: 'Clear All',
954
+ onPress: () => {
955
+ clearActionHistory();
956
+ setReduxActionHistory([]);
957
+ setReduxLastActionMap({});
958
+ },
959
+ style: 'destructive',
960
+ },
961
+ ]);
962
+ return;
963
+ }
964
+ // Default: APIs tab. Only clears NETWORK logs — never touches the other tabs.
871
965
  if (selectedLogs.size > 0) {
872
966
  setLogs(prev => prev.filter(l => !selectedLogs.has(l.id)));
873
967
  setSelectedLogs(new Set());
@@ -875,10 +969,22 @@ const NetworkInspector = ({ enabled = true, }) => {
875
969
  else {
876
970
  Alert.alert('Clear Logs', 'Are you sure you want to clear all network logs?', [
877
971
  { text: 'Cancel', style: 'cancel' },
878
- { text: 'Clear All', onPress: handleClearAll, style: 'destructive' },
972
+ { text: 'Clear All', onPress: clearNetworkOnly, style: 'destructive' },
879
973
  ]);
880
974
  }
881
975
  }
976
+ // Clears ONLY network logs + their derived selection/filter state.
977
+ function clearNetworkOnly() {
978
+ clearNetworkLogs();
979
+ setLogs([]);
980
+ setSelectedLogs(new Set());
981
+ setSectionFilters({});
982
+ setCollapsedSections(new Set());
983
+ setStatusFilters(new Set());
984
+ setMethodFilters(new Set());
985
+ prevLogIdsRef.current = new Set();
986
+ logRouteMapRef.current = new Map();
987
+ }
882
988
  const detailTitle = useMemo(() => {
883
989
  if (!selected)
884
990
  return '';
@@ -995,7 +1101,7 @@ const NetworkInspector = ({ enabled = true, }) => {
995
1101
  fontSize: 10.5,
996
1102
  color: '#FFFFFF',
997
1103
  }}>
998
- v1.0.13
1104
+ v{LIB_VERSION}
999
1105
  </Text>
1000
1106
  </View>
1001
1107
  </View>
@@ -1163,30 +1269,56 @@ const NetworkInspector = ({ enabled = true, }) => {
1163
1269
  width: 38,
1164
1270
  height: 22,
1165
1271
  borderRadius: 11,
1166
- backgroundColor: isVisible
1167
- ? AppColors.purple
1168
- : AppColors.grayBorderSecondary,
1272
+ backgroundColor: isLocked
1273
+ ? AppColors.grayBackground
1274
+ : isVisible
1275
+ ? AppColors.purple
1276
+ : AppColors.grayBorderSecondary,
1277
+ borderWidth: isLocked ? 1.5 : 0,
1278
+ borderColor: isLocked
1279
+ ? AppColors.grayBorderSecondary
1280
+ : 'transparent',
1281
+ borderStyle: isLocked ? 'dashed' : 'solid',
1169
1282
  padding: 2,
1170
1283
  justifyContent: 'center',
1171
1284
  alignItems: isVisible ? 'flex-end' : 'flex-start',
1285
+ opacity: isLocked ? 0.9 : 1,
1172
1286
  }}>
1173
1287
  <View style={{
1174
1288
  width: 18,
1175
1289
  height: 18,
1176
1290
  borderRadius: 9,
1177
- backgroundColor: '#FFFFFF',
1291
+ backgroundColor: isLocked
1292
+ ? AppColors.grayBorderSecondary
1293
+ : '#FFFFFF',
1294
+ alignItems: 'center',
1295
+ justifyContent: 'center',
1178
1296
  shadowColor: '#000',
1179
- shadowOpacity: 0.15,
1297
+ shadowOpacity: isLocked ? 0 : 0.15,
1180
1298
  shadowRadius: 1.5,
1181
1299
  shadowOffset: { width: 0, height: 1 },
1182
- }}/>
1300
+ }}>
1301
+ {isLocked && (<Svg width={10} height={10} viewBox="0 0 24 24" fill="none">
1302
+ <Path d="M7 10V7a5 5 0 0 1 10 0v3" stroke={AppColors.grayText} strokeWidth="2.2" strokeLinecap="round"/>
1303
+ <Path d="M5 10h14v9a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z" fill={AppColors.grayText}/>
1304
+ </Svg>)}
1305
+ </View>
1183
1306
  </TouchableScale>
1307
+ {isLocked && (<Text style={{
1308
+ fontFamily: AppFonts.interBold,
1309
+ fontSize: 8,
1310
+ color: AppColors.grayTextWeak,
1311
+ letterSpacing: 0.4,
1312
+ marginTop: 3,
1313
+ }}>
1314
+ REQUIRED
1315
+ </Text>)}
1184
1316
  </View>
1185
1317
  </View>);
1186
1318
  })}
1187
1319
  </View>
1188
1320
 
1189
- {/* Preferences Section */}
1321
+ {/* UI Preferences Section */}
1190
1322
  <View style={{ marginTop: 8 }}>
1191
1323
  <Text style={{
1192
1324
  fontFamily: AppFonts.interBold,
@@ -1195,7 +1327,7 @@ const NetworkInspector = ({ enabled = true, }) => {
1195
1327
  letterSpacing: 0.6,
1196
1328
  marginBottom: 8,
1197
1329
  }}>
1198
- PREFERENCES
1330
+ UI PREFERENCES
1199
1331
  </Text>
1200
1332
  <View style={{
1201
1333
  backgroundColor: AppColors.primaryLight,
@@ -1269,6 +1401,86 @@ const NetworkInspector = ({ enabled = true, }) => {
1269
1401
  }}/>
1270
1402
  </TouchableScale>
1271
1403
  </View>
1404
+
1405
+ {/* Divider */}
1406
+ <View style={{
1407
+ height: 1,
1408
+ backgroundColor: AppColors.dividerColor,
1409
+ }}/>
1410
+
1411
+ {/* Modal Height */}
1412
+ <View style={{
1413
+ paddingVertical: 12,
1414
+ paddingHorizontal: 14,
1415
+ }}>
1416
+ <View style={{
1417
+ flexDirection: 'row',
1418
+ alignItems: 'center',
1419
+ gap: 8,
1420
+ }}>
1421
+ <View style={{
1422
+ width: 20,
1423
+ height: 20,
1424
+ borderRadius: 6,
1425
+ backgroundColor: AppColors.purpleShade50,
1426
+ borderWidth: 1,
1427
+ borderColor: 'rgba(104,75,155,0.2)',
1428
+ alignItems: 'center',
1429
+ justifyContent: 'center',
1430
+ }}>
1431
+ <ScreenIcon color={AppColors.purple} size={11}/>
1432
+ </View>
1433
+ <View style={{ flex: 1 }}>
1434
+ <Text style={{
1435
+ fontFamily: AppFonts.interBold,
1436
+ fontSize: 13,
1437
+ color: AppColors.primaryBlack,
1438
+ }}>
1439
+ Modal Height
1440
+ </Text>
1441
+ <Text style={{
1442
+ fontFamily: AppFonts.interRegular,
1443
+ fontSize: 11,
1444
+ color: AppColors.grayText,
1445
+ marginTop: 1,
1446
+ }}>
1447
+ Height of the inspector panel relative to the screen
1448
+ </Text>
1449
+ </View>
1450
+ </View>
1451
+
1452
+ {/* Segmented picker */}
1453
+ <View style={{
1454
+ flexDirection: 'row',
1455
+ backgroundColor: AppColors.grayBackground,
1456
+ borderRadius: 8,
1457
+ padding: 2.5,
1458
+ marginTop: 10,
1459
+ borderWidth: 1,
1460
+ borderColor: AppColors.dividerColor,
1461
+ }}>
1462
+ {[50, 70, 90, 100].map(opt => {
1463
+ const isActive = modalHeightPercent === opt;
1464
+ return (<TouchableScale key={opt} onPress={() => setModalHeightPercent(opt)} style={{
1465
+ flex: 1,
1466
+ paddingVertical: 6,
1467
+ alignItems: 'center',
1468
+ borderRadius: 6,
1469
+ backgroundColor: isActive
1470
+ ? AppColors.purple
1471
+ : 'transparent',
1472
+ }}>
1473
+ <Text style={{
1474
+ fontFamily: AppFonts.interBold,
1475
+ fontSize: 11,
1476
+ color: isActive ? '#FFFFFF' : AppColors.grayText,
1477
+ }}>
1478
+ {opt}%
1479
+ </Text>
1480
+ </TouchableScale>);
1481
+ })}
1482
+ </View>
1483
+ </View>
1272
1484
  </View>
1273
1485
  </View>
1274
1486
  </ScrollView>
@@ -1403,11 +1615,13 @@ const NetworkInspector = ({ enabled = true, }) => {
1403
1615
  fontSize: 11,
1404
1616
  color: isActive ? '#FFFFFF' : AppColors.grayText,
1405
1617
  }}>
1406
- {typeof opt === 'number' &&
1407
- opt >= 500 &&
1408
- settingsPage === 'insights'
1409
- ? `${opt}ms`
1410
- : opt}
1618
+ {opts.picker.formatLabel
1619
+ ? opts.picker.formatLabel(opt)
1620
+ : typeof opt === 'number' &&
1621
+ opt >= 500 &&
1622
+ settingsPage === 'insights'
1623
+ ? `${opt}ms`
1624
+ : opt}
1411
1625
  </Text>
1412
1626
  </TouchableScale>);
1413
1627
  })}
@@ -1872,7 +2086,187 @@ const NetworkInspector = ({ enabled = true, }) => {
1872
2086
  e.name === 'page_view' ||
1873
2087
  e.name === 'firebase_screen_class').length;
1874
2088
  const webviewTotal = webViewNavHistory.length;
2089
+ // --- Richer insights metrics ---
2090
+ const slowestTime = durations.length > 0 ? Math.max(...durations) : null;
2091
+ const fastestTime = durations.length > 0 ? Math.min(...durations) : null;
2092
+ const slowCount = durations.filter(d => d >= slowRequestThreshold).length;
2093
+ const status2xx = logs.filter(l => l.status != null && l.status >= 200 && l.status < 300).length;
2094
+ const status3xx = logs.filter(l => l.status != null && l.status >= 300 && l.status < 400).length;
2095
+ const status4xx = logs.filter(l => l.status != null && l.status >= 400 && l.status < 500).length;
2096
+ const status5xx = logs.filter(l => l.status != null && l.status >= 500).length;
2097
+ const totalSignals = apiTotal + logTotal + analyticsTotal + webviewTotal;
2098
+ const totalIssues = apiErrors + logErrors;
2099
+ const activeModules = [
2100
+ tabVisibility.apis,
2101
+ tabVisibility.logs,
2102
+ tabVisibility.analytics,
2103
+ tabVisibility.webview,
2104
+ tabVisibility.redux,
2105
+ ].filter(Boolean).length;
2106
+ // Composite health score: success rate penalised by error volume and slow requests.
2107
+ const healthScore = totalSignals === 0
2108
+ ? 100
2109
+ : Math.max(0, Math.min(100, Math.round(apiSuccessRate -
2110
+ (logErrors > 0 ? Math.min(15, logErrors * 3) : 0) -
2111
+ (slowCount > 0 ? Math.min(10, slowCount * 2) : 0))));
2112
+ const healthColor = healthScore >= 90
2113
+ ? AppColors.greenColor
2114
+ : healthScore >= 70
2115
+ ? AppColors.warningIconGold
2116
+ : AppColors.errorColor;
2117
+ const healthLabel = healthScore >= 90
2118
+ ? 'Healthy'
2119
+ : healthScore >= 70
2120
+ ? 'Needs attention'
2121
+ : 'Degraded';
1875
2122
  return (<View style={styles.dashboardContainer}>
2123
+ {/* Overview hero card */}
2124
+ <View style={{
2125
+ backgroundColor: AppColors.primaryLight,
2126
+ borderRadius: 14,
2127
+ borderWidth: 1,
2128
+ borderColor: AppColors.grayBorderSecondary,
2129
+ padding: 16,
2130
+ marginBottom: 12,
2131
+ }}>
2132
+ <View style={{
2133
+ flexDirection: 'row',
2134
+ alignItems: 'center',
2135
+ gap: 14,
2136
+ }}>
2137
+ {/* Health ring stand-in */}
2138
+ <View style={{
2139
+ width: 64,
2140
+ height: 64,
2141
+ borderRadius: 32,
2142
+ borderWidth: 4,
2143
+ borderColor: healthColor,
2144
+ alignItems: 'center',
2145
+ justifyContent: 'center',
2146
+ backgroundColor: AppColors.purpleShade50,
2147
+ }}>
2148
+ <Text style={{
2149
+ fontFamily: AppFonts.interBold,
2150
+ fontSize: 18,
2151
+ color: healthColor,
2152
+ }}>
2153
+ {healthScore}
2154
+ </Text>
2155
+ <Text style={{
2156
+ fontFamily: AppFonts.interMedium,
2157
+ fontSize: 7.5,
2158
+ color: AppColors.grayTextWeak,
2159
+ letterSpacing: 0.4,
2160
+ }}>
2161
+ HEALTH
2162
+ </Text>
2163
+ </View>
2164
+ <View style={{ flex: 1 }}>
2165
+ <Text style={{
2166
+ fontFamily: AppFonts.interBold,
2167
+ fontSize: 15,
2168
+ color: AppColors.primaryBlack,
2169
+ }}>
2170
+ Session Overview
2171
+ </Text>
2172
+ <View style={{
2173
+ flexDirection: 'row',
2174
+ alignItems: 'center',
2175
+ gap: 6,
2176
+ marginTop: 3,
2177
+ }}>
2178
+ <View style={{
2179
+ width: 7,
2180
+ height: 7,
2181
+ borderRadius: 3.5,
2182
+ backgroundColor: healthColor,
2183
+ }}/>
2184
+ <Text style={{
2185
+ fontFamily: AppFonts.interMedium,
2186
+ fontSize: 11.5,
2187
+ color: healthColor,
2188
+ }}>
2189
+ {healthLabel}
2190
+ </Text>
2191
+ <Text style={{
2192
+ fontFamily: AppFonts.interRegular,
2193
+ fontSize: 11.5,
2194
+ color: AppColors.grayTextWeak,
2195
+ }}>
2196
+ • {activeModules} modules active
2197
+ </Text>
2198
+ </View>
2199
+ </View>
2200
+ </View>
2201
+
2202
+ {/* Quick totals strip */}
2203
+ <View style={{
2204
+ flexDirection: 'row',
2205
+ marginTop: 14,
2206
+ borderTopWidth: 1,
2207
+ borderTopColor: AppColors.dividerColor,
2208
+ paddingTop: 12,
2209
+ }}>
2210
+ <View style={{ flex: 1, alignItems: 'center' }}>
2211
+ <Text style={{
2212
+ fontFamily: AppFonts.interBold,
2213
+ fontSize: 16,
2214
+ color: AppColors.primaryBlack,
2215
+ }}>
2216
+ {totalSignals}
2217
+ </Text>
2218
+ <Text style={{
2219
+ fontFamily: AppFonts.interRegular,
2220
+ fontSize: 10,
2221
+ color: AppColors.grayTextWeak,
2222
+ marginTop: 1,
2223
+ }}>
2224
+ Signals
2225
+ </Text>
2226
+ </View>
2227
+ <View style={{ width: 1, backgroundColor: AppColors.dividerColor }}/>
2228
+ <View style={{ flex: 1, alignItems: 'center' }}>
2229
+ <Text style={{
2230
+ fontFamily: AppFonts.interBold,
2231
+ fontSize: 16,
2232
+ color: totalIssues > 0
2233
+ ? AppColors.errorColor
2234
+ : AppColors.primaryBlack,
2235
+ }}>
2236
+ {totalIssues}
2237
+ </Text>
2238
+ <Text style={{
2239
+ fontFamily: AppFonts.interRegular,
2240
+ fontSize: 10,
2241
+ color: AppColors.grayTextWeak,
2242
+ marginTop: 1,
2243
+ }}>
2244
+ Issues
2245
+ </Text>
2246
+ </View>
2247
+ <View style={{ width: 1, backgroundColor: AppColors.dividerColor }}/>
2248
+ <View style={{ flex: 1, alignItems: 'center' }}>
2249
+ <Text style={{
2250
+ fontFamily: AppFonts.interBold,
2251
+ fontSize: 16,
2252
+ color: slowCount > 0
2253
+ ? AppColors.warningIconGold
2254
+ : AppColors.primaryBlack,
2255
+ }}>
2256
+ {slowCount}
2257
+ </Text>
2258
+ <Text style={{
2259
+ fontFamily: AppFonts.interRegular,
2260
+ fontSize: 10,
2261
+ color: AppColors.grayTextWeak,
2262
+ marginTop: 1,
2263
+ }}>
2264
+ Slow ({slowRequestThreshold}ms+)
2265
+ </Text>
2266
+ </View>
2267
+ </View>
2268
+ </View>
2269
+
1876
2270
  {/* Module 1: APIs */}
1877
2271
  {tabVisibility.apis && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('apis')}>
1878
2272
  <View style={styles.dashboardModuleHeader}>
@@ -1912,9 +2306,75 @@ const NetworkInspector = ({ enabled = true, }) => {
1912
2306
  <Text style={styles.dashboardGridLbl}>Avg Latency</Text>
1913
2307
  </View>
1914
2308
  </View>
1915
- </TouchableScale>)}
1916
2309
 
1917
- {/* Module 2: Logs */}
2310
+ {/* Status-class breakdown + latency range */}
2311
+ <View style={{
2312
+ marginTop: 10,
2313
+ paddingTop: 10,
2314
+ borderTopWidth: 1,
2315
+ borderTopColor: AppColors.dividerColor,
2316
+ flexDirection: 'row',
2317
+ alignItems: 'center',
2318
+ flexWrap: 'wrap',
2319
+ gap: 6,
2320
+ }}>
2321
+ {[
2322
+ { label: '2xx', value: status2xx, color: AppColors.greenColor },
2323
+ { label: '3xx', value: status3xx, color: AppColors.skyBlue },
2324
+ {
2325
+ label: '4xx',
2326
+ value: status4xx,
2327
+ color: AppColors.warningIconGold,
2328
+ },
2329
+ { label: '5xx', value: status5xx, color: AppColors.errorColor },
2330
+ ].map(s => (<View key={s.label} style={{
2331
+ flexDirection: 'row',
2332
+ alignItems: 'center',
2333
+ gap: 4,
2334
+ backgroundColor: AppColors.grayBackground,
2335
+ borderRadius: 6,
2336
+ borderWidth: 1,
2337
+ borderColor: AppColors.dividerColor,
2338
+ paddingHorizontal: 7,
2339
+ paddingVertical: 3,
2340
+ }}>
2341
+ <View style={{
2342
+ width: 6,
2343
+ height: 6,
2344
+ borderRadius: 3,
2345
+ backgroundColor: s.color,
2346
+ }}/>
2347
+ <Text style={{
2348
+ fontFamily: AppFonts.interBold,
2349
+ fontSize: 10,
2350
+ color: AppColors.grayTextStrong,
2351
+ }}>
2352
+ {s.label} {s.value}
2353
+ </Text>
2354
+ </View>))}
2355
+ {slowestTime != null && (<View style={{
2356
+ marginLeft: 'auto',
2357
+ flexDirection: 'row',
2358
+ alignItems: 'center',
2359
+ gap: 4,
2360
+ }}>
2361
+ <Text style={{
2362
+ fontFamily: AppFonts.interRegular,
2363
+ fontSize: 10,
2364
+ color: AppColors.grayTextWeak,
2365
+ }}>
2366
+ Range
2367
+ </Text>
2368
+ <Text style={{
2369
+ fontFamily: AppFonts.interBold,
2370
+ fontSize: 10,
2371
+ color: AppColors.grayTextStrong,
2372
+ }}>
2373
+ {fastestTime}–{slowestTime}ms
2374
+ </Text>
2375
+ </View>)}
2376
+ </View>
2377
+ </TouchableScale>)}
1918
2378
  {tabVisibility.logs && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => switchActiveTab('logs')}>
1919
2379
  <View style={styles.dashboardModuleHeader}>
1920
2380
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
@@ -2118,54 +2578,11 @@ const NetworkInspector = ({ enabled = true, }) => {
2118
2578
  <Text style={styles.emptySub}>Connected store state is empty.</Text>
2119
2579
  </View>);
2120
2580
  }
2121
- // Build hierarchical tree: Store -> Reducers -> Action -> Data
2122
- const lastActionMap = getLastActionForReducer();
2123
- const actionHistory = getActionHistory();
2581
+ // Build hierarchical tree: Store -> Reducers -> Action -> Data.
2582
+ // These come from component state (refreshed on every dispatch) so the view stays live.
2583
+ const lastActionMap = reduxLastActionMap;
2584
+ const actionHistory = reduxActionHistory;
2124
2585
  return (<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingBottom: 24 }}>
2125
- {/* Top Summary Card */}
2126
- <View style={{
2127
- backgroundColor: AppColors.primaryLight,
2128
- borderRadius: 12,
2129
- borderWidth: 1,
2130
- borderColor: AppColors.grayBorderSecondary,
2131
- padding: 14,
2132
- marginHorizontal: 16,
2133
- marginTop: 12,
2134
- marginBottom: 12,
2135
- flexDirection: 'row',
2136
- alignItems: 'center',
2137
- gap: 12,
2138
- }}>
2139
- <View style={{
2140
- width: 44,
2141
- height: 44,
2142
- borderRadius: 10,
2143
- backgroundColor: AppColors.purpleShade50,
2144
- alignItems: 'center',
2145
- justifyContent: 'center',
2146
- }}>
2147
- <TerminalIcon color={AppColors.purple} size={20}/>
2148
- </View>
2149
- <View style={{ flex: 1 }}>
2150
- <Text style={{
2151
- fontFamily: AppFonts.interBold,
2152
- fontSize: 13,
2153
- color: AppColors.primaryBlack,
2154
- }}>
2155
- Redux Store Snapshot
2156
- </Text>
2157
- <Text style={{
2158
- fontFamily: AppFonts.interRegular,
2159
- fontSize: 11,
2160
- color: AppColors.grayText,
2161
- marginTop: 2,
2162
- }}>
2163
- Total size: {getSize(reduxState)} • {reducerKeys.length} Reducers
2164
- </Text>
2165
- </View>
2166
- <CopyButton value={() => reduxState} label="Overall Store"/>
2167
- </View>
2168
-
2169
2586
  {/* Tab View Selection Segments */}
2170
2587
  <View style={{
2171
2588
  flexDirection: 'row',
@@ -2173,6 +2590,7 @@ const NetworkInspector = ({ enabled = true, }) => {
2173
2590
  borderRadius: 10,
2174
2591
  padding: 3,
2175
2592
  marginHorizontal: 16,
2593
+ marginTop: 12,
2176
2594
  marginBottom: 12,
2177
2595
  borderWidth: 1,
2178
2596
  borderColor: AppColors.dividerColor,
@@ -2190,15 +2608,46 @@ const NetworkInspector = ({ enabled = true, }) => {
2190
2608
  ? AppColors.purple
2191
2609
  : 'transparent',
2192
2610
  }}>
2193
- <Text style={{
2611
+ <View style={{
2612
+ flexDirection: 'row',
2613
+ alignItems: 'center',
2614
+ justifyContent: 'center',
2615
+ gap: 6,
2616
+ }}>
2617
+ <ReduxTimelineIcon color={reduxActiveSubTab === 'timeline'
2618
+ ? '#FFFFFF'
2619
+ : AppColors.grayText} size={13}/>
2620
+ <Text style={{
2194
2621
  fontFamily: AppFonts.interBold,
2195
2622
  fontSize: 11,
2196
2623
  color: reduxActiveSubTab === 'timeline'
2197
2624
  ? '#FFFFFF'
2198
2625
  : AppColors.grayText,
2199
2626
  }}>
2200
- Action Timeline
2201
- </Text>
2627
+ Action Timeline
2628
+ </Text>
2629
+ <View style={{
2630
+ minWidth: 18,
2631
+ paddingHorizontal: 5,
2632
+ height: 16,
2633
+ borderRadius: 8,
2634
+ alignItems: 'center',
2635
+ justifyContent: 'center',
2636
+ backgroundColor: reduxActiveSubTab === 'timeline'
2637
+ ? 'rgba(255,255,255,0.28)'
2638
+ : AppColors.dividerColor,
2639
+ }}>
2640
+ <Text style={{
2641
+ fontFamily: AppFonts.interBold,
2642
+ fontSize: 9,
2643
+ color: reduxActiveSubTab === 'timeline'
2644
+ ? '#FFFFFF'
2645
+ : AppColors.grayText,
2646
+ }}>
2647
+ {actionHistory.length}
2648
+ </Text>
2649
+ </View>
2650
+ </View>
2202
2651
  </TouchableOpacity>
2203
2652
  <TouchableOpacity onPress={() => {
2204
2653
  animateNextLayout();
@@ -2211,13 +2660,44 @@ const NetworkInspector = ({ enabled = true, }) => {
2211
2660
  borderRadius: 8,
2212
2661
  backgroundColor: reduxActiveSubTab === 'tree' ? AppColors.purple : 'transparent',
2213
2662
  }}>
2214
- <Text style={{
2663
+ <View style={{
2664
+ flexDirection: 'row',
2665
+ alignItems: 'center',
2666
+ justifyContent: 'center',
2667
+ gap: 6,
2668
+ }}>
2669
+ <ReduxTreeIcon color={reduxActiveSubTab === 'tree' ? '#FFFFFF' : AppColors.grayText} size={13}/>
2670
+ <Text style={{
2215
2671
  fontFamily: AppFonts.interBold,
2216
2672
  fontSize: 11,
2217
- color: reduxActiveSubTab === 'tree' ? '#FFFFFF' : AppColors.grayText,
2673
+ color: reduxActiveSubTab === 'tree'
2674
+ ? '#FFFFFF'
2675
+ : AppColors.grayText,
2218
2676
  }}>
2219
- 🏪 Store Tree
2220
- </Text>
2677
+ Store Tree
2678
+ </Text>
2679
+ <View style={{
2680
+ minWidth: 18,
2681
+ paddingHorizontal: 5,
2682
+ height: 16,
2683
+ borderRadius: 8,
2684
+ alignItems: 'center',
2685
+ justifyContent: 'center',
2686
+ backgroundColor: reduxActiveSubTab === 'tree'
2687
+ ? 'rgba(255,255,255,0.28)'
2688
+ : AppColors.dividerColor,
2689
+ }}>
2690
+ <Text style={{
2691
+ fontFamily: AppFonts.interBold,
2692
+ fontSize: 9,
2693
+ color: reduxActiveSubTab === 'tree'
2694
+ ? '#FFFFFF'
2695
+ : AppColors.grayText,
2696
+ }}>
2697
+ {reducerKeys.length}
2698
+ </Text>
2699
+ </View>
2700
+ </View>
2221
2701
  </TouchableOpacity>
2222
2702
  </View>
2223
2703
 
@@ -2263,27 +2743,36 @@ const NetworkInspector = ({ enabled = true, }) => {
2263
2743
  };
2264
2744
  return (<>
2265
2745
  {hasNavigationContext && (<NavigationTracker onStateChange={setNavState}/>)}
2266
- <TouchableScale style={styles.fabWrapper} onPress={() => setVisible(true)} hitSlop={10}>
2267
- <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
2268
- <BrandCircleIcon size={62}/>
2269
- {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
2746
+ <Animated.View style={[styles.fabWrapper, { transform: fabPan.getTranslateTransform() }]} {...fabPanResponder.panHandlers}>
2747
+ <TouchableScale style={{ alignItems: 'center', justifyContent: 'center' }} onPress={() => {
2748
+ if (fabDraggedRef.current)
2749
+ return;
2750
+ setVisible(true);
2751
+ }} hitSlop={10}>
2752
+ <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
2753
+ <BrandCircleIcon size={62}/>
2754
+ {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
2270
2755
  styles.fabBadge,
2271
2756
  hasErrors ? styles.fabBadgeError : styles.fabBadgeNormal,
2272
2757
  { transform: [{ scale: badgeAnim }] },
2273
2758
  ]}>
2274
- <Text style={styles.fabBadgeText}>
2275
- {logs.length + analyticsEvents.length > 99
2759
+ <Text style={styles.fabBadgeText}>
2760
+ {logs.length + analyticsEvents.length > 99
2276
2761
  ? '99+'
2277
2762
  : logs.length + analyticsEvents.length}
2278
- </Text>
2279
- </Animated.View>)}
2280
- </TouchableScale>
2763
+ </Text>
2764
+ </Animated.View>)}
2765
+ </TouchableScale>
2766
+ </Animated.View>
2281
2767
 
2282
2768
  <Modal visible={visible} animationType="slide" transparent>
2283
2769
  {visible && (<ErrorBoundary onClose={closeModal}>
2284
2770
  <View style={styles.modalBackdrop}>
2285
2771
  <Pressable style={styles.modalBackdropPressable} onPress={closeModal}/>
2286
- <View style={styles.modalContentCard}>
2772
+ <View style={[
2773
+ styles.modalContentCard,
2774
+ { height: `${modalHeightPercent}%` },
2775
+ ]}>
2287
2776
  <StatusBar translucent backgroundColor="transparent" barStyle="light-content"/>
2288
2777
 
2289
2778
  <LinearGradient colors={[AppColors.purple, '#6B4EFF']} style={styles.headerGradient}>
@@ -2304,10 +2793,27 @@ const NetworkInspector = ({ enabled = true, }) => {
2304
2793
  setSelectedEvent(null);
2305
2794
  });
2306
2795
  }} hitSlop={15} style={[
2307
- styles.iconBtnMinimal,
2796
+ {
2797
+ width: 38,
2798
+ height: 38,
2799
+ borderRadius: 19,
2800
+ alignItems: 'center',
2801
+ justifyContent: 'center',
2802
+ backgroundColor: 'rgba(255,255,255,0.18)',
2803
+ borderWidth: 1,
2804
+ borderColor: 'rgba(255,255,255,0.30)',
2805
+ },
2308
2806
  selected == null &&
2309
2807
  selectedEvent == null && { display: 'none' },
2310
2808
  ]}>
2809
+ {/* Soft outer glow to fake a blurred circle */}
2810
+ <View style={{
2811
+ position: 'absolute',
2812
+ width: 48,
2813
+ height: 48,
2814
+ borderRadius: 24,
2815
+ backgroundColor: 'rgba(255,255,255,0.10)',
2816
+ }}/>
2311
2817
  <WhiteBackNavigation />
2312
2818
  </TouchableScale>
2313
2819
 
@@ -2318,8 +2824,8 @@ const NetworkInspector = ({ enabled = true, }) => {
2318
2824
  flex: 1,
2319
2825
  }}>
2320
2826
  <View style={{
2321
- width: 42,
2322
- height: 42,
2827
+ width: 50,
2828
+ height: 50,
2323
2829
  borderRadius: 10,
2324
2830
  backgroundColor: 'rgba(255,255,255,0.13)',
2325
2831
  borderWidth: 1.5,
@@ -2331,37 +2837,92 @@ const NetworkInspector = ({ enabled = true, }) => {
2331
2837
  shadowRadius: 4,
2332
2838
  shadowOffset: { width: 0, height: 2 },
2333
2839
  }}>
2334
- <BrandSquareIcon size={36}/>
2840
+ <BrandSquareIcon size={45}/>
2335
2841
  </View>
2336
2842
  <View style={{ gap: 3 }}>
2337
- <Text style={[
2338
- styles.headerTitle,
2339
- { fontSize: 17, letterSpacing: 0.2 },
2340
- ]}>
2843
+ <Text style={[styles.headerTitle]}>
2341
2844
  RN InApp Inspector
2342
2845
  </Text>
2343
2846
  <View style={{
2344
2847
  flexDirection: 'row',
2345
2848
  alignItems: 'center',
2346
- gap: 5,
2849
+ gap: 6,
2347
2850
  }}>
2348
- <Animated.View style={{
2349
- width: 6,
2350
- height: 6,
2351
- borderRadius: 3,
2352
- backgroundColor: '#4ADE80',
2353
- opacity: activePulseAnim,
2354
- }}/>
2355
- <Text style={{
2851
+ {/* OS chip */}
2852
+ <View style={{
2853
+ flexDirection: 'row',
2854
+ alignItems: 'center',
2855
+ borderRadius: 6,
2856
+ overflow: 'hidden',
2857
+ borderWidth: 1,
2858
+ borderColor: 'rgba(255,255,255,0.18)',
2859
+ }}>
2860
+ <View style={{
2861
+ paddingHorizontal: 5,
2862
+ paddingVertical: 2,
2863
+ backgroundColor: 'rgba(255,255,255,0.28)',
2864
+ }}>
2865
+ <Text style={{
2866
+ fontFamily: AppFonts.interBold,
2867
+ fontSize: 9,
2868
+ color: '#FFFFFF',
2869
+ letterSpacing: 0.3,
2870
+ }}>
2871
+ {Platform.OS === 'ios' ? 'iOS' : 'Android'}
2872
+ </Text>
2873
+ </View>
2874
+ <View style={{
2875
+ paddingHorizontal: 5,
2876
+ paddingVertical: 2,
2877
+ backgroundColor: 'rgba(255,255,255,0.12)',
2878
+ }}>
2879
+ <Text style={{
2356
2880
  fontFamily: AppFonts.interMedium,
2357
- fontSize: 10,
2358
- color: 'rgba(255,255,255,0.78)',
2881
+ fontSize: 9.5,
2882
+ color: 'rgba(255,255,255,0.92)',
2883
+ }}>
2884
+ {String(Platform.Version)}
2885
+ </Text>
2886
+ </View>
2887
+ </View>
2888
+
2889
+ {/* npm chip */}
2890
+ <View style={{
2891
+ flexDirection: 'row',
2892
+ alignItems: 'center',
2893
+ borderRadius: 6,
2894
+ overflow: 'hidden',
2895
+ borderWidth: 1,
2896
+ borderColor: 'rgba(255,255,255,0.18)',
2897
+ }}>
2898
+ <View style={{
2899
+ paddingHorizontal: 5,
2900
+ paddingVertical: 2,
2901
+ backgroundColor: 'rgba(255,255,255,0.28)',
2902
+ }}>
2903
+ <Text style={{
2904
+ fontFamily: AppFonts.interBold,
2905
+ fontSize: 9,
2906
+ color: '#FFFFFF',
2359
2907
  letterSpacing: 0.3,
2360
2908
  }}>
2361
- Active •{' '}
2362
- {Platform.OS === 'ios' ? 'iOS' : 'Android'}{' '}
2363
- (v1.0.13)
2364
- </Text>
2909
+ npm
2910
+ </Text>
2911
+ </View>
2912
+ <View style={{
2913
+ paddingHorizontal: 5,
2914
+ paddingVertical: 2,
2915
+ backgroundColor: 'rgba(255,255,255,0.12)',
2916
+ }}>
2917
+ <Text style={{
2918
+ fontFamily: AppFonts.interMedium,
2919
+ fontSize: 9.5,
2920
+ color: 'rgba(255,255,255,0.92)',
2921
+ }}>
2922
+ v{LIB_VERSION}
2923
+ </Text>
2924
+ </View>
2925
+ </View>
2365
2926
  </View>
2366
2927
  </View>
2367
2928
  </View>) : null}
@@ -2386,21 +2947,48 @@ const NetworkInspector = ({ enabled = true, }) => {
2386
2947
  </Text>
2387
2948
  </View>
2388
2949
  <View style={styles.headerDetailSubRow}>
2389
- <View style={[
2950
+ <View style={{
2951
+ flexDirection: 'row',
2952
+ alignItems: 'center',
2953
+ gap: 5,
2954
+ paddingHorizontal: 8,
2955
+ paddingVertical: 3,
2956
+ borderRadius: 20,
2957
+ backgroundColor: `${getStatusColor(selected.status)}26`,
2958
+ borderWidth: 1,
2959
+ borderColor: `${getStatusColor(selected.status)}55`,
2960
+ }}>
2961
+ <View style={[
2390
2962
  styles.headerStatusDot,
2391
2963
  {
2392
2964
  backgroundColor: getStatusColor(selected.status),
2393
2965
  },
2394
2966
  ]}/>
2395
- <Text style={styles.headerSubTitle}>
2396
- {selected.status === 0
2967
+ <Text style={[
2968
+ styles.headerSubTitle,
2969
+ { fontFamily: AppFonts.interBold },
2970
+ ]}>
2971
+ {selected.status === 0
2397
2972
  ? 'Failed'
2398
- : selected.status ?? 'Pending'}{' '}
2399
- •{' '}
2400
- {selected.duration != null
2973
+ : selected.status ?? 'Pending'}
2974
+ </Text>
2975
+ </View>
2976
+ <View style={{
2977
+ flexDirection: 'row',
2978
+ alignItems: 'center',
2979
+ gap: 4,
2980
+ paddingHorizontal: 8,
2981
+ paddingVertical: 3,
2982
+ borderRadius: 20,
2983
+ backgroundColor: 'rgba(255,255,255,0.16)',
2984
+ }}>
2985
+ <ClockIcon color="#FFFFFF" size={11}/>
2986
+ <Text style={styles.headerSubTitle}>
2987
+ {selected.duration != null
2401
2988
  ? `${selected.duration}ms`
2402
- : '-'}
2403
- </Text>
2989
+ : ''}
2990
+ </Text>
2991
+ </View>
2404
2992
  </View>
2405
2993
  </View>) : selectedEvent != null ? (<View style={styles.headerDetailCenter}>
2406
2994
  <View style={styles.headerDetailRow}>
@@ -2443,7 +3031,50 @@ const NetworkInspector = ({ enabled = true, }) => {
2443
3031
  </View>) : null}
2444
3032
  </View>
2445
3033
 
2446
- <View style={styles.headerRight}>
3034
+ <View style={[
3035
+ styles.headerRight,
3036
+ selected == null &&
3037
+ selectedEvent == null && {
3038
+ flexShrink: 0,
3039
+ minWidth: 116,
3040
+ },
3041
+ ]}>
3042
+ {selected == null && selectedEvent == null && (<TouchableScale onPress={() => {
3043
+ Alert.alert('Clear Everything', 'This clears all tabs — APIs, Logs, Analytics, WebView and Redux timeline. Continue?', [
3044
+ { text: 'Cancel', style: 'cancel' },
3045
+ {
3046
+ text: 'Clear All',
3047
+ onPress: runClearAllWithAnimation,
3048
+ style: 'destructive',
3049
+ },
3050
+ ]);
3051
+ }} hitSlop={15} style={[
3052
+ styles.closeButtonSquare,
3053
+ {
3054
+ marginRight: 8,
3055
+ backgroundColor: 'rgba(255,255,255,0.15)',
3056
+ },
3057
+ ]}>
3058
+ <Animated.View style={{
3059
+ transform: [
3060
+ {
3061
+ rotate: clearAnim.interpolate({
3062
+ inputRange: [0, 1],
3063
+ outputRange: ['0deg', '-25deg'],
3064
+ }),
3065
+ },
3066
+ {
3067
+ scale: clearAnim.interpolate({
3068
+ inputRange: [0, 0.5, 1],
3069
+ outputRange: [1, 1.25, 1],
3070
+ }),
3071
+ },
3072
+ ],
3073
+ }}>
3074
+ <TrashIcon color="#FFFFFF" size={15}/>
3075
+ </Animated.View>
3076
+ </TouchableScale>)}
3077
+
2447
3078
  {selected == null && selectedEvent == null && (<TouchableScale onPress={() => setSettingsPage('main')} hitSlop={15} style={[
2448
3079
  styles.closeButtonSquare,
2449
3080
  {
@@ -2778,50 +3409,53 @@ const NetworkInspector = ({ enabled = true, }) => {
2778
3409
  filteredAnalyticsEvents.length === 0 && {
2779
3410
  flexGrow: 1,
2780
3411
  },
2781
- ]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (<FlatList data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
2782
- <View style={styles.toolbarRow}>
2783
- <View style={styles.searchContainer}>
2784
- <SearchIcon color={AppColors.grayTextWeak} size={16}/>
2785
- <TextInput placeholder="Search endpoints..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
2786
- {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10} style={styles.clearBtn}>
2787
- <ClearIcon color={AppColors.grayTextWeak} size={14}/>
2788
- </Pressable>)}
2789
- </View>
3412
+ ]} 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 }}>
3414
+ <View style={styles.toolbarRow}>
3415
+ <View style={styles.searchContainer}>
3416
+ <SearchIcon color={AppColors.grayTextWeak} size={16}/>
3417
+ <TextInput placeholder="Search endpoints..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
3418
+ {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10} style={styles.clearBtn}>
3419
+ <ClearIcon color={AppColors.grayTextWeak} size={14}/>
3420
+ </Pressable>)}
3421
+ </View>
2790
3422
 
2791
- <View style={styles.toolbarRight}>
2792
- <TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
2793
- <TrashIcon color={AppColors.grayTextStrong} size={18}/>
2794
- {selectedLogs.size > 0 && (<View style={styles.trashBadge}>
2795
- <Text style={styles.trashBadgeText}>
2796
- {selectedLogs.size}
2797
- </Text>
2798
- </View>)}
2799
- </TouchableScale>
3423
+ <View style={styles.toolbarRight}>
3424
+ <TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
3425
+ <TrashIcon color={AppColors.grayTextStrong} size={18}/>
3426
+ {selectedLogs.size > 0 && (<View style={styles.trashBadge}>
3427
+ <Text style={styles.trashBadgeText}>
3428
+ {selectedLogs.size}
3429
+ </Text>
3430
+ </View>)}
3431
+ </TouchableScale>
2800
3432
 
2801
- <TouchableScale style={styles.toolbarBtn} onPress={() => setSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
2802
- <SortArrowIcon direction={sortOrder === 'newest' ? 'down' : 'up'} color={AppColors.grayTextStrong} size={18}/>
2803
- </TouchableScale>
3433
+ <TouchableScale style={styles.toolbarBtn} onPress={() => setSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
3434
+ <SortArrowIcon direction={sortOrder === 'newest' ? 'down' : 'up'} color={AppColors.grayTextStrong} size={18}/>
3435
+ </TouchableScale>
2804
3436
 
2805
- <TouchableScale style={[
3437
+ <TouchableScale style={[
2806
3438
  styles.toolbarBtn,
2807
3439
  filtersAccordion.isOpen &&
2808
3440
  styles.toolbarBtnActive,
2809
3441
  ]} onPress={filtersAccordion.toggleOpen} hitSlop={10}>
2810
- <FilterIcon color={filtersAccordion.isOpen
3442
+ <FilterIcon color={filtersAccordion.isOpen
2811
3443
  ? AppColors.purple
2812
3444
  : AppColors.grayTextStrong} size={18}/>
2813
- </TouchableScale>
3445
+ </TouchableScale>
3446
+ </View>
2814
3447
  </View>
2815
- </View>
2816
3448
 
2817
- <Animated.View style={[
3449
+ <Animated.View style={[
2818
3450
  filtersAccordion.bodyStyle,
2819
3451
  { overflow: 'hidden' },
2820
3452
  ]}>
2821
- <View style={styles.filtersContainer}>
2822
- <Text style={styles.filtersHeading}>STATUS</Text>
2823
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
2824
- {STATUS_FILTERS.map(filter => {
3453
+ <View style={styles.filtersContainer}>
3454
+ <Text style={styles.filtersHeading}>
3455
+ STATUS
3456
+ </Text>
3457
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
3458
+ {STATUS_FILTERS.map(filter => {
2825
3459
  const isAll = filter === 'ALL';
2826
3460
  const active = isAll
2827
3461
  ? statusFilters.size === 0
@@ -2840,38 +3474,38 @@ const NetworkInspector = ({ enabled = true, }) => {
2840
3474
  });
2841
3475
  }
2842
3476
  }} hitSlop={10}>
2843
- {active ? (<View style={[
3477
+ {active ? (<View style={[
2844
3478
  styles.statusFilterChip,
2845
3479
  styles.statusFilterActive,
2846
3480
  { overflow: 'hidden' },
2847
3481
  ]}>
2848
- <LinearGradient colors={[
3482
+ <LinearGradient colors={[
2849
3483
  AppColors.purpleShade50,
2850
3484
  '#EAE5FF',
2851
3485
  ]} style={StyleSheet.absoluteFill} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}/>
2852
- <Text style={[
3486
+ <Text style={[
2853
3487
  styles.statusFilterText,
2854
3488
  { color: AppColors.purple },
2855
3489
  ]}>
2856
- {filter}
2857
- </Text>
2858
- </View>) : (<View style={styles.statusFilterChip}>
2859
- <Text style={styles.statusFilterText}>
2860
- {filter}
2861
- </Text>
2862
- </View>)}
2863
- </TouchableScale>);
3490
+ {filter}
3491
+ </Text>
3492
+ </View>) : (<View style={styles.statusFilterChip}>
3493
+ <Text style={styles.statusFilterText}>
3494
+ {filter}
3495
+ </Text>
3496
+ </View>)}
3497
+ </TouchableScale>);
2864
3498
  })}
2865
- </ScrollView>
3499
+ </ScrollView>
2866
3500
 
2867
- <Text style={[
3501
+ <Text style={[
2868
3502
  styles.filtersHeading,
2869
3503
  { marginTop: 16 },
2870
3504
  ]}>
2871
- METHOD
2872
- </Text>
2873
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
2874
- {availableMethods.map(filter => {
3505
+ METHOD
3506
+ </Text>
3507
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
3508
+ {availableMethods.map(filter => {
2875
3509
  const isAll = filter === 'ALL';
2876
3510
  const active = isAll
2877
3511
  ? methodFilters.size === 0
@@ -2890,43 +3524,55 @@ const NetworkInspector = ({ enabled = true, }) => {
2890
3524
  });
2891
3525
  }
2892
3526
  }} hitSlop={10}>
2893
- {active ? (<View style={[
3527
+ {active ? (<View style={[
2894
3528
  styles.statusFilterChip,
2895
3529
  styles.statusFilterActive,
2896
3530
  { overflow: 'hidden' },
2897
3531
  ]}>
2898
- <LinearGradient colors={[
3532
+ <LinearGradient colors={[
2899
3533
  AppColors.purpleShade50,
2900
3534
  '#EAE5FF',
2901
3535
  ]} style={StyleSheet.absoluteFill} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}/>
2902
- <Text style={[
3536
+ <Text style={[
2903
3537
  styles.statusFilterText,
2904
3538
  { color: AppColors.purple },
2905
3539
  ]}>
2906
- {filter}
2907
- </Text>
2908
- </View>) : (<View style={styles.statusFilterChip}>
2909
- <Text style={styles.statusFilterText}>
2910
- {filter}
2911
- </Text>
2912
- </View>)}
2913
- </TouchableScale>);
3540
+ {filter}
3541
+ </Text>
3542
+ </View>) : (<View style={styles.statusFilterChip}>
3543
+ <Text style={styles.statusFilterText}>
3544
+ {filter}
3545
+ </Text>
3546
+ </View>)}
3547
+ </TouchableScale>);
2914
3548
  })}
2915
- </ScrollView>
2916
- </View>
2917
- </Animated.View>
3549
+ </ScrollView>
3550
+ </View>
3551
+ </Animated.View>
2918
3552
 
2919
- {(search ||
3553
+ {(search ||
2920
3554
  statusFilters.size > 0 ||
2921
3555
  methodFilters.size > 0) && (<Text style={styles.resultCount}>
2922
- {filteredLogs.length === logs.length
3556
+ {filteredLogs.length === logs.length
2923
3557
  ? `${logs.length} requests`
2924
3558
  : `${filteredLogs.length} of ${logs.length} filtered requests`}
2925
- </Text>)}
2926
- </View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} contentContainerStyle={[
3559
+ </Text>)}
3560
+ </View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} contentContainerStyle={[
2927
3561
  styles.listContent,
2928
3562
  filteredLogs.length === 0 && { flexGrow: 1 },
2929
- ]} keyboardShouldPersistTaps="handled"/>) : activeTab === 'logs' ? (<View style={{ flex: 1 }}>
3563
+ ]} 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>)}
3575
+ </View>) : activeTab === 'logs' ? (<View style={{ flex: 1 }}>
2930
3576
  <View style={{
2931
3577
  backgroundColor: '#FFFFFF',
2932
3578
  borderBottomWidth: 1,
@@ -2946,6 +3592,9 @@ const NetworkInspector = ({ enabled = true, }) => {
2946
3592
  </View>
2947
3593
 
2948
3594
  <View style={styles.toolbarRight}>
3595
+ <TouchableScale style={styles.toolbarBtn} onPress={() => setLogSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
3596
+ <SortArrowIcon color={AppColors.grayTextStrong} size={18} direction={logSortOrder === 'newest' ? 'down' : 'up'}/>
3597
+ </TouchableScale>
2949
3598
  <TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
2950
3599
  <TrashIcon color={AppColors.grayTextStrong} size={18}/>
2951
3600
  </TouchableScale>
@@ -4185,7 +4834,7 @@ const NetworkInspector = ({ enabled = true, }) => {
4185
4834
  if (!resExpanded && !showResDiff)
4186
4835
  setResExpanded(true);
4187
4836
  }}/>
4188
- {showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : (<JsonViewer data={selected.response} search={detailSearch} forceOpen={resExpanded}/>)}
4837
+ {showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : (<JsonViewer data={selected.response} search={detailSearch} forceOpen={resExpanded} wrap/>)}
4189
4838
  </View>
4190
4839
  </>)}
4191
4840
  </ScrollView>