react-native-inapp-inspector 1.0.9 → 1.0.10

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 (47) hide show
  1. package/README.md +5 -6
  2. package/dist/commonjs/components/AnalyticsDetail.js +28 -23
  3. package/dist/commonjs/components/AnalyticsEventCard.js +9 -9
  4. package/dist/commonjs/components/BrandSquareIcon.d.ts +5 -0
  5. package/dist/commonjs/components/BrandSquareIcon.js +180 -0
  6. package/dist/commonjs/components/CodeSnippet.js +32 -24
  7. package/dist/commonjs/components/ConsoleLogCard.js +127 -70
  8. package/dist/commonjs/components/JsonViewer.d.ts +2 -1
  9. package/dist/commonjs/components/JsonViewer.js +2 -2
  10. package/dist/commonjs/components/MetaAccordion.d.ts +1 -1
  11. package/dist/commonjs/components/MetaAccordion.js +45 -2
  12. package/dist/commonjs/components/NetworkIcons.d.ts +7 -0
  13. package/dist/commonjs/components/NetworkIcons.js +42 -1
  14. package/dist/commonjs/components/ReduxTreeView.d.ts +6 -0
  15. package/dist/commonjs/components/ReduxTreeView.js +403 -0
  16. package/dist/commonjs/components/TouchableScale.js +15 -1
  17. package/dist/commonjs/components/TreeNode.js +3 -3
  18. package/dist/commonjs/customHooks/reduxLogger.d.ts +4 -0
  19. package/dist/commonjs/customHooks/reduxLogger.js +48 -2
  20. package/dist/commonjs/index.js +1520 -504
  21. package/dist/commonjs/styles/index.d.ts +11 -1
  22. package/dist/commonjs/styles/index.js +19 -9
  23. package/dist/commonjs/types/index.d.ts +4 -0
  24. package/dist/esm/components/AnalyticsDetail.js +28 -23
  25. package/dist/esm/components/AnalyticsEventCard.js +9 -9
  26. package/dist/esm/components/BrandSquareIcon.d.ts +5 -0
  27. package/dist/esm/components/BrandSquareIcon.js +140 -0
  28. package/dist/esm/components/CodeSnippet.js +32 -24
  29. package/dist/esm/components/ConsoleLogCard.js +127 -70
  30. package/dist/esm/components/JsonViewer.d.ts +2 -1
  31. package/dist/esm/components/JsonViewer.js +2 -2
  32. package/dist/esm/components/MetaAccordion.d.ts +1 -1
  33. package/dist/esm/components/MetaAccordion.js +46 -3
  34. package/dist/esm/components/NetworkIcons.d.ts +7 -0
  35. package/dist/esm/components/NetworkIcons.js +34 -0
  36. package/dist/esm/components/ReduxTreeView.d.ts +6 -0
  37. package/dist/esm/components/ReduxTreeView.js +366 -0
  38. package/dist/esm/components/TouchableScale.js +16 -2
  39. package/dist/esm/components/TreeNode.js +3 -3
  40. package/dist/esm/customHooks/reduxLogger.d.ts +4 -0
  41. package/dist/esm/customHooks/reduxLogger.js +43 -1
  42. package/dist/esm/index.js +1523 -507
  43. package/dist/esm/styles/index.d.ts +11 -1
  44. package/dist/esm/styles/index.js +19 -9
  45. package/dist/esm/types/index.d.ts +4 -0
  46. package/example/App.tsx +46 -0
  47. package/package.json +7 -5
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, Modal, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, } from 'react-native';
2
+ import { Alert, Animated, StyleSheet, FlatList, Modal, Platform, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, 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,6 +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 } from './components/ReduxTreeView';
14
15
  import DomainHeader from './components/DomainHeader';
15
16
  import DiffViewer from './components/DiffViewer';
16
17
  import LogCard from './components/LogCard';
@@ -22,7 +23,7 @@ import CodeSnippet from './components/CodeSnippet';
22
23
  // Helpers
23
24
  import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
24
25
  // Assets
25
- import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, ChevronIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, } from './components/NetworkIcons';
26
+ 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';
26
27
  import ErrorBoundary from './components/ErrorBoundary';
27
28
  // Stylesheet
28
29
  import { AppColors } from './styles/AppColors';
@@ -36,9 +37,68 @@ import { IGNORED_LOG_PREFIXES } from './customHooks/logFilters';
36
37
  import { subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
37
38
  import AnalyticsEventCard, { getEventColor, } from './components/AnalyticsEventCard';
38
39
  import AnalyticsDetail from './components/AnalyticsDetail';
39
- // WebView
40
40
  import { getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
41
- import { getReduxState, subscribeReduxState, } from './customHooks/reduxLogger';
41
+ let OriginalWebView = null;
42
+ try {
43
+ const RNWebView = require('react-native-webview');
44
+ OriginalWebView = RNWebView.WebView || RNWebView.default;
45
+ }
46
+ catch (e) {
47
+ // Silent fail
48
+ }
49
+ const previewInspectScript = `
50
+ (function() {
51
+ var style = document.createElement('style');
52
+ style.innerHTML = '* { cursor: pointer !important; }';
53
+ document.head.appendChild(style);
54
+
55
+ document.addEventListener('click', function(e) {
56
+ var el = e.target;
57
+ if (!el) return;
58
+
59
+ e.preventDefault();
60
+ e.stopPropagation();
61
+
62
+ var oldOutline = el.style.outline;
63
+ var oldTransition = el.style.transition;
64
+ el.style.transition = 'outline 0.15s ease';
65
+ el.style.outline = '3px solid #684B9B';
66
+ setTimeout(function() {
67
+ el.style.outline = oldOutline;
68
+ el.style.transition = oldTransition;
69
+ }, 600);
70
+
71
+ var tagName = el.tagName.toLowerCase();
72
+ var searchStr = '<' + tagName;
73
+
74
+ if (el.id) {
75
+ searchStr += ' id="' + el.id + '"';
76
+ } else if (el.className && typeof el.className === 'string') {
77
+ var firstClass = el.className.trim().split(/\\s+/)[0];
78
+ if (firstClass) {
79
+ searchStr += ' class="' + firstClass;
80
+ }
81
+ } else {
82
+ var text = (el.textContent || '').trim().substring(0, 25);
83
+ if (text) {
84
+ searchStr = text;
85
+ }
86
+ }
87
+
88
+ if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
89
+ window.ReactNativeWebView.postMessage(JSON.stringify({
90
+ type: 'preview-inspect',
91
+ tagName: tagName,
92
+ id: el.id || '',
93
+ className: typeof el.className === 'string' ? el.className : '',
94
+ searchStr: searchStr
95
+ }));
96
+ }
97
+ }, true);
98
+ })();
99
+ true;
100
+ `;
101
+ import { getReduxState, subscribeReduxState, setReduxAutoRefresh, getLastActionForReducer, } from './customHooks/reduxLogger';
42
102
  import { METHOD_COLORS, STATUS_FILTERS } from './constants';
43
103
  const NavigationTracker = ({ onStateChange }) => {
44
104
  const navState = useNavigationState(state => state);
@@ -47,7 +107,7 @@ const NavigationTracker = ({ onStateChange }) => {
47
107
  }, [navState, onStateChange]);
48
108
  return null;
49
109
  };
50
- const NetworkInspector = () => {
110
+ const NetworkInspector = ({ enabled = true }) => {
51
111
  const [isDark, setIsDark] = useState(false);
52
112
  const [reduxState, setReduxState] = useState(null);
53
113
  const [expandedReducers, setExpandedReducers] = useState({});
@@ -59,10 +119,21 @@ const NetworkInspector = () => {
59
119
  const [search, setSearch] = useState('');
60
120
  const [detailSearch, setDetailSearch] = useState('');
61
121
  const [reduxSearch, setReduxSearch] = useState('');
62
- const [apiDetailActiveTab, setApiDetailActiveTab] = useState('metadata');
122
+ const [apiDetailActiveTab, setApiDetailActiveTab] = useState('response');
123
+ useEffect(() => {
124
+ if (enabled && visible) {
125
+ LogBox.ignoreAllLogs(true);
126
+ }
127
+ else {
128
+ LogBox.ignoreAllLogs(false);
129
+ }
130
+ return () => {
131
+ LogBox.ignoreAllLogs(false);
132
+ };
133
+ }, [enabled, visible]);
63
134
  useEffect(() => {
64
135
  if (selected) {
65
- setApiDetailActiveTab('metadata');
136
+ setApiDetailActiveTab('response');
66
137
  setDetailSearch('');
67
138
  }
68
139
  }, [selected]);
@@ -83,9 +154,43 @@ const NetworkInspector = () => {
83
154
  const [analyticsEvents, setAnalyticsEvents] = useState([]);
84
155
  // ─── Logs state ────────────────────────────────────────────────────────────
85
156
  const [consoleLogs, setConsoleLogs] = useState([]);
157
+ const [lastReadLogsCount, setLastReadLogsCount] = useState(0);
158
+ const [lastReadApisCount, setLastReadApisCount] = useState(0);
159
+ useEffect(() => {
160
+ if (visible) {
161
+ if (activeTab === 'apis') {
162
+ setLastReadApisCount(logs.length);
163
+ }
164
+ if (activeTab === 'logs') {
165
+ setLastReadLogsCount(consoleLogs.length);
166
+ }
167
+ }
168
+ }, [visible]);
169
+ useEffect(() => {
170
+ if (activeTab === 'apis') {
171
+ setLastReadApisCount(logs.length);
172
+ }
173
+ }, [activeTab, logs.length]);
174
+ useEffect(() => {
175
+ if (activeTab === 'logs') {
176
+ setLastReadLogsCount(consoleLogs.length);
177
+ }
178
+ }, [activeTab, consoleLogs.length]);
179
+ const [maxConsoleLogs, setMaxConsoleLogs] = useState(200);
180
+ const [showConsoleLevels, setShowConsoleLevels] = useState({
181
+ info: true,
182
+ warn: true,
183
+ error: true,
184
+ });
86
185
  const visibleConsoleLogs = useMemo(() => {
87
- return consoleLogs.filter(log => {
186
+ const filtered = consoleLogs.filter(log => {
88
187
  const type = log.type;
188
+ if (type === 'info' && !showConsoleLevels.info)
189
+ return false;
190
+ if (type === 'warn' && !showConsoleLevels.warn)
191
+ return false;
192
+ if (type === 'error' && !showConsoleLevels.error)
193
+ return false;
89
194
  const message = log.message || '';
90
195
  const allPrefixes = [
91
196
  ...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.info) || []),
@@ -99,13 +204,79 @@ const NetworkInspector = () => {
99
204
  message.toLowerCase().trim().includes(prefix.toLowerCase().trim()));
100
205
  return !isIgnored;
101
206
  });
102
- }, [consoleLogs]);
207
+ return filtered.slice(0, maxConsoleLogs);
208
+ }, [consoleLogs, showConsoleLevels, maxConsoleLogs]);
103
209
  const [logSearch, setLogSearch] = useState('');
104
210
  const [logFilters, setLogFilters] = useState(new Set(['user-log']));
105
211
  // ─── WebView state ─────────────────────────────────────────────────────────
106
212
  const [webViewLogs, setWebViewLogs] = useState([]);
107
213
  const [webViewNavHistory, setWebViewNavHistory] = useState([]);
108
214
  const [webViewSubTab, setWebViewSubTab] = useState('html');
215
+ const [inspectedElement, setInspectedElement] = useState(null);
216
+ // ─── Settings state ──────────────────────────────────────────────────────────
217
+ const [settingsPage, setSettingsPage] = useState(null);
218
+ const [tabVisibility, setTabVisibility] = useState({
219
+ insights: true,
220
+ apis: true,
221
+ logs: true,
222
+ analytics: true,
223
+ webview: true,
224
+ redux: true,
225
+ });
226
+ const [maxNetworkLogs, setMaxNetworkLogs] = useState(100);
227
+ const [webViewCaptureCssJs, setWebViewCaptureCssJs] = useState(true);
228
+ const [reduxAutoRefresh, setReduxAutoRefreshState] = useState(true);
229
+ const [reduxExpandDepth, setReduxExpandDepth] = useState(1);
230
+ const [slowRequestThreshold, setSlowRequestThreshold] = useState(1000);
231
+ const [insightsShowConsoleAlerts, setInsightsShowConsoleAlerts] = useState(true);
232
+ useEffect(() => {
233
+ setReduxAutoRefresh(reduxAutoRefresh);
234
+ }, [reduxAutoRefresh]);
235
+ const toggleTabVisibility = (key) => {
236
+ if (key === 'apis')
237
+ return;
238
+ setTabVisibility(prev => {
239
+ const nextVal = !prev[key];
240
+ const newVisibility = { ...prev, [key]: nextVal };
241
+ if (!nextVal && activeTab === key) {
242
+ setActiveTab('apis');
243
+ }
244
+ return newVisibility;
245
+ });
246
+ };
247
+ const navigateFromDashboard = (key) => {
248
+ setTabVisibility(prev => ({ ...prev, [key]: true }));
249
+ setActiveTab(key);
250
+ };
251
+ const getSearchTermForTab = () => {
252
+ if (!inspectedElement)
253
+ return '';
254
+ const { tagName, id, className, searchStr } = inspectedElement;
255
+ if (htmlSubTab === 'html') {
256
+ return searchStr;
257
+ }
258
+ if (htmlSubTab === 'css') {
259
+ if (className) {
260
+ const firstClass = className.trim().split(/\s+/)[0];
261
+ if (firstClass)
262
+ return `.${firstClass}`;
263
+ }
264
+ if (id)
265
+ return `#${id}`;
266
+ return tagName;
267
+ }
268
+ if (htmlSubTab === 'javascript') {
269
+ if (id)
270
+ return id;
271
+ if (className) {
272
+ const firstClass = className.trim().split(/\s+/)[0];
273
+ if (firstClass)
274
+ return firstClass;
275
+ }
276
+ return tagName;
277
+ }
278
+ return '';
279
+ };
109
280
  const [webViewSearch, setWebViewSearch] = useState('');
110
281
  const [webViewHtml, setWebViewHtml] = useState('');
111
282
  const [webViewCss, setWebViewCss] = useState('');
@@ -144,6 +315,8 @@ const NetworkInspector = () => {
144
315
  const [newLogIds, setNewLogIds] = useState(new Set());
145
316
  const pulseAnim = useRef(new Animated.Value(1)).current;
146
317
  const badgeAnim = useRef(new Animated.Value(1)).current;
318
+ const activePulseAnim = useRef(new Animated.Value(0.4)).current;
319
+ const unreadPulseAnim = useRef(new Animated.Value(1)).current;
147
320
  useEffect(() => {
148
321
  const loop = Animated.loop(Animated.sequence([
149
322
  Animated.timing(pulseAnim, {
@@ -160,6 +333,38 @@ const NetworkInspector = () => {
160
333
  loop.start();
161
334
  return () => loop.stop();
162
335
  }, [pulseAnim]);
336
+ useEffect(() => {
337
+ const loop = Animated.loop(Animated.sequence([
338
+ Animated.timing(activePulseAnim, {
339
+ toValue: 1,
340
+ duration: 1200,
341
+ useNativeDriver: true,
342
+ }),
343
+ Animated.timing(activePulseAnim, {
344
+ toValue: 0.4,
345
+ duration: 1200,
346
+ useNativeDriver: true,
347
+ }),
348
+ ]));
349
+ loop.start();
350
+ return () => loop.stop();
351
+ }, [activePulseAnim]);
352
+ useEffect(() => {
353
+ const loop = Animated.loop(Animated.sequence([
354
+ Animated.timing(unreadPulseAnim, {
355
+ toValue: 1.3,
356
+ duration: 800,
357
+ useNativeDriver: true,
358
+ }),
359
+ Animated.timing(unreadPulseAnim, {
360
+ toValue: 0.8,
361
+ duration: 800,
362
+ useNativeDriver: true,
363
+ }),
364
+ ]));
365
+ loop.start();
366
+ return () => loop.stop();
367
+ }, [unreadPulseAnim]);
163
368
  useEffect(() => {
164
369
  if ((logs.length > 0 || analyticsEvents.length > 0) && newLogIds.size > 0) {
165
370
  badgeAnim.setValue(0.8);
@@ -318,8 +523,8 @@ const NetworkInspector = () => {
318
523
  if (sortOrder === 'oldest') {
319
524
  result = [...result].reverse();
320
525
  }
321
- return result;
322
- }, [logs, search, statusFilters, methodFilters, sortOrder]);
526
+ return result.slice(0, maxNetworkLogs);
527
+ }, [logs, search, statusFilters, methodFilters, sortOrder, maxNetworkLogs]);
323
528
  const availableMethods = useMemo(() => {
324
529
  const methods = new Set();
325
530
  logs.forEach(log => {
@@ -716,6 +921,574 @@ const NetworkInspector = () => {
716
921
  toggleSectionFilter,
717
922
  toggleSectionCollapse,
718
923
  ]);
924
+ const renderSettings = () => {
925
+ if (settingsPage === 'main') {
926
+ const settingsTabs = [
927
+ { key: 'insights', label: 'Insights', icon: 'insights' },
928
+ { key: 'apis', label: 'APIs', icon: 'apis' },
929
+ { key: 'logs', label: 'Logs', icon: 'logs' },
930
+ { key: 'analytics', label: 'Analytics', icon: 'analytics' },
931
+ { key: 'webview', label: 'WebView', icon: 'webview' },
932
+ { key: 'redux', label: 'Redux', icon: 'redux' },
933
+ ];
934
+ return (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
935
+ {/* Settings Header with back button */}
936
+ <LinearGradient colors={[AppColors.purple, '#6B4EFF']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={styles.headerGradient}>
937
+ <View style={[styles.header, { paddingHorizontal: 16, gap: 12 }]}>
938
+ <TouchableScale onPress={() => {
939
+ setSettingsPage(null);
940
+ setActiveTab('apis');
941
+ }} hitSlop={12} style={{
942
+ padding: 8,
943
+ borderRadius: 10,
944
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
945
+ borderWidth: 1,
946
+ borderColor: 'rgba(255, 255, 255, 0.08)',
947
+ }}>
948
+ <WhiteBackNavigation color="#FFFFFF" size={16}/>
949
+ </TouchableScale>
950
+ <View style={{ flex: 1 }}>
951
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 17, color: '#FFFFFF' }}>Settings</Text>
952
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: 'rgba(255,255,255,0.75)', marginTop: 1 }}>Manage modules and preferences</Text>
953
+ </View>
954
+ <View style={{
955
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
956
+ paddingHorizontal: 8,
957
+ paddingVertical: 4,
958
+ borderRadius: 8,
959
+ borderWidth: 1,
960
+ borderColor: 'rgba(255, 255, 255, 0.1)'
961
+ }}>
962
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10.5, color: '#FFFFFF' }}>v1.0.10</Text>
963
+ </View>
964
+ </View>
965
+ </LinearGradient>
966
+
967
+ <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
968
+
969
+ {/* Tab list */}
970
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, overflow: 'hidden' }}>
971
+ {/* Table Header */}
972
+ <View style={{
973
+ flexDirection: 'row',
974
+ alignItems: 'center',
975
+ paddingVertical: 8,
976
+ paddingHorizontal: 14,
977
+ backgroundColor: AppColors.grayBackground,
978
+ borderBottomWidth: 1,
979
+ borderBottomColor: AppColors.dividerColor,
980
+ gap: 12,
981
+ }}>
982
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.6, flex: 1 }}>MODULE</Text>
983
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.6, width: 90, textAlign: 'right', paddingRight: 4 }}>VISIBILITY</Text>
984
+ </View>
985
+
986
+ {settingsTabs.map((tab, idx) => {
987
+ const isVisible = tab.key === 'apis' || tabVisibility[tab.key];
988
+ const isLast = idx === settingsTabs.length - 1;
989
+ const isLocked = tab.key === 'apis';
990
+ return (<View key={tab.key} style={{
991
+ flexDirection: 'row',
992
+ alignItems: 'center',
993
+ paddingVertical: 10,
994
+ paddingHorizontal: 14,
995
+ borderBottomWidth: isLast ? 0 : 1,
996
+ borderBottomColor: AppColors.dividerColor,
997
+ gap: 12,
998
+ }}>
999
+ {/* Icon + Label — fills remaining space */}
1000
+ <View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1001
+ {/* Small icon tile */}
1002
+ <View style={{
1003
+ width: 20, height: 20, borderRadius: 6,
1004
+ backgroundColor: isLocked ? AppColors.grayBorderSecondary : AppColors.purpleShade50,
1005
+ borderWidth: 1,
1006
+ borderColor: isLocked ? AppColors.dividerColor : 'rgba(104,75,155,0.2)',
1007
+ alignItems: 'center', justifyContent: 'center',
1008
+ }}>
1009
+ {tab.icon === 'insights' && <InsightsIcon color={isLocked ? AppColors.grayTextWeak : AppColors.purple} size={11}/>}
1010
+ {tab.icon === 'apis' && <SignalIcon color={isLocked ? AppColors.grayTextWeak : AppColors.purple} size={11}/>}
1011
+ {tab.icon === 'logs' && <TerminalIcon color={isLocked ? AppColors.grayTextWeak : AppColors.purple} size={11}/>}
1012
+ {tab.icon === 'analytics' && <AnalyticsIcon color={isLocked ? AppColors.grayTextWeak : AppColors.purple} size={11}/>}
1013
+ {tab.icon === 'webview' && <GlobeIcon color={isLocked ? AppColors.grayTextWeak : AppColors.purple} size={11}/>}
1014
+ {tab.icon === 'redux' && <TerminalIcon color={isLocked ? AppColors.grayTextWeak : AppColors.purple} size={11}/>}
1015
+ </View>
1016
+ {/* Label + Required badge */}
1017
+ <Text style={{
1018
+ fontFamily: AppFonts.interBold,
1019
+ fontSize: 13,
1020
+ color: isLocked ? AppColors.grayText : AppColors.primaryBlack,
1021
+ }}>
1022
+ {tab.label}
1023
+ </Text>
1024
+ {isLocked && (<View style={{
1025
+ flexDirection: 'row',
1026
+ alignItems: 'center',
1027
+ backgroundColor: 'rgba(104,75,155,0.08)',
1028
+ borderRadius: 20,
1029
+ paddingHorizontal: 6,
1030
+ paddingVertical: 2,
1031
+ borderWidth: 1,
1032
+ borderColor: 'rgba(104,75,155,0.18)',
1033
+ gap: 3,
1034
+ }}>
1035
+ <View style={{ width: 4, height: 4, borderRadius: 2, backgroundColor: AppColors.purple, opacity: 0.7 }}/>
1036
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 8.5, color: AppColors.purple, letterSpacing: 0.4 }}>
1037
+ DEFAULT
1038
+ </Text>
1039
+ </View>)}
1040
+
1041
+ {/* Settings gear icon next to label */}
1042
+ <TouchableScale onPress={() => setSettingsPage(tab.key)} hitSlop={8} style={{
1043
+ marginLeft: 4,
1044
+ padding: 4,
1045
+ borderRadius: 6,
1046
+ backgroundColor: AppColors.purpleShade50,
1047
+ borderWidth: 1,
1048
+ borderColor: 'rgba(104,75,155,0.15)',
1049
+ alignItems: 'center',
1050
+ justifyContent: 'center',
1051
+ }}>
1052
+ <SettingsIcon color={AppColors.purple} size={10}/>
1053
+ </TouchableScale>
1054
+ </View>
1055
+
1056
+ {/* Visibility Switch in VISIBILITY column */}
1057
+ <View style={{ width: 90, alignItems: 'flex-end', justifyContent: 'center' }}>
1058
+ <TouchableScale disabled={isLocked} onPress={() => toggleTabVisibility(tab.key)} style={{
1059
+ width: 38,
1060
+ height: 22,
1061
+ borderRadius: 11,
1062
+ backgroundColor: isVisible ? AppColors.purple : AppColors.grayBorderSecondary,
1063
+ padding: 2,
1064
+ justifyContent: 'center',
1065
+ alignItems: isVisible ? 'flex-end' : 'flex-start',
1066
+ }}>
1067
+ <View style={{
1068
+ width: 18,
1069
+ height: 18,
1070
+ borderRadius: 9,
1071
+ backgroundColor: '#FFFFFF',
1072
+ shadowColor: '#000',
1073
+ shadowOpacity: 0.15,
1074
+ shadowRadius: 1.5,
1075
+ shadowOffset: { width: 0, height: 1 },
1076
+ }}/>
1077
+ </TouchableScale>
1078
+ </View>
1079
+ </View>);
1080
+ })}
1081
+ </View>
1082
+
1083
+ {/* Preferences Section */}
1084
+ <View style={{ marginTop: 8 }}>
1085
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.6, marginBottom: 8 }}>PREFERENCES</Text>
1086
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, overflow: 'hidden' }}>
1087
+ <View style={{
1088
+ flexDirection: 'row',
1089
+ alignItems: 'center',
1090
+ paddingVertical: 12,
1091
+ paddingHorizontal: 14,
1092
+ gap: 12,
1093
+ }}>
1094
+ {/* Icon + Label */}
1095
+ <View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1096
+ <View style={{
1097
+ width: 20, height: 20, borderRadius: 6,
1098
+ backgroundColor: AppColors.purpleShade50,
1099
+ borderWidth: 1,
1100
+ borderColor: 'rgba(104,75,155,0.2)',
1101
+ alignItems: 'center', justifyContent: 'center',
1102
+ }}>
1103
+ {isDark ? (<SunIcon color={AppColors.purple} size={11}/>) : (<MoonIcon color={AppColors.purple} size={11}/>)}
1104
+ </View>
1105
+ <View style={{ flex: 1 }}>
1106
+ <Text style={{
1107
+ fontFamily: AppFonts.interBold,
1108
+ fontSize: 13,
1109
+ color: AppColors.primaryBlack,
1110
+ }}>
1111
+ Dark Theme
1112
+ </Text>
1113
+ </View>
1114
+ </View>
1115
+
1116
+ {/* Toggle Switch */}
1117
+ <TouchableScale onPress={() => {
1118
+ const newTheme = !isDark;
1119
+ setIsDark(newTheme);
1120
+ toggleGlobalTheme(newTheme);
1121
+ }} style={{
1122
+ width: 38,
1123
+ height: 22,
1124
+ borderRadius: 11,
1125
+ backgroundColor: isDark ? AppColors.purple : AppColors.grayBorderSecondary,
1126
+ padding: 2,
1127
+ justifyContent: 'center',
1128
+ alignItems: isDark ? 'flex-end' : 'flex-start',
1129
+ }}>
1130
+ <View style={{
1131
+ width: 18,
1132
+ height: 18,
1133
+ borderRadius: 9,
1134
+ backgroundColor: '#FFFFFF',
1135
+ shadowColor: '#000',
1136
+ shadowOpacity: 0.15,
1137
+ shadowRadius: 1.5,
1138
+ shadowOffset: { width: 0, height: 1 },
1139
+ }}/>
1140
+ </TouchableScale>
1141
+ </View>
1142
+ </View>
1143
+ </View>
1144
+ </ScrollView>
1145
+ </View>);
1146
+ }
1147
+ const goBackToMain = () => setSettingsPage('main');
1148
+ const renderSubHeader = (title, icon, rightInfo) => (<View style={{
1149
+ flexDirection: 'row',
1150
+ alignItems: 'center',
1151
+ gap: 12,
1152
+ paddingBottom: 16,
1153
+ borderBottomWidth: 1,
1154
+ borderBottomColor: AppColors.dividerColor,
1155
+ marginBottom: 16,
1156
+ }}>
1157
+ <TouchableScale onPress={goBackToMain} style={{
1158
+ width: 36,
1159
+ height: 36,
1160
+ borderRadius: 10,
1161
+ backgroundColor: AppColors.purpleShade50,
1162
+ borderWidth: 1,
1163
+ borderColor: 'rgba(104,75,155,0.2)',
1164
+ alignItems: 'center',
1165
+ justifyContent: 'center',
1166
+ }}>
1167
+ <WhiteBackNavigation color={AppColors.purple} size={16}/>
1168
+ </TouchableScale>
1169
+ {icon && (<View style={{
1170
+ width: 36, height: 36, borderRadius: 10,
1171
+ backgroundColor: AppColors.purpleShade50,
1172
+ borderWidth: 1, borderColor: 'rgba(104,75,155,0.2)',
1173
+ alignItems: 'center', justifyContent: 'center',
1174
+ }}>
1175
+ {icon}
1176
+ </View>)}
1177
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 18, color: AppColors.primaryBlack, flex: 1 }}>
1178
+ {title}
1179
+ </Text>
1180
+ {rightInfo ? (<View style={{
1181
+ backgroundColor: 'rgba(104,75,155,0.08)',
1182
+ paddingHorizontal: 10,
1183
+ paddingVertical: 5,
1184
+ borderRadius: 8,
1185
+ borderWidth: 1,
1186
+ borderColor: 'rgba(104,75,155,0.15)',
1187
+ }}>
1188
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 11, color: AppColors.purple }}>
1189
+ {rightInfo}
1190
+ </Text>
1191
+ </View>) : null}
1192
+ </View>);
1193
+ // Helper: settings row with icon + label + optional description
1194
+ const renderSettingRow = (opts) => (<View style={{
1195
+ paddingVertical: 12,
1196
+ borderBottomWidth: opts.isLast ? 0 : 1,
1197
+ borderBottomColor: AppColors.dividerColor,
1198
+ }}>
1199
+ <TouchableScale disabled={!opts.onPress} onPress={opts.onPress} style={{
1200
+ flexDirection: 'row',
1201
+ alignItems: 'center',
1202
+ gap: 12,
1203
+ }}>
1204
+ <View style={{
1205
+ width: 36, height: 36, borderRadius: 10,
1206
+ backgroundColor: AppColors.purpleShade50,
1207
+ borderWidth: 1, borderColor: 'rgba(104,75,155,0.18)',
1208
+ alignItems: 'center', justifyContent: 'center',
1209
+ }}>
1210
+ {opts.icon}
1211
+ </View>
1212
+ <View style={{ flex: 1 }}>
1213
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 14, color: AppColors.primaryBlack }}>
1214
+ {opts.label}
1215
+ </Text>
1216
+ {opts.description ? (<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: AppColors.grayText, marginTop: 1 }}>
1217
+ {opts.description}
1218
+ </Text>) : null}
1219
+ </View>
1220
+ {opts.right || null}
1221
+ </TouchableScale>
1222
+ {opts.picker && (<View style={{
1223
+ flexDirection: 'row',
1224
+ backgroundColor: AppColors.grayBackground,
1225
+ borderRadius: 8,
1226
+ padding: 2.5,
1227
+ marginTop: 10,
1228
+ borderWidth: 1,
1229
+ borderColor: AppColors.dividerColor,
1230
+ }}>
1231
+ {opts.picker.options.map(opt => {
1232
+ const isActive = opts.picker.selectedValue === opt;
1233
+ return (<TouchableScale key={String(opt)} onPress={() => opts.picker.onSelect(opt)} style={{
1234
+ flex: 1,
1235
+ paddingVertical: 6,
1236
+ alignItems: 'center',
1237
+ borderRadius: 6,
1238
+ backgroundColor: isActive ? AppColors.purple : 'transparent',
1239
+ }}>
1240
+ <Text style={{
1241
+ fontFamily: AppFonts.interBold,
1242
+ fontSize: 11,
1243
+ color: isActive ? '#FFFFFF' : AppColors.grayText,
1244
+ }}>
1245
+ {typeof opt === 'number' && opt >= 500 && settingsPage === 'insights' ? `${opt}ms` : opt}
1246
+ </Text>
1247
+ </TouchableScale>);
1248
+ })}
1249
+ </View>)}
1250
+ </View>);
1251
+ if (settingsPage === 'apis') {
1252
+ return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1253
+ {renderSubHeader('APIs Settings', <SignalIcon color={AppColors.purple} size={16}/>, `Total: ${logs.length}`)}
1254
+ <View style={{ backgroundColor: AppColors.primaryLight, padding: 16, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, gap: 4 }}>
1255
+ {renderSettingRow({
1256
+ icon: <SignalIcon color={AppColors.purple} size={16}/>,
1257
+ label: 'Max Request Logs',
1258
+ description: 'How many network requests to keep in memory',
1259
+ picker: {
1260
+ options: [50, 100, 200, 500],
1261
+ selectedValue: maxNetworkLogs,
1262
+ onSelect: setMaxNetworkLogs,
1263
+ },
1264
+ isLast: true,
1265
+ })}
1266
+ </View>
1267
+
1268
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, padding: 16, marginTop: 12 }}>
1269
+ {renderSettingRow({
1270
+ icon: <TrashIcon color={AppColors.errorColor} size={16}/>,
1271
+ label: 'Clear Network Logs',
1272
+ description: `${logs.length} requests stored`,
1273
+ isLast: true,
1274
+ onPress: () => {
1275
+ clearNetworkLogs();
1276
+ setSelected(null);
1277
+ Alert.alert('Success', 'Network logs cleared.');
1278
+ },
1279
+ right: (<View style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 7, backgroundColor: 'rgba(255,46,87,0.08)', borderWidth: 1, borderColor: 'rgba(255,46,87,0.2)' }}>
1280
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 11, color: AppColors.errorColor }}>Clear</Text>
1281
+ </View>),
1282
+ })}
1283
+ </View>
1284
+ </ScrollView>);
1285
+ }
1286
+ if (settingsPage === 'logs') {
1287
+ return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1288
+ {renderSubHeader('Logs Settings', <TerminalIcon color={AppColors.purple} size={16}/>, `Total: ${consoleLogs.length}`)}
1289
+ <View style={{ backgroundColor: AppColors.primaryLight, padding: 16, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, gap: 4 }}>
1290
+ {renderSettingRow({
1291
+ icon: <TerminalIcon color={AppColors.purple} size={16}/>,
1292
+ label: 'Max Console Logs',
1293
+ description: 'How many console messages to retain',
1294
+ picker: {
1295
+ options: [100, 200, 500, 1000],
1296
+ selectedValue: maxConsoleLogs,
1297
+ onSelect: setMaxConsoleLogs,
1298
+ },
1299
+ })}
1300
+ <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
1301
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.primaryBlack, paddingTop: 4 }}>Log Levels</Text>
1302
+ {['info', 'warn', 'error'].map((level, li) => {
1303
+ const isLvlActive = showConsoleLevels[level];
1304
+ const levelColor = level === 'error' ? AppColors.errorColor : level === 'warn' ? AppColors.warningIconGold : AppColors.skyBlue;
1305
+ return renderSettingRow({
1306
+ icon: <View style={{ width: 10, height: 10, borderRadius: 5, backgroundColor: levelColor }}/>,
1307
+ label: `Show ${level.charAt(0).toUpperCase() + level.slice(1)} logs`,
1308
+ description: level === 'info' ? 'Informational messages' : level === 'warn' ? 'Warning messages' : 'Error messages',
1309
+ isLast: level === 'error',
1310
+ onPress: () => setShowConsoleLevels(prev => ({ ...prev, [level]: !prev[level] })),
1311
+ right: (<View style={{
1312
+ width: 22, height: 22, borderRadius: 6,
1313
+ borderWidth: 2,
1314
+ borderColor: isLvlActive ? AppColors.purple : AppColors.grayTextWeak,
1315
+ backgroundColor: isLvlActive ? 'rgba(104, 75, 155, 0.1)' : 'transparent',
1316
+ alignItems: 'center', justifyContent: 'center',
1317
+ }}>
1318
+ {isLvlActive && <CheckIcon size={12} color={AppColors.purple}/>}
1319
+ </View>),
1320
+ });
1321
+ })}
1322
+ </View>
1323
+
1324
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, padding: 16, marginTop: 12 }}>
1325
+ {renderSettingRow({
1326
+ icon: <TrashIcon color={AppColors.errorColor} size={16}/>,
1327
+ label: 'Clear Console Logs',
1328
+ description: `${consoleLogs.length} logs stored`,
1329
+ isLast: true,
1330
+ onPress: () => {
1331
+ clearConsoleLogs();
1332
+ Alert.alert('Success', 'Console logs cleared.');
1333
+ },
1334
+ right: (<View style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 7, backgroundColor: 'rgba(255,46,87,0.08)', borderWidth: 1, borderColor: 'rgba(255,46,87,0.2)' }}>
1335
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 11, color: AppColors.errorColor }}>Clear</Text>
1336
+ </View>),
1337
+ })}
1338
+ </View>
1339
+ </ScrollView>);
1340
+ }
1341
+ if (settingsPage === 'analytics') {
1342
+ return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1343
+ {renderSubHeader('Analytics Settings', <AnalyticsIcon color={AppColors.purple} size={16}/>, `Events: ${analyticsEvents.length}`)}
1344
+ <View style={{ backgroundColor: AppColors.primaryLight, padding: 16, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, gap: 4 }}>
1345
+ {renderSettingRow({
1346
+ icon: <AnalyticsIcon color={AppColors.purple} size={16}/>,
1347
+ label: 'Events Captured',
1348
+ description: `${analyticsEvents.length} analytics events stored`,
1349
+ isLast: true,
1350
+ })}
1351
+ </View>
1352
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, padding: 16, marginTop: 12 }}>
1353
+ {renderSettingRow({
1354
+ icon: <TrashIcon color={AppColors.errorColor} size={16}/>,
1355
+ label: 'Clear Analytics History',
1356
+ description: 'Remove all captured events',
1357
+ isLast: true,
1358
+ onPress: () => {
1359
+ clearAnalyticsEvents();
1360
+ setSelectedEvent(null);
1361
+ Alert.alert('Success', 'Analytics events cleared.');
1362
+ },
1363
+ right: (<View style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 7, backgroundColor: 'rgba(255,46,87,0.08)', borderWidth: 1, borderColor: 'rgba(255,46,87,0.2)' }}>
1364
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 11, color: AppColors.errorColor }}>Clear</Text>
1365
+ </View>),
1366
+ })}
1367
+ </View>
1368
+ </ScrollView>);
1369
+ }
1370
+ if (settingsPage === 'webview') {
1371
+ return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1372
+ {renderSubHeader('WebView Settings', <GlobeIcon color={AppColors.purple} size={16}/>, `History: ${webViewNavHistory.length}`)}
1373
+ <View style={{ backgroundColor: AppColors.primaryLight, padding: 16, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, gap: 4 }}>
1374
+ {renderSettingRow({
1375
+ icon: <GlobeIcon color={AppColors.purple} size={16}/>,
1376
+ label: 'Capture CSS & JavaScript',
1377
+ description: 'Extract stylesheet and script source from pages',
1378
+ onPress: () => setWebViewCaptureCssJs(prev => !prev),
1379
+ right: (<View style={{
1380
+ width: 22, height: 22, borderRadius: 6,
1381
+ borderWidth: 2,
1382
+ borderColor: webViewCaptureCssJs ? AppColors.purple : AppColors.grayTextWeak,
1383
+ backgroundColor: webViewCaptureCssJs ? 'rgba(104, 75, 155, 0.1)' : 'transparent',
1384
+ alignItems: 'center', justifyContent: 'center',
1385
+ }}>
1386
+ {webViewCaptureCssJs && <CheckIcon size={12} color={AppColors.purple}/>}
1387
+ </View>),
1388
+ isLast: true,
1389
+ })}
1390
+ </View>
1391
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, padding: 16, marginTop: 12 }}>
1392
+ {renderSettingRow({
1393
+ icon: <TrashIcon color={AppColors.errorColor} size={16}/>,
1394
+ label: 'Clear WebView Data',
1395
+ description: 'Remove all captured source & navigation history',
1396
+ isLast: true,
1397
+ onPress: () => {
1398
+ clearWebViewData();
1399
+ Alert.alert('Success', 'WebView source history cleared.');
1400
+ },
1401
+ right: (<View style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 7, backgroundColor: 'rgba(255,46,87,0.08)', borderWidth: 1, borderColor: 'rgba(255,46,87,0.2)' }}>
1402
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 11, color: AppColors.errorColor }}>Clear</Text>
1403
+ </View>),
1404
+ })}
1405
+ </View>
1406
+ </ScrollView>);
1407
+ }
1408
+ if (settingsPage === 'redux') {
1409
+ return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1410
+ {renderSubHeader('Redux Settings', <TerminalIcon color={AppColors.purple} size={16}/>, `Reducers: ${Object.keys(reduxState || {}).length}`)}
1411
+ <View style={{ backgroundColor: AppColors.primaryLight, padding: 16, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, gap: 4 }}>
1412
+ {renderSettingRow({
1413
+ icon: <TerminalIcon color={AppColors.purple} size={16}/>,
1414
+ label: 'Auto-refresh Store',
1415
+ description: 'Automatically capture Redux store state updates',
1416
+ onPress: () => setReduxAutoRefreshState(prev => !prev),
1417
+ right: (<View style={{
1418
+ width: 22, height: 22, borderRadius: 6,
1419
+ borderWidth: 2,
1420
+ borderColor: reduxAutoRefresh ? AppColors.purple : AppColors.grayTextWeak,
1421
+ backgroundColor: reduxAutoRefresh ? 'rgba(104, 75, 155, 0.1)' : 'transparent',
1422
+ alignItems: 'center', justifyContent: 'center',
1423
+ }}>
1424
+ {reduxAutoRefresh && <CheckIcon size={12} color={AppColors.purple}/>}
1425
+ </View>),
1426
+ })}
1427
+ <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
1428
+ {renderSettingRow({
1429
+ icon: <InsightsIcon color={AppColors.purple} size={16}/>,
1430
+ label: 'Default JSON Expand Depth',
1431
+ description: 'Initial depth of Redux state tree to auto-expand',
1432
+ picker: {
1433
+ options: [1, 2, 3, 5],
1434
+ selectedValue: reduxExpandDepth,
1435
+ onSelect: setReduxExpandDepth,
1436
+ },
1437
+ isLast: true,
1438
+ })}
1439
+ </View>
1440
+
1441
+ <View style={{ backgroundColor: AppColors.primaryLight, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, padding: 16, marginTop: 12 }}>
1442
+ {renderSettingRow({
1443
+ icon: <TrashIcon color={AppColors.errorColor} size={16}/>,
1444
+ label: 'Clear Redux State',
1445
+ description: reduxState ? 'Reset state snapshot in inspector' : 'No store snapshot stored',
1446
+ isLast: true,
1447
+ onPress: () => {
1448
+ setReduxState(null);
1449
+ Alert.alert('Success', 'Redux state snapshot cleared.');
1450
+ },
1451
+ right: (<View style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 7, backgroundColor: 'rgba(255,46,87,0.08)', borderWidth: 1, borderColor: 'rgba(255,46,87,0.2)' }}>
1452
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 11, color: AppColors.errorColor }}>Clear</Text>
1453
+ </View>),
1454
+ })}
1455
+ </View>
1456
+ </ScrollView>);
1457
+ }
1458
+ // Default return page is Insights settings
1459
+ return (<ScrollView style={{ flex: 1, backgroundColor: AppColors.grayBackground }} contentContainerStyle={{ padding: 16 }}>
1460
+ {renderSubHeader('Insights Settings', <InsightsIcon color={AppColors.purple} size={16}/>)}
1461
+ <View style={{ backgroundColor: AppColors.primaryLight, padding: 16, borderRadius: 12, borderWidth: 1, borderColor: AppColors.grayBorderSecondary, gap: 4 }}>
1462
+ {renderSettingRow({
1463
+ icon: <SignalIcon color={AppColors.purple} size={16}/>,
1464
+ label: 'Slow Latency Warning',
1465
+ description: 'Alert threshold for slow API request duration',
1466
+ picker: {
1467
+ options: [500, 1000, 2000],
1468
+ selectedValue: slowRequestThreshold,
1469
+ onSelect: setSlowRequestThreshold,
1470
+ },
1471
+ })}
1472
+ <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
1473
+ {renderSettingRow({
1474
+ icon: <TerminalIcon color={AppColors.purple} size={16}/>,
1475
+ label: 'Show Console Alerts',
1476
+ description: 'Flags critical warnings or crash events on dashboard',
1477
+ isLast: true,
1478
+ onPress: () => setInsightsShowConsoleAlerts(prev => !prev),
1479
+ right: (<View style={{
1480
+ width: 22, height: 22, borderRadius: 6,
1481
+ borderWidth: 2,
1482
+ borderColor: insightsShowConsoleAlerts ? AppColors.purple : AppColors.grayTextWeak,
1483
+ backgroundColor: insightsShowConsoleAlerts ? 'rgba(104, 75, 155, 0.1)' : 'transparent',
1484
+ alignItems: 'center', justifyContent: 'center',
1485
+ }}>
1486
+ {insightsShowConsoleAlerts && <CheckIcon size={12} color={AppColors.purple}/>}
1487
+ </View>),
1488
+ })}
1489
+ </View>
1490
+ </ScrollView>);
1491
+ };
719
1492
  const renderInsightsDashboard = () => {
720
1493
  const apiTotal = logs.length;
721
1494
  const apiErrors = logs.filter(l => (l.status != null && l.status >= 400) || l.status === 0 || l.status == null).length;
@@ -735,168 +1508,168 @@ const NetworkInspector = () => {
735
1508
  const webviewTotal = webViewNavHistory.length;
736
1509
  return (<View style={styles.dashboardContainer}>
737
1510
  {/* Module 1: APIs */}
738
- <TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('apis')}>
739
- <View style={styles.dashboardModuleHeader}>
740
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
741
- <SignalIcon color={AppColors.purple} size={18}/>
742
- <Text style={styles.dashboardModuleTitle}>APIs & Network</Text>
743
- </View>
744
- <Text style={styles.dashboardModuleGoText}>View Details →</Text>
745
- </View>
746
- <View style={styles.dashboardModuleGrid}>
747
- <View style={styles.dashboardGridItem}>
748
- <Text style={styles.dashboardGridVal}>{apiTotal}</Text>
749
- <Text style={styles.dashboardGridLbl}>Requests</Text>
750
- </View>
751
- <View style={styles.dashboardGridItem}>
752
- <Text style={[styles.dashboardGridVal, apiSuccessRate < 90 && { color: AppColors.warningIconGold }]}>
753
- {apiSuccessRate}%
754
- </Text>
755
- <Text style={styles.dashboardGridLbl}>Success Rate</Text>
756
- </View>
757
- <View style={styles.dashboardGridItem}>
758
- <Text style={[styles.dashboardGridVal, apiErrors > 0 && { color: AppColors.errorColor }]}>
759
- {apiErrors}
760
- </Text>
761
- <Text style={styles.dashboardGridLbl}>Errors</Text>
1511
+ {tabVisibility.apis && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('apis')}>
1512
+ <View style={styles.dashboardModuleHeader}>
1513
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1514
+ <SignalIcon color={AppColors.purple} size={18}/>
1515
+ <Text style={styles.dashboardModuleTitle}>APIs & Network</Text>
1516
+ </View>
1517
+ <Text style={styles.dashboardModuleGoText}>View Details →</Text>
762
1518
  </View>
763
- <View style={styles.dashboardGridItem}>
764
- <Text style={styles.dashboardGridVal}>
765
- {avgTime != null ? `${avgTime}ms` : '—'}
766
- </Text>
767
- <Text style={styles.dashboardGridLbl}>Avg Latency</Text>
1519
+ <View style={styles.dashboardModuleGrid}>
1520
+ <View style={styles.dashboardGridItem}>
1521
+ <Text style={styles.dashboardGridVal}>{apiTotal}</Text>
1522
+ <Text style={styles.dashboardGridLbl}>Requests</Text>
1523
+ </View>
1524
+ <View style={styles.dashboardGridItem}>
1525
+ <Text style={[styles.dashboardGridVal, apiSuccessRate < 90 && { color: AppColors.warningIconGold }]}>
1526
+ {apiSuccessRate}%
1527
+ </Text>
1528
+ <Text style={styles.dashboardGridLbl}>Success Rate</Text>
1529
+ </View>
1530
+ <View style={styles.dashboardGridItem}>
1531
+ <Text style={[styles.dashboardGridVal, apiErrors > 0 && { color: AppColors.errorColor }]}>
1532
+ {apiErrors}
1533
+ </Text>
1534
+ <Text style={styles.dashboardGridLbl}>Errors</Text>
1535
+ </View>
1536
+ <View style={styles.dashboardGridItem}>
1537
+ <Text style={styles.dashboardGridVal}>
1538
+ {avgTime != null ? `${avgTime}ms` : '—'}
1539
+ </Text>
1540
+ <Text style={styles.dashboardGridLbl}>Avg Latency</Text>
1541
+ </View>
768
1542
  </View>
769
- </View>
770
- </TouchableScale>
1543
+ </TouchableScale>)}
771
1544
 
772
1545
  {/* Module 2: Logs */}
773
- <TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('logs')}>
774
- <View style={styles.dashboardModuleHeader}>
775
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
776
- <TerminalIcon color="#0D9488" size={18}/>
777
- <Text style={styles.dashboardModuleTitle}>Console Logs</Text>
778
- </View>
779
- <Text style={styles.dashboardModuleGoText}>View Details →</Text>
780
- </View>
781
- <View style={styles.dashboardModuleGrid}>
782
- <View style={styles.dashboardGridItem}>
783
- <Text style={styles.dashboardGridVal}>{logTotal}</Text>
784
- <Text style={styles.dashboardGridLbl}>Total Logs</Text>
1546
+ {tabVisibility.logs && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('logs')}>
1547
+ <View style={styles.dashboardModuleHeader}>
1548
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1549
+ <TerminalIcon color="#0D9488" size={18}/>
1550
+ <Text style={styles.dashboardModuleTitle}>Console Logs</Text>
1551
+ </View>
1552
+ <Text style={styles.dashboardModuleGoText}>View Details →</Text>
785
1553
  </View>
786
- <View style={styles.dashboardGridItem}>
787
- <Text style={[styles.dashboardGridVal, { color: '#0D9488' }]}>{logInfos}</Text>
788
- <Text style={styles.dashboardGridLbl}>Info</Text>
789
- </View>
790
- <View style={styles.dashboardGridItem}>
791
- <Text style={[styles.dashboardGridVal, logWarns > 0 && { color: AppColors.warningIconGold }]}>
792
- {logWarns}
793
- </Text>
794
- <Text style={styles.dashboardGridLbl}>Warnings</Text>
795
- </View>
796
- <View style={styles.dashboardGridItem}>
797
- <Text style={[styles.dashboardGridVal, logErrors > 0 && { color: AppColors.errorColor }]}>
798
- {logErrors}
799
- </Text>
800
- <Text style={styles.dashboardGridLbl}>Errors</Text>
1554
+ <View style={styles.dashboardModuleGrid}>
1555
+ <View style={styles.dashboardGridItem}>
1556
+ <Text style={styles.dashboardGridVal}>{logTotal}</Text>
1557
+ <Text style={styles.dashboardGridLbl}>Total Logs</Text>
1558
+ </View>
1559
+ <View style={styles.dashboardGridItem}>
1560
+ <Text style={[styles.dashboardGridVal, { color: '#0D9488' }]}>{logInfos}</Text>
1561
+ <Text style={styles.dashboardGridLbl}>Info</Text>
1562
+ </View>
1563
+ <View style={styles.dashboardGridItem}>
1564
+ <Text style={[styles.dashboardGridVal, logWarns > 0 && { color: AppColors.warningIconGold }]}>
1565
+ {logWarns}
1566
+ </Text>
1567
+ <Text style={styles.dashboardGridLbl}>Warnings</Text>
1568
+ </View>
1569
+ <View style={styles.dashboardGridItem}>
1570
+ <Text style={[styles.dashboardGridVal, logErrors > 0 && { color: AppColors.errorColor }]}>
1571
+ {logErrors}
1572
+ </Text>
1573
+ <Text style={styles.dashboardGridLbl}>Errors</Text>
1574
+ </View>
801
1575
  </View>
802
- </View>
803
- </TouchableScale>
1576
+ </TouchableScale>)}
804
1577
 
805
1578
  {/* Module 3: Analytics */}
806
- <TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('analytics')}>
807
- <View style={styles.dashboardModuleHeader}>
808
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
809
- <AnalyticsIcon color="#EA580C" size={18}/>
810
- <Text style={styles.dashboardModuleTitle}>Analytics Events</Text>
811
- </View>
812
- <Text style={styles.dashboardModuleGoText}>View Details →</Text>
813
- </View>
814
- <View style={styles.dashboardModuleGrid}>
815
- <View style={styles.dashboardGridItem}>
816
- <Text style={styles.dashboardGridVal}>{analyticsTotal}</Text>
817
- <Text style={styles.dashboardGridLbl}>Total Events</Text>
818
- </View>
819
- <View style={styles.dashboardGridItem}>
820
- <Text style={[styles.dashboardGridVal, { color: '#EA580C' }]}>{uniqueEvents}</Text>
821
- <Text style={styles.dashboardGridLbl}>Unique Names</Text>
822
- </View>
823
- <View style={styles.dashboardGridItem}>
824
- <Text style={styles.dashboardGridVal}>{screenViews}</Text>
825
- <Text style={styles.dashboardGridLbl}>Screen Views</Text>
1579
+ {tabVisibility.analytics && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('analytics')}>
1580
+ <View style={styles.dashboardModuleHeader}>
1581
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1582
+ <AnalyticsIcon color="#EA580C" size={18}/>
1583
+ <Text style={styles.dashboardModuleTitle}>Analytics Events</Text>
1584
+ </View>
1585
+ <Text style={styles.dashboardModuleGoText}>View Details →</Text>
826
1586
  </View>
827
- <View style={styles.dashboardGridItem}>
828
- <Text style={styles.dashboardGridVal}>
829
- {analyticsTotal > 0 ? Math.round(analyticsTotal / Math.max(1, logs.length / 5)) : 0}
830
- </Text>
831
- <Text style={styles.dashboardGridLbl}>Events Ratio</Text>
1587
+ <View style={styles.dashboardModuleGrid}>
1588
+ <View style={styles.dashboardGridItem}>
1589
+ <Text style={styles.dashboardGridVal}>{analyticsTotal}</Text>
1590
+ <Text style={styles.dashboardGridLbl}>Total Events</Text>
1591
+ </View>
1592
+ <View style={styles.dashboardGridItem}>
1593
+ <Text style={[styles.dashboardGridVal, { color: '#EA580C' }]}>{uniqueEvents}</Text>
1594
+ <Text style={styles.dashboardGridLbl}>Unique Names</Text>
1595
+ </View>
1596
+ <View style={styles.dashboardGridItem}>
1597
+ <Text style={styles.dashboardGridVal}>{screenViews}</Text>
1598
+ <Text style={styles.dashboardGridLbl}>Screen Views</Text>
1599
+ </View>
1600
+ <View style={styles.dashboardGridItem}>
1601
+ <Text style={styles.dashboardGridVal}>
1602
+ {analyticsTotal > 0 ? Math.round(analyticsTotal / Math.max(1, logs.length / 5)) : 0}
1603
+ </Text>
1604
+ <Text style={styles.dashboardGridLbl}>Events Ratio</Text>
1605
+ </View>
832
1606
  </View>
833
- </View>
834
- </TouchableScale>
1607
+ </TouchableScale>)}
835
1608
 
836
1609
  {/* Module 4: WebView */}
837
- <TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('webview')}>
838
- <View style={styles.dashboardModuleHeader}>
839
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
840
- <GlobeIcon color="#2563EB" size={18}/>
841
- <Text style={styles.dashboardModuleTitle}>WebView Captures</Text>
842
- </View>
843
- <Text style={styles.dashboardModuleGoText}>View Details →</Text>
844
- </View>
845
- <View style={styles.dashboardModuleGrid}>
846
- <View style={styles.dashboardGridItem}>
847
- <Text style={styles.dashboardGridVal}>{webviewTotal}</Text>
848
- <Text style={styles.dashboardGridLbl}>History Size</Text>
849
- </View>
850
- <View style={styles.dashboardGridItem}>
851
- <Text style={[styles.dashboardGridVal, { color: '#16A34A' }]}>Active</Text>
852
- <Text style={styles.dashboardGridLbl}>Status</Text>
1610
+ {tabVisibility.webview && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('webview')}>
1611
+ <View style={styles.dashboardModuleHeader}>
1612
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1613
+ <GlobeIcon color="#2563EB" size={18}/>
1614
+ <Text style={styles.dashboardModuleTitle}>WebView Captures</Text>
1615
+ </View>
1616
+ <Text style={styles.dashboardModuleGoText}>View Details →</Text>
853
1617
  </View>
854
- <View style={styles.dashboardGridItem}>
855
- <Text numberOfLines={1} style={styles.dashboardGridVal}>
856
- {webviewTotal > 0 ? `${webViewNavHistory[0]?.title?.substring(0, 10) ?? ''}...` : '—'}
857
- </Text>
858
- <Text style={styles.dashboardGridLbl}>Last URL</Text>
1618
+ <View style={styles.dashboardModuleGrid}>
1619
+ <View style={styles.dashboardGridItem}>
1620
+ <Text style={styles.dashboardGridVal}>{webviewTotal}</Text>
1621
+ <Text style={styles.dashboardGridLbl}>History Size</Text>
1622
+ </View>
1623
+ <View style={styles.dashboardGridItem}>
1624
+ <Text style={[styles.dashboardGridVal, { color: '#16A34A' }]}>Active</Text>
1625
+ <Text style={styles.dashboardGridLbl}>Status</Text>
1626
+ </View>
1627
+ <View style={styles.dashboardGridItem}>
1628
+ <Text numberOfLines={1} style={styles.dashboardGridVal}>
1629
+ {webviewTotal > 0 ? `${webViewNavHistory[0]?.title?.substring(0, 10) ?? ''}...` : '—'}
1630
+ </Text>
1631
+ <Text style={styles.dashboardGridLbl}>Last URL</Text>
1632
+ </View>
859
1633
  </View>
860
- </View>
861
- </TouchableScale>
1634
+ </TouchableScale>)}
862
1635
 
863
1636
  {/* Module 5: Redux Store */}
864
- <TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('redux')}>
865
- <View style={styles.dashboardModuleHeader}>
866
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
867
- <TerminalIcon color={AppColors.purple} size={18}/>
868
- <Text style={styles.dashboardModuleTitle}>Redux Store State</Text>
1637
+ {tabVisibility.redux && (<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('redux')}>
1638
+ <View style={styles.dashboardModuleHeader}>
1639
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1640
+ <TerminalIcon color={AppColors.purple} size={18}/>
1641
+ <Text style={styles.dashboardModuleTitle}>Redux Store State</Text>
1642
+ </View>
1643
+ <Text style={styles.dashboardModuleGoText}>View Details →</Text>
869
1644
  </View>
870
- <Text style={styles.dashboardModuleGoText}>View Details →</Text>
871
- </View>
872
- {reduxState ? (<View style={{ paddingHorizontal: 12, paddingBottom: 12, gap: 6 }}>
873
- <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 }}>
874
- <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.5 }}>
875
- REDUCER NAME
876
- </Text>
877
- <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.5 }}>
878
- SIZE / FIELDS
1645
+ {reduxState ? (<View style={{ paddingHorizontal: 12, paddingBottom: 12, gap: 6 }}>
1646
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 }}>
1647
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.5 }}>
1648
+ REDUCER NAME
1649
+ </Text>
1650
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: AppColors.grayTextWeak, letterSpacing: 0.5 }}>
1651
+ SIZE / FIELDS
1652
+ </Text>
1653
+ </View>
1654
+ {Object.keys(reduxState).map(key => {
1655
+ const val = reduxState[key];
1656
+ const fieldsCount = typeof val === 'object' && val !== null ? Object.keys(val).length : 0;
1657
+ const sizeStr = getSize(val);
1658
+ return (<View key={key} style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 2 }}>
1659
+ <Text style={{ fontFamily: AppFonts.interMedium, fontSize: 12, color: AppColors.grayTextStrong }}>
1660
+ {key}
1661
+ </Text>
1662
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: AppColors.grayTextWeak }}>
1663
+ {sizeStr} ({fieldsCount} fields)
1664
+ </Text>
1665
+ </View>);
1666
+ })}
1667
+ </View>) : (<View style={{ padding: 12, alignItems: 'center' }}>
1668
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 12, color: AppColors.grayTextWeak }}>
1669
+ No connected Redux store.
879
1670
  </Text>
880
- </View>
881
- {Object.keys(reduxState).map(key => {
882
- const val = reduxState[key];
883
- const fieldsCount = typeof val === 'object' && val !== null ? Object.keys(val).length : 0;
884
- const sizeStr = getSize(val);
885
- return (<View key={key} style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 2 }}>
886
- <Text style={{ fontFamily: AppFonts.interMedium, fontSize: 12, color: AppColors.grayTextStrong }}>
887
- {key}
888
- </Text>
889
- <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: AppColors.grayTextWeak }}>
890
- {sizeStr} ({fieldsCount} fields)
891
- </Text>
892
- </View>);
893
- })}
894
- </View>) : (<View style={{ padding: 12, alignItems: 'center' }}>
895
- <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 12, color: AppColors.grayTextWeak }}>
896
- No connected Redux store.
897
- </Text>
898
- </View>)}
899
- </TouchableScale>
1671
+ </View>)}
1672
+ </TouchableScale>)}
900
1673
  </View>);
901
1674
  };
902
1675
  const renderReduxTab = () => {
@@ -918,37 +1691,52 @@ const NetworkInspector = () => {
918
1691
  <Text style={styles.emptySub}>Connected store state is empty.</Text>
919
1692
  </View>);
920
1693
  }
1694
+ // Build hierarchical tree: Store -> Reducers -> Action -> Data
1695
+ const lastActionMap = getLastActionForReducer();
921
1696
  return (<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingBottom: 24 }}>
1697
+ {/* Top Summary Card */}
922
1698
  <View style={{
1699
+ backgroundColor: AppColors.primaryLight,
1700
+ borderRadius: 12,
1701
+ borderWidth: 1,
1702
+ borderColor: AppColors.grayBorderSecondary,
1703
+ padding: 14,
1704
+ marginHorizontal: 16,
1705
+ marginTop: 12,
1706
+ marginBottom: 12,
923
1707
  flexDirection: 'row',
924
1708
  alignItems: 'center',
925
- justifyContent: 'space-between',
926
- paddingHorizontal: 16,
927
- paddingVertical: 12,
928
- borderBottomWidth: 1,
929
- borderBottomColor: AppColors.dividerColor,
930
- backgroundColor: AppColors.primaryLight,
1709
+ gap: 12,
931
1710
  }}>
932
- <Text style={{
933
- fontFamily: AppFonts.interBold,
934
- color: AppColors.grayTextStrong,
935
- fontSize: 12,
936
- textTransform: 'uppercase',
937
- letterSpacing: 0.6,
1711
+ <View style={{
1712
+ width: 44,
1713
+ height: 44,
1714
+ borderRadius: 10,
1715
+ backgroundColor: AppColors.purpleShade50,
1716
+ alignItems: 'center',
1717
+ justifyContent: 'center',
938
1718
  }}>
939
- Redux Store ({reducerKeys.length} Reducers)
940
- </Text>
1719
+ <TerminalIcon color={AppColors.purple} size={20}/>
1720
+ </View>
1721
+ <View style={{ flex: 1 }}>
1722
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.primaryBlack }}>
1723
+ Redux Store Snapshot
1724
+ </Text>
1725
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: AppColors.grayText, marginTop: 2 }}>
1726
+ Total size: {getSize(reduxState)} • {reducerKeys.length} Reducers
1727
+ </Text>
1728
+ </View>
941
1729
  <CopyButton value={() => reduxState} label="Overall Store"/>
942
1730
  </View>
943
1731
 
1732
+ {/* Search Bar */}
944
1733
  <View style={{
945
1734
  flexDirection: 'row',
946
1735
  alignItems: 'center',
947
1736
  backgroundColor: AppColors.grayBackground,
948
1737
  borderRadius: 8,
949
1738
  marginHorizontal: 16,
950
- marginTop: 12,
951
- marginBottom: 8,
1739
+ marginBottom: 12,
952
1740
  paddingHorizontal: 10,
953
1741
  borderWidth: 1,
954
1742
  borderColor: AppColors.dividerColor,
@@ -966,66 +1754,24 @@ const NetworkInspector = () => {
966
1754
  </Pressable>)}
967
1755
  </View>
968
1756
 
969
- {reducerKeys.map(key => {
970
- const isExpanded = expandedReducers[key];
971
- const val = reduxState[key];
972
- return (<View key={key} style={{
973
- backgroundColor: AppColors.primaryLight,
974
- borderBottomWidth: 1,
975
- borderBottomColor: AppColors.dividerColor,
976
- }}>
977
- <Pressable onPress={() => {
978
- setExpandedReducers(prev => ({
979
- ...prev,
980
- [key]: !prev[key],
981
- }));
982
- }} style={{
983
- flexDirection: 'row',
984
- alignItems: 'center',
985
- justifyContent: 'space-between',
986
- paddingHorizontal: 16,
987
- paddingVertical: 12,
988
- }}>
989
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8, flex: 1 }}>
990
- <Animated.View style={{ transform: [{ rotate: isExpanded ? '0deg' : '-90deg' }] }}>
991
- <ChevronIcon color={AppColors.grayTextWeak} size={14}/>
992
- </Animated.View>
993
- <Text style={{
994
- fontFamily: AppFonts.interBold,
995
- fontSize: 13,
996
- color: AppColors.purple,
997
- }}>
998
- {key}
999
- </Text>
1000
- <Text style={{
1001
- fontFamily: AppFonts.interRegular,
1002
- fontSize: 11,
1003
- color: AppColors.grayTextWeak,
1004
- }}>
1005
- ({typeof val === 'object' && val !== null ? `${Object.keys(val).length} fields` : typeof val})
1006
- </Text>
1007
- </View>
1008
- <CopyButton value={() => val} label={`${key} Reducer`}/>
1009
- </Pressable>
1010
-
1011
- {isExpanded && (<View style={{
1012
- backgroundColor: AppColors.grayBackground,
1013
- paddingHorizontal: 12,
1014
- paddingVertical: 8,
1015
- borderTopWidth: 1,
1016
- borderTopColor: AppColors.dividerColor,
1017
- }}>
1018
- <JsonViewer data={val} search={reduxSearch}/>
1019
- </View>)}
1020
- </View>);
1021
- })}
1757
+ {/* Main Tree Card */}
1758
+ <View style={{
1759
+ backgroundColor: AppColors.primaryLight,
1760
+ borderRadius: 12,
1761
+ borderWidth: 1,
1762
+ borderColor: AppColors.grayBorderSecondary,
1763
+ marginHorizontal: 16,
1764
+ padding: 12,
1765
+ }}>
1766
+ <ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>
1767
+ </View>
1022
1768
  </ScrollView>);
1023
1769
  };
1024
1770
  return (<>
1025
1771
  {hasNavigationContext && (<NavigationTracker onStateChange={setNavState}/>)}
1026
1772
  <TouchableScale style={styles.fabWrapper} onPress={() => setVisible(true)} hitSlop={10}>
1027
1773
  <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
1028
- <BrandCircleIcon size={56}/>
1774
+ <BrandCircleIcon size={62}/>
1029
1775
  {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
1030
1776
  styles.fabBadge,
1031
1777
  hasErrors ? styles.fabBadgeError : styles.fabBadgeNormal,
@@ -1070,7 +1816,33 @@ const NetworkInspector = () => {
1070
1816
  <WhiteBackNavigation />
1071
1817
  </TouchableScale>
1072
1818
 
1073
- {selected == null && selectedEvent == null ? (<Text style={styles.headerTitle}>RN-InApp-Inspector</Text>) : null}
1819
+ {selected == null && selectedEvent == null ? (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 14, flex: 1 }}>
1820
+ <View style={{
1821
+ width: 42,
1822
+ height: 42,
1823
+ borderRadius: 10,
1824
+ backgroundColor: 'rgba(255,255,255,0.13)',
1825
+ borderWidth: 1.5,
1826
+ borderColor: 'rgba(255,255,255,0.25)',
1827
+ alignItems: 'center',
1828
+ justifyContent: 'center',
1829
+ shadowColor: '#000',
1830
+ shadowOpacity: 0.15,
1831
+ shadowRadius: 4,
1832
+ shadowOffset: { width: 0, height: 2 },
1833
+ }}>
1834
+ <BrandSquareIcon size={36}/>
1835
+ </View>
1836
+ <View style={{ gap: 3 }}>
1837
+ <Text style={[styles.headerTitle, { fontSize: 17, letterSpacing: 0.2 }]}>RN InApp Inspector</Text>
1838
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
1839
+ <Animated.View style={{ width: 6, height: 6, borderRadius: 3, backgroundColor: '#4ADE80', opacity: activePulseAnim }}/>
1840
+ <Text style={{ fontFamily: AppFonts.interMedium, fontSize: 10, color: 'rgba(255,255,255,0.78)', letterSpacing: 0.3 }}>
1841
+ Active • {Platform.OS === 'ios' ? 'iOS' : 'Android'} (v1.0.10)
1842
+ </Text>
1843
+ </View>
1844
+ </View>
1845
+ </View>) : null}
1074
1846
  </View>
1075
1847
 
1076
1848
  <View style={styles.headerCenter}>
@@ -1146,15 +1918,11 @@ const NetworkInspector = () => {
1146
1918
  </View>
1147
1919
 
1148
1920
  <View style={styles.headerRight}>
1149
- <TouchableScale onPress={() => {
1150
- const newTheme = !isDark;
1151
- setIsDark(newTheme);
1152
- toggleGlobalTheme(newTheme);
1153
- }} hitSlop={15} style={[styles.closeButtonCircle, { marginRight: 8 }]}>
1154
- {isDark ? (<SunIcon color="#FFFFFF" size={16}/>) : (<MoonIcon color="#FFFFFF" size={16}/>)}
1155
- </TouchableScale>
1921
+ {selected == null && selectedEvent == null && (<TouchableScale onPress={() => setSettingsPage('main')} hitSlop={15} style={[styles.closeButtonSquare, { marginRight: 8, backgroundColor: 'rgba(255,255,255,0.15)' }]}>
1922
+ <SettingsIcon color="#FFFFFF" size={16}/>
1923
+ </TouchableScale>)}
1156
1924
 
1157
- <TouchableScale onPress={closeModal} hitSlop={15} style={styles.closeButtonCircle}>
1925
+ <TouchableScale onPress={closeModal} hitSlop={15} style={styles.closeButtonSquare}>
1158
1926
  <CloseWhite size={16}/>
1159
1927
  </TouchableScale>
1160
1928
  </View>
@@ -1162,7 +1930,7 @@ const NetworkInspector = () => {
1162
1930
  </LinearGradient>
1163
1931
 
1164
1932
  {/* ─── Horizontal Scrollable Tab Bar inside Content ─── */}
1165
- {selected == null && selectedEvent == null ? (<View style={styles.tabBarContainer}>
1933
+ {selected == null && selectedEvent == null && settingsPage === null ? (<View style={styles.tabBarContainer}>
1166
1934
  <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingRight: 16 }}>
1167
1935
  {[
1168
1936
  { key: 'insights', label: 'Insights', count: 0, icon: 'insights' },
@@ -1171,10 +1939,12 @@ const NetworkInspector = () => {
1171
1939
  { key: 'analytics', label: 'Analytics', count: analyticsEvents.length, icon: 'analytics' },
1172
1940
  { key: 'webview', label: 'WebView', count: webViewNavHistory.length, icon: 'webview' },
1173
1941
  { key: 'redux', label: 'Redux', count: 0, icon: 'redux' },
1174
- ].map(tab => {
1942
+ ].filter(tab => tabVisibility[tab.key]).map(tab => {
1175
1943
  const isActive = activeTab === tab.key;
1176
1944
  const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
1177
1945
  const countLabel = tab.count > 9 ? '9+' : String(tab.count);
1946
+ const hasUnreadApis = activeTab !== 'apis' && logs.length > lastReadApisCount;
1947
+ const hasUnreadLogs = activeTab !== 'logs' && consoleLogs.length > lastReadLogsCount;
1178
1948
  return (<TouchableScale key={tab.key} onPress={() => {
1179
1949
  requestAnimationFrame(() => {
1180
1950
  setActiveTab(tab.key);
@@ -1196,6 +1966,15 @@ const NetworkInspector = () => {
1196
1966
  ]}>
1197
1967
  {tab.label} {tab.count > 0 ? `(${countLabel})` : ''}
1198
1968
  </Text>
1969
+ {((tab.key === 'apis' && hasUnreadApis) || (tab.key === 'logs' && hasUnreadLogs)) && (<Animated.View style={{
1970
+ width: 6,
1971
+ height: 6,
1972
+ borderRadius: 3,
1973
+ backgroundColor: AppColors.errorColor,
1974
+ marginLeft: 4,
1975
+ alignSelf: 'center',
1976
+ transform: [{ scale: unreadPulseAnim }],
1977
+ }}/>)}
1199
1978
  </View>
1200
1979
  </TouchableScale>);
1201
1980
  })}
@@ -1818,156 +2597,250 @@ const NetworkInspector = () => {
1818
2597
  styles.listContent,
1819
2598
  filteredConsoleLogs.length === 0 && { flexGrow: 1 },
1820
2599
  ]} keyboardShouldPersistTaps="handled"/>
1821
- </View>) : activeTab === 'webview' ? (<View style={{ flex: 1 }}>
2600
+ </View>) : activeTab === 'webview' ? (webViewNavHistory.length === 0 ? (<View style={styles.emptyContainer}>
2601
+ <View style={styles.emptyIconWrap}>
2602
+ <GlobeIcon color={AppColors.purple} size={32}/>
2603
+ </View>
2604
+ <Text style={styles.emptyTitle}>No WebView Activity</Text>
2605
+ <Text style={styles.emptySub}>
2606
+ Load a webpage within a connected WebView component to inspect pages, page source, and console logs.
2607
+ </Text>
2608
+ </View>) : (<View style={{ flex: 1 }}>
2609
+ {/* ─── Current Page Address Bar (Now on top) ─── */}
2610
+ {(() => {
2611
+ const currentUrl = webViewNavHistory[0]?.url;
2612
+ if (!currentUrl)
2613
+ return null;
2614
+ return (<View style={{
2615
+ paddingHorizontal: 12,
2616
+ paddingTop: 6,
2617
+ paddingBottom: 6,
2618
+ backgroundColor: AppColors.primaryLight,
2619
+ borderBottomWidth: 1,
2620
+ borderBottomColor: AppColors.dividerColor,
2621
+ }}>
2622
+ <View style={{
2623
+ flexDirection: 'row',
2624
+ alignItems: 'center',
2625
+ backgroundColor: AppColors.grayBackground,
2626
+ borderRadius: 8,
2627
+ borderWidth: 1.5,
2628
+ borderColor: AppColors.grayBorderSecondary,
2629
+ paddingHorizontal: 10,
2630
+ paddingVertical: 5,
2631
+ gap: 8,
2632
+ }}>
2633
+ {/* Left: Lock and HTTPS label */}
2634
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
2635
+ <Text style={{ fontSize: 11 }}>🔒</Text>
2636
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 9.5, color: AppColors.greenColor, letterSpacing: 0.5 }}>HTTPS</Text>
2637
+ </View>
2638
+ <View style={{ width: 1.5, height: 12, backgroundColor: AppColors.grayBorderSecondary }}/>
2639
+
2640
+ {/* Middle: URL text (Address style) */}
2641
+ <View style={{ flex: 1 }}>
2642
+ <HighlightText text={currentUrl} search={webViewSearch} numberOfLines={1} ellipsizeMode="tail" style={{
2643
+ fontFamily: AppFonts.interMedium,
2644
+ fontSize: 11.5,
2645
+ color: AppColors.primaryBlack,
2646
+ }} highlightStyle={styles.highlight} detectLinks={false}/>
2647
+ </View>
2648
+
2649
+ {/* Right: Copy Button */}
2650
+ <CopyButton value={currentUrl} label="URL"/>
2651
+
2652
+ {/* Right: Globe Icon button to open browser */}
2653
+ <TouchableScale onPress={() => Linking.openURL(currentUrl)} hitSlop={8} style={{
2654
+ width: 26,
2655
+ height: 26,
2656
+ borderRadius: 13,
2657
+ backgroundColor: AppColors.grayBackground,
2658
+ borderWidth: 1,
2659
+ borderColor: AppColors.grayBorderSecondary,
2660
+ alignItems: 'center',
2661
+ justifyContent: 'center',
2662
+ }}>
2663
+ <GlobeIcon size={11} color={AppColors.purple}/>
2664
+ </TouchableScale>
2665
+ </View>
2666
+ </View>);
2667
+ })()}
2668
+
2669
+ {/* ─── WebView Sub-Tabs (Now below Address Bar) ─── */}
1822
2670
  <View style={{
1823
- backgroundColor: '#FFFFFF',
2671
+ backgroundColor: AppColors.primaryLight,
1824
2672
  borderBottomWidth: 1,
1825
2673
  borderBottomColor: AppColors.dividerColor,
1826
- paddingBottom: 6,
2674
+ paddingVertical: 6,
1827
2675
  }}>
1828
- {/* ─── WebView Sub-Tabs ─────────────────────────────────────── */}
1829
- <View style={{
1830
- marginHorizontal: 16,
1831
- marginTop: webViewSubTab === 'navigation' ? 12 : 4,
1832
- marginBottom: 8,
1833
- backgroundColor: AppColors.grayBackground,
1834
- borderRadius: 8,
1835
- padding: 4,
2676
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
2677
+ paddingHorizontal: 12,
1836
2678
  flexDirection: 'row',
1837
- borderWidth: 1,
1838
- borderColor: AppColors.grayBorderSecondary,
2679
+ gap: 8,
1839
2680
  }}>
2681
+ {/* Sub-tab 1: Preview */}
1840
2682
  <Pressable style={[
1841
2683
  {
1842
- flex: 1,
1843
- paddingVertical: 8,
1844
- borderRadius: 6,
2684
+ paddingVertical: 6,
2685
+ paddingHorizontal: 14,
2686
+ borderRadius: 8,
1845
2687
  alignItems: 'center',
2688
+ flexDirection: 'row',
2689
+ gap: 6,
2690
+ backgroundColor: 'rgba(0, 0, 0, 0.03)',
2691
+ borderWidth: 1,
2692
+ borderColor: 'transparent',
1846
2693
  },
1847
- webViewSubTab === 'html' && {
1848
- backgroundColor: AppColors.primaryLight,
1849
- shadowColor: '#000',
1850
- shadowOpacity: 0.1,
1851
- shadowRadius: 3,
1852
- shadowOffset: { width: 0, height: 1 },
1853
- elevation: 2,
2694
+ webViewSubTab === 'preview' && {
2695
+ backgroundColor: AppColors.purple,
2696
+ borderColor: AppColors.purple,
1854
2697
  },
1855
- ]} onPress={() => setWebViewSubTab('html')}>
1856
- <Text style={[
2698
+ ]} onPress={() => setWebViewSubTab('preview')}>
2699
+ <EyeIcon color={webViewSubTab === 'preview' ? '#FFFFFF' : AppColors.grayTextWeak} size={13}/>
2700
+ <Text style={{
2701
+ fontFamily: webViewSubTab === 'preview' ? AppFonts.interBold : AppFonts.interMedium,
2702
+ fontSize: 12,
2703
+ color: webViewSubTab === 'preview' ? '#FFFFFF' : AppColors.grayTextStrong,
2704
+ }}>
2705
+ Preview
2706
+ </Text>
2707
+ </Pressable>
2708
+
2709
+ {/* Sub-tab 2: Page Source */}
2710
+ <Pressable style={[
1857
2711
  {
1858
- fontFamily: AppFonts.interMedium,
1859
- fontSize: 12,
1860
- color: AppColors.grayTextStrong,
2712
+ paddingVertical: 6,
2713
+ paddingHorizontal: 14,
2714
+ borderRadius: 8,
2715
+ alignItems: 'center',
2716
+ flexDirection: 'row',
2717
+ gap: 6,
2718
+ backgroundColor: 'rgba(0, 0, 0, 0.03)',
2719
+ borderWidth: 1,
2720
+ borderColor: 'transparent',
1861
2721
  },
1862
2722
  webViewSubTab === 'html' && {
1863
- fontFamily: AppFonts.interBold,
1864
- color: '#475569',
2723
+ backgroundColor: AppColors.purple,
2724
+ borderColor: AppColors.purple,
1865
2725
  },
1866
- ]}>
1867
- HTML Source
2726
+ ]} onPress={() => setWebViewSubTab('html')}>
2727
+ <HtmlIcon color={webViewSubTab === 'html' ? '#FFFFFF' : AppColors.grayTextWeak} size={13}/>
2728
+ <Text style={{
2729
+ fontFamily: webViewSubTab === 'html' ? AppFonts.interBold : AppFonts.interMedium,
2730
+ fontSize: 12,
2731
+ color: webViewSubTab === 'html' ? '#FFFFFF' : AppColors.grayTextStrong,
2732
+ }}>
2733
+ Page Source
1868
2734
  </Text>
1869
2735
  </Pressable>
2736
+
2737
+ {/* Sub-tab 3: History */}
1870
2738
  <Pressable style={[
1871
2739
  {
1872
- flex: 1,
1873
- paddingVertical: 8,
1874
- borderRadius: 6,
2740
+ paddingVertical: 6,
2741
+ paddingHorizontal: 14,
2742
+ borderRadius: 8,
1875
2743
  alignItems: 'center',
2744
+ flexDirection: 'row',
2745
+ gap: 6,
2746
+ backgroundColor: 'rgba(0, 0, 0, 0.03)',
2747
+ borderWidth: 1,
2748
+ borderColor: 'transparent',
1876
2749
  },
1877
2750
  webViewSubTab === 'navigation' && {
1878
- backgroundColor: AppColors.primaryLight,
1879
- shadowColor: '#000',
1880
- shadowOpacity: 0.1,
1881
- shadowRadius: 3,
1882
- shadowOffset: { width: 0, height: 1 },
1883
- elevation: 2,
2751
+ backgroundColor: AppColors.purple,
2752
+ borderColor: AppColors.purple,
1884
2753
  },
1885
2754
  ]} onPress={() => setWebViewSubTab('navigation')}>
1886
- <Text style={[
2755
+ <ClockIcon color={webViewSubTab === 'navigation' ? '#FFFFFF' : AppColors.grayTextWeak} size={13}/>
2756
+ <Text style={{
2757
+ fontFamily: webViewSubTab === 'navigation' ? AppFonts.interBold : AppFonts.interMedium,
2758
+ fontSize: 12,
2759
+ color: webViewSubTab === 'navigation' ? '#FFFFFF' : AppColors.grayTextStrong,
2760
+ }}>
2761
+ History ({webViewNavHistory.length})
2762
+ </Text>
2763
+ </Pressable>
2764
+
2765
+ {/* Sub-tab 4: Console */}
2766
+ <Pressable style={[
1887
2767
  {
1888
- fontFamily: AppFonts.interMedium,
1889
- fontSize: 12,
1890
- color: AppColors.grayTextStrong,
2768
+ paddingVertical: 6,
2769
+ paddingHorizontal: 14,
2770
+ borderRadius: 8,
2771
+ alignItems: 'center',
2772
+ flexDirection: 'row',
2773
+ gap: 6,
2774
+ backgroundColor: 'rgba(0, 0, 0, 0.03)',
2775
+ borderWidth: 1,
2776
+ borderColor: 'transparent',
1891
2777
  },
1892
- webViewSubTab === 'navigation' && {
1893
- fontFamily: AppFonts.interBold,
1894
- color: '#475569',
2778
+ webViewSubTab === 'console' && {
2779
+ backgroundColor: AppColors.purple,
2780
+ borderColor: AppColors.purple,
1895
2781
  },
1896
- ]}>
1897
- Nav History ({webViewNavHistory.length})
2782
+ ]} onPress={() => setWebViewSubTab('console')}>
2783
+ <TerminalIcon color={webViewSubTab === 'console' ? '#FFFFFF' : AppColors.grayTextWeak} size={13}/>
2784
+ <Text style={{
2785
+ fontFamily: webViewSubTab === 'console' ? AppFonts.interBold : AppFonts.interMedium,
2786
+ fontSize: 12,
2787
+ color: webViewSubTab === 'console' ? '#FFFFFF' : AppColors.grayTextStrong,
2788
+ }}>
2789
+ Console ({webViewLogs.length})
1898
2790
  </Text>
1899
2791
  </Pressable>
1900
- </View>
2792
+ </ScrollView>
1901
2793
  </View>
1902
2794
 
1903
- {/* ─── Current Page Address Bar (Always visible at the top) ─── */}
1904
- {(() => {
1905
- const currentUrl = webViewNavHistory[0]?.url;
1906
- if (!currentUrl)
1907
- return null;
1908
- return (<View style={{
1909
- paddingHorizontal: 16,
1910
- paddingTop: 10,
1911
- paddingBottom: 10,
1912
- backgroundColor: '#FFFFFF',
1913
- borderBottomWidth: 1,
1914
- borderBottomColor: AppColors.dividerColor,
1915
- }}>
1916
- <Text style={{
1917
- fontFamily: AppFonts.interBold,
1918
- fontSize: 10,
1919
- color: '#64748B',
1920
- textTransform: 'uppercase',
1921
- letterSpacing: 0.5,
1922
- marginBottom: 6,
1923
- }}>
1924
- Currently debugging for URL
1925
- </Text>
1926
- <View style={{
1927
- flexDirection: 'row',
1928
- alignItems: 'center',
1929
- backgroundColor: '#F1F5F9',
1930
- borderRadius: 20,
1931
- borderWidth: 1,
1932
- borderColor: '#E2E8F0',
1933
- paddingHorizontal: 12,
1934
- paddingVertical: 6,
1935
- gap: 8,
1936
- }}>
1937
- {/* Left: Clickable Globe Icon to open browser */}
1938
- <TouchableScale onPress={() => Linking.openURL(currentUrl)} hitSlop={8} style={{
1939
- width: 24,
1940
- height: 24,
1941
- borderRadius: 12,
1942
- backgroundColor: '#E2E8F0',
1943
- alignItems: 'center',
1944
- justifyContent: 'center',
1945
- }} children={<GlobeIcon size={12} color="#475569"/>}/>
1946
-
1947
- {/* Middle: URL text (Address style) */}
1948
- <View style={{ flex: 1 }}>
1949
- <HighlightText text={currentUrl} search={webViewSearch} numberOfLines={1} ellipsizeMode="tail" style={{
1950
- fontFamily: AppFonts.interMedium,
1951
- fontSize: 11,
1952
- color: '#475569',
1953
- }} highlightStyle={styles.highlight} detectLinks={false}/>
1954
- </View>
1955
-
1956
- {/* Right: Copy Button */}
1957
- <CopyButton value={currentUrl} label="URL"/>
1958
- </View>
1959
- </View>);
1960
- })()}
1961
-
1962
2795
  {webViewSubTab === 'html' ? (<View style={{ flex: 1 }}>
1963
2796
  {webViewHtml || webViewCss || webViewJs ? (<View style={{ flex: 1 }}>
2797
+ {/* Clear Inspect Banner */}
2798
+ {inspectedElement && (<View style={{
2799
+ flexDirection: 'row',
2800
+ alignItems: 'center',
2801
+ justifyContent: 'space-between',
2802
+ backgroundColor: AppColors.purpleShade50,
2803
+ paddingHorizontal: 12,
2804
+ paddingVertical: 6,
2805
+ borderBottomWidth: 1,
2806
+ borderBottomColor: AppColors.dividerColor,
2807
+ }}>
2808
+ <Text style={{
2809
+ fontFamily: AppFonts.interMedium,
2810
+ fontSize: 11.5,
2811
+ color: AppColors.purple,
2812
+ flex: 1,
2813
+ }}>
2814
+ Inspecting element:{' '}
2815
+ <Text style={{ fontFamily: AppFonts.interBold }}>
2816
+ &lt;{inspectedElement.tagName}
2817
+ {inspectedElement.id ? ` id="${inspectedElement.id}"` : ''}
2818
+ {inspectedElement.className ? ` class="${inspectedElement.className.trim().split(/\s+/)[0]}"` : ''}
2819
+ &gt;
2820
+ </Text>
2821
+ </Text>
2822
+ <Pressable onPress={() => setInspectedElement(null)} style={{
2823
+ paddingHorizontal: 8,
2824
+ paddingVertical: 4,
2825
+ }}>
2826
+ <Text style={{
2827
+ fontFamily: AppFonts.interBold,
2828
+ fontSize: 11,
2829
+ color: AppColors.purple,
2830
+ }}>
2831
+ Clear Inspect
2832
+ </Text>
2833
+ </Pressable>
2834
+ </View>)}
2835
+
1964
2836
  {/* Inner sub-tabs inside HTML source view */}
1965
2837
  <View style={{
1966
2838
  flexDirection: 'row',
1967
2839
  borderBottomWidth: 1,
1968
- borderBottomColor: '#E2E8F0',
1969
- backgroundColor: '#FFFFFF',
1970
- paddingHorizontal: 16,
2840
+ borderBottomColor: AppColors.dividerColor,
2841
+ backgroundColor: AppColors.primaryLight,
2842
+ paddingHorizontal: 12,
2843
+ gap: 12,
1971
2844
  }}>
1972
2845
  {['html', 'css', 'javascript'].map(tab => {
1973
2846
  const active = htmlSubTab === tab;
@@ -1975,23 +2848,34 @@ const NetworkInspector = () => {
1975
2848
  ? 'HTML'
1976
2849
  : tab === 'css'
1977
2850
  ? 'CSS'
1978
- : 'Javascript';
2851
+ : 'JavaScript';
2852
+ const activeColor = tab === 'html'
2853
+ ? '#EA580C' // Orange
2854
+ : tab === 'css'
2855
+ ? '#2563EB' // Blue
2856
+ : '#D97706'; // Dark Yellow/Amber
1979
2857
  return (<Pressable key={tab} onPress={() => setHtmlSubTab(tab)} style={{
1980
- paddingVertical: 10,
1981
- marginRight: 16,
2858
+ paddingVertical: 8,
2859
+ paddingHorizontal: 4,
1982
2860
  borderBottomWidth: 2,
1983
2861
  borderBottomColor: active
1984
- ? AppColors.purple
2862
+ ? activeColor
1985
2863
  : 'transparent',
2864
+ flexDirection: 'row',
2865
+ alignItems: 'center',
2866
+ gap: 4,
1986
2867
  }}>
2868
+ {tab === 'html' && (<HtmlIcon color={active ? activeColor : AppColors.grayTextWeak} size={14}/>)}
2869
+ {tab === 'css' && (<CssIcon color={active ? activeColor : AppColors.grayTextWeak} size={14}/>)}
2870
+ {tab === 'javascript' && (<JsIcon color={active ? activeColor : AppColors.grayTextWeak} size={14}/>)}
1987
2871
  <Text style={{
1988
2872
  fontFamily: active
1989
2873
  ? AppFonts.interBold
1990
2874
  : AppFonts.interMedium,
1991
- fontSize: 12,
2875
+ fontSize: 13,
1992
2876
  color: active
1993
- ? AppColors.purple
1994
- : '#64748B',
2877
+ ? activeColor
2878
+ : AppColors.grayTextWeak,
1995
2879
  }}>
1996
2880
  {label}
1997
2881
  </Text>
@@ -2001,21 +2885,21 @@ const NetworkInspector = () => {
2001
2885
  <View style={{ flex: 1, padding: 12 }}>
2002
2886
  {!isHtmlTabReady ? (<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
2003
2887
  <ActivityIndicator size="large" color={AppColors.purple}/>
2004
- </View>) : htmlSubTab === 'html' ? (webViewHtml ? (<CodeSnippet code={webViewHtml} language="html"/>) : (<Text style={{
2888
+ </View>) : htmlSubTab === 'html' ? (webViewHtml ? (<CodeSnippet code={webViewHtml} language="html" search={getSearchTermForTab()}/>) : (<Text style={{
2005
2889
  fontFamily: 'monospace',
2006
2890
  fontSize: 11,
2007
2891
  color: '#94A3B8',
2008
2892
  padding: 12,
2009
2893
  }}>
2010
2894
  No HTML content captured.
2011
- </Text>)) : htmlSubTab === 'css' ? (webViewCss ? (<CodeSnippet code={webViewCss} language="css"/>) : (<Text style={{
2895
+ </Text>)) : htmlSubTab === 'css' ? (webViewCss ? (<CodeSnippet code={webViewCss} language="css" search={getSearchTermForTab()}/>) : (<Text style={{
2012
2896
  fontFamily: 'monospace',
2013
2897
  fontSize: 11,
2014
2898
  color: '#94A3B8',
2015
2899
  padding: 12,
2016
2900
  }}>
2017
2901
  No CSS styles detected on this page.
2018
- </Text>)) : webViewJs ? (<CodeSnippet code={webViewJs} language="javascript"/>) : (<Text style={{
2902
+ </Text>)) : webViewJs ? (<CodeSnippet code={webViewJs} language="javascript" search={getSearchTermForTab()}/>) : (<Text style={{
2019
2903
  fontFamily: 'monospace',
2020
2904
  fontSize: 11,
2021
2905
  color: '#94A3B8',
@@ -2036,7 +2920,7 @@ const NetworkInspector = () => {
2036
2920
  or Javascript source.
2037
2921
  </Text>
2038
2922
  </View>)}
2039
- </View>) : (<FlatList data={filteredNavHistory} keyExtractor={(item, index) => `${index}-${item.timestamp}`} ListHeaderComponent={<View style={{
2923
+ </View>) : webViewSubTab === 'navigation' ? (<FlatList data={filteredNavHistory} keyExtractor={(item, index) => `${index}-${item.timestamp}`} style={{ flex: 1, backgroundColor: AppColors.grayBackground }} ListHeaderComponent={<View style={{
2040
2924
  paddingHorizontal: 16,
2041
2925
  paddingTop: 12,
2042
2926
  paddingBottom: 8,
@@ -2051,191 +2935,304 @@ const NetworkInspector = () => {
2051
2935
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
2052
2936
  };
2053
2937
  return (<View style={{
2054
- paddingHorizontal: 16,
2055
- paddingVertical: 8,
2056
- backgroundColor: isLatest ? '#F1F5F9' : '#FFFFFF',
2057
- borderBottomWidth: 1,
2058
- borderBottomColor: '#E2E8F0',
2938
+ marginHorizontal: 16,
2939
+ marginVertical: 6,
2940
+ borderRadius: 12,
2941
+ borderWidth: 1,
2942
+ borderColor: AppColors.grayBorderSecondary,
2943
+ backgroundColor: isLatest ? AppColors.purpleShade50 : AppColors.primaryLight,
2944
+ padding: 14,
2059
2945
  flexDirection: 'row',
2060
2946
  alignItems: 'center',
2061
2947
  justifyContent: 'space-between',
2062
2948
  gap: 12,
2949
+ shadowColor: '#000000',
2950
+ shadowOffset: { width: 0, height: 2 },
2951
+ shadowOpacity: isDark ? 0.2 : 0.04,
2952
+ shadowRadius: 4,
2953
+ elevation: 2,
2063
2954
  }}>
2064
- <View style={{ flex: 1, gap: 2 }}>
2955
+ <View style={{ flex: 1, gap: 8 }}>
2956
+ {/* Top row: Title and Badge */}
2065
2957
  <View style={{
2066
2958
  flexDirection: 'row',
2067
2959
  alignItems: 'center',
2068
- gap: 6,
2960
+ gap: 8,
2069
2961
  flexWrap: 'wrap',
2070
2962
  }}>
2071
2963
  <Text numberOfLines={1} ellipsizeMode="tail" style={{
2072
2964
  fontFamily: AppFonts.interBold,
2073
- fontSize: 13,
2074
- color: '#334155',
2965
+ fontSize: 14,
2966
+ color: AppColors.primaryBlack,
2075
2967
  flexShrink: 1,
2076
2968
  }}>
2077
2969
  {item.title || 'Untitled Page'}
2078
2970
  </Text>
2079
2971
  {isLatest && (<View style={{
2080
- backgroundColor: '#E2E8F0',
2081
- paddingHorizontal: 6,
2082
- paddingVertical: 2,
2083
- borderRadius: 4,
2972
+ flexDirection: 'row',
2973
+ alignItems: 'center',
2974
+ gap: 4,
2975
+ backgroundColor: AppColors.greenStatus,
2976
+ paddingHorizontal: 8,
2977
+ paddingVertical: 3,
2978
+ borderRadius: 12,
2084
2979
  }}>
2980
+ <View style={{
2981
+ width: 6,
2982
+ height: 6,
2983
+ borderRadius: 3,
2984
+ backgroundColor: AppColors.greenBaggageText,
2985
+ }}/>
2085
2986
  <Text style={{
2086
2987
  fontFamily: AppFonts.interBold,
2087
- fontSize: 9,
2088
- color: '#475569',
2988
+ fontSize: 9.5,
2989
+ color: AppColors.greenBaggageText,
2089
2990
  }}>
2090
2991
  Active
2091
2992
  </Text>
2092
2993
  </View>)}
2093
2994
  </View>
2094
- <HighlightText text={item.url} search={webViewSearch} numberOfLines={3} ellipsizeMode="tail" style={{
2995
+
2996
+ {/* Middle row: URL with Globe Icon */}
2997
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
2998
+ <GlobeIcon size={12} color={AppColors.grayTextWeak}/>
2999
+ <HighlightText text={item.url} search={webViewSearch} numberOfLines={2} ellipsizeMode="tail" style={{
2095
3000
  fontFamily: AppFonts.interRegular,
2096
- fontSize: 11,
2097
- color: '#64748B',
2098
- flexShrink: 1,
3001
+ fontSize: 12,
3002
+ color: AppColors.grayText,
3003
+ flex: 1,
2099
3004
  }} highlightStyle={styles.highlight} detectLinks={true}/>
2100
- <Text style={{
2101
- fontFamily: AppFonts.interRegular,
2102
- fontSize: 10,
2103
- color: '#94A3B8',
2104
- }}>
2105
- {formatNavTime(item.timestamp)}
2106
- </Text>
3005
+ </View>
3006
+
3007
+ {/* Bottom row: Time */}
3008
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
3009
+ <ClockIcon size={11} color={AppColors.grayTextWeak}/>
3010
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 11, color: AppColors.grayTextWeak }}>
3011
+ {formatNavTime(item.timestamp)}
3012
+ </Text>
3013
+ </View>
2107
3014
  </View>
2108
- <CopyButton value={item.url} label="Copy URL"/>
3015
+ <CopyButton value={item.url} label="URL"/>
2109
3016
  </View>);
2110
- }} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={false}/>} contentContainerStyle={[
3017
+ }} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={webViewSearch.length > 0}/>} contentContainerStyle={[
2111
3018
  styles.listContent,
2112
3019
  filteredNavHistory.length === 0 && { flexGrow: 1 },
2113
- ]} keyboardShouldPersistTaps="handled"/>)}
2114
- </View>) : activeTab === 'redux' ? (renderReduxTab()) : (<View style={{ flex: 1 }}>
3020
+ ]} keyboardShouldPersistTaps="handled"/>) : webViewSubTab === 'console' ? (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
3021
+ {webViewLogs.length > 0 ? (<FlatList data={webViewLogs} keyExtractor={(item) => String(item.id)} style={{ flex: 1 }} ListHeaderComponent={<View style={{ paddingHorizontal: 16, paddingTop: 12, paddingBottom: 8, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
3022
+ <Text style={styles.resultCount}>
3023
+ Console Logs ({webViewLogs.length})
3024
+ </Text>
3025
+ <TouchableScale onPress={() => clearWebViewData()} style={{ padding: 6, borderRadius: 6, backgroundColor: AppColors.primaryLight, borderWidth: 1, borderColor: AppColors.grayBorderSecondary }}>
3026
+ <TrashIcon color={AppColors.errorColor} size={14}/>
3027
+ </TouchableScale>
3028
+ </View>} renderItem={({ item }) => {
3029
+ const logColor = item.type === 'error' ? AppColors.errorColor :
3030
+ item.type === 'warn' ? AppColors.warningIconGold :
3031
+ item.type === 'info' ? AppColors.skyBlue :
3032
+ AppColors.grayTextWeak;
3033
+ const bgColor = item.type === 'error' ? 'rgba(255, 46, 87, 0.06)' :
3034
+ item.type === 'warn' ? 'rgba(191, 162, 82, 0.08)' :
3035
+ AppColors.primaryLight;
3036
+ const d = new Date(item.timestamp);
3037
+ const timeStr = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
3038
+ return (<View style={{
3039
+ marginHorizontal: 12,
3040
+ marginVertical: 3,
3041
+ borderRadius: 8,
3042
+ borderWidth: 1,
3043
+ borderColor: AppColors.grayBorderSecondary,
3044
+ borderLeftWidth: 3,
3045
+ borderLeftColor: logColor,
3046
+ backgroundColor: bgColor,
3047
+ padding: 10,
3048
+ flexDirection: 'row',
3049
+ gap: 8,
3050
+ alignItems: 'flex-start',
3051
+ }}>
3052
+ <View style={{ paddingTop: 1 }}>
3053
+ <TerminalIcon color={logColor} size={11}/>
3054
+ </View>
3055
+ <View style={{ flex: 1, gap: 3 }}>
3056
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
3057
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10, color: logColor, textTransform: 'uppercase', letterSpacing: 0.5 }}>
3058
+ {item.type}
3059
+ </Text>
3060
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 10, color: AppColors.grayTextWeak }}>
3061
+ {timeStr}
3062
+ </Text>
3063
+ </View>
3064
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 12, color: AppColors.primaryBlack, lineHeight: 16 }}>
3065
+ {item.message}
3066
+ </Text>
3067
+ </View>
3068
+ </View>);
3069
+ }} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={7} contentContainerStyle={[styles.listContent, webViewLogs.length === 0 && { flexGrow: 1 }]}/>) : (<View style={styles.emptyContainer}>
3070
+ <View style={styles.emptyIconWrap}>
3071
+ <TerminalIcon color={AppColors.purple} size={32}/>
3072
+ </View>
3073
+ <Text style={styles.emptyTitle}>No Console Logs</Text>
3074
+ <Text style={styles.emptySub}>
3075
+ Console logs from the WebView will appear here.
3076
+ </Text>
3077
+ </View>)}
3078
+ </View>) : (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
3079
+ {webViewHtml ? (OriginalWebView ? (<OriginalWebView source={{ html: webViewHtml, baseUrl: webViewHtmlUrl }} injectedJavaScript={previewInspectScript} onMessage={(event) => {
3080
+ try {
3081
+ const data = JSON.parse(event.nativeEvent.data);
3082
+ if (data.type === 'preview-inspect') {
3083
+ setInspectedElement({
3084
+ tagName: data.tagName,
3085
+ id: data.id,
3086
+ className: data.className,
3087
+ searchStr: data.searchStr,
3088
+ });
3089
+ setWebViewSubTab('html');
3090
+ setHtmlSubTab('html');
3091
+ }
3092
+ }
3093
+ catch (err) { }
3094
+ }} style={{ flex: 1 }}/>) : (<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
3095
+ <Text style={{ color: AppColors.grayText, textAlign: 'center', fontFamily: AppFonts.interMedium }}>
3096
+ react-native-webview is not installed in the target project. Install it to enable Preview mode.
3097
+ </Text>
3098
+ </View>)) : (<View style={styles.emptyContainer}>
3099
+ <View style={styles.emptyIconWrap}>
3100
+ <GlobeIcon color={AppColors.purple} size={32}/>
3101
+ </View>
3102
+ <Text style={styles.emptyTitle}>
3103
+ No Preview Available
3104
+ </Text>
3105
+ <Text style={styles.emptySub}>
3106
+ Load a page in the WebView to see its visual preview.
3107
+ </Text>
3108
+ </View>)}
3109
+ </View>)}
3110
+ </View>)) : activeTab === 'redux' ? (renderReduxTab()) : (<View style={{ flex: 1 }}>
2115
3111
  {/* Non-scrollable details header */}
2116
3112
  <View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
2117
- {(() => {
2118
- const routeInfo = logRouteMapRef.current.get(selected.id);
2119
- const screenPath = routeInfo && routeInfo.path !== 'Navigators'
2120
- ? routeInfo.path.split(' ')
2121
- : [];
2122
- const parts = ['APIs', ...screenPath];
2123
- return (<View style={{
2124
- flexDirection: 'row',
2125
- alignItems: 'center',
2126
- backgroundColor: AppColors.primaryLight,
2127
- borderRadius: 8,
2128
- paddingVertical: 8,
2129
- paddingHorizontal: 12,
2130
- borderWidth: 1,
2131
- borderColor: AppColors.dividerColor,
2132
- marginBottom: 12,
2133
- marginTop: 4,
2134
- }}>
2135
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
3113
+ <View style={styles.detailInfoBar}>
3114
+ {(() => {
3115
+ let hostStr = '';
3116
+ let pathStr = detailDisplayUrl;
3117
+ let queryStr = '';
3118
+ try {
3119
+ // Simple parsing fallback for React Native environments
3120
+ const qIndex = detailDisplayUrl.indexOf('?');
3121
+ let cleanUrlForParsing = detailDisplayUrl;
3122
+ if (qIndex !== -1) {
3123
+ pathStr = detailDisplayUrl.substring(0, qIndex);
3124
+ queryStr = detailDisplayUrl.substring(qIndex);
3125
+ cleanUrlForParsing = pathStr;
3126
+ }
3127
+ const schemeIndex = cleanUrlForParsing.indexOf('://');
3128
+ if (schemeIndex !== -1) {
3129
+ const withoutScheme = cleanUrlForParsing.substring(schemeIndex + 3);
3130
+ const firstSlash = withoutScheme.indexOf('/');
3131
+ if (firstSlash !== -1) {
3132
+ hostStr = withoutScheme.substring(0, firstSlash);
3133
+ pathStr = withoutScheme.substring(firstSlash);
3134
+ }
3135
+ else {
3136
+ hostStr = withoutScheme;
3137
+ pathStr = '/';
3138
+ }
3139
+ }
3140
+ }
3141
+ catch (e) { }
3142
+ return (<>
3143
+ <View style={styles.detailInfoTop}>
3144
+ <View style={{
2136
3145
  flexDirection: 'row',
2137
3146
  alignItems: 'center',
2138
- gap: 6,
3147
+ gap: 8,
2139
3148
  }}>
2140
- {parts.map((part, index) => {
2141
- const isLast = index === parts.length - 1;
2142
- return (<React.Fragment key={index}>
2143
- {index > 0 && (<Text style={{ color: AppColors.grayTextWeak, fontSize: 11, marginHorizontal: 2 }}>
2144
- /
2145
- </Text>)}
2146
- <View style={isLast
2147
- ? {
2148
- backgroundColor: `${AppColors.purple}12`,
2149
- paddingHorizontal: 8,
2150
- paddingVertical: 3,
2151
- borderRadius: 6,
2152
- }
2153
- : {
2154
- paddingHorizontal: 4,
2155
- paddingVertical: 2,
2156
- }}>
2157
- <Text style={{
2158
- fontFamily: isLast ? AppFonts.interBold : AppFonts.interMedium,
2159
- fontSize: 11.5,
2160
- color: isLast ? AppColors.purple : AppColors.grayText,
2161
- }}>
2162
- {part}
3149
+ <View style={[
3150
+ styles.methodBadge,
3151
+ {
3152
+ backgroundColor: `${METHOD_COLORS[selected.method] ??
3153
+ METHOD_COLORS.ALL}15`,
3154
+ },
3155
+ ]}>
3156
+ <Text style={[
3157
+ styles.methodBadgeText,
3158
+ {
3159
+ color: METHOD_COLORS[selected.method] ??
3160
+ METHOD_COLORS.ALL,
3161
+ },
3162
+ ]}>
3163
+ {selected.method}
3164
+ </Text>
3165
+ </View>
3166
+
3167
+ {selected.status != null && (<View style={[
3168
+ styles.chip,
3169
+ {
3170
+ backgroundColor: selected.status === 0
3171
+ ? `${AppColors.errorColor}15`
3172
+ : `${getStatusColor(selected.status)}15`,
3173
+ borderColor: selected.status === 0
3174
+ ? `${AppColors.errorColor}40`
3175
+ : `${getStatusColor(selected.status)}40`,
3176
+ },
3177
+ ]}>
3178
+ {selected.status === 0 ? (<FailIcon size={8} color={AppColors.errorColor}/>) : (<Svg width={6} height={6} viewBox="0 0 10 10" fill="none">
3179
+ <Circle cx="5" cy="5" r="5" fill={getStatusColor(selected.status)}/>
3180
+ </Svg>)}
3181
+ <Text style={[
3182
+ styles.chipText,
3183
+ {
3184
+ color: selected.status === 0
3185
+ ? AppColors.errorColor
3186
+ : getStatusColor(selected.status),
3187
+ },
3188
+ ]}>
3189
+ {selected.status === 0
3190
+ ? 'Failed'
3191
+ : String(selected.status)}
2163
3192
  </Text>
2164
- </View>
2165
- </React.Fragment>);
2166
- })}
2167
- </ScrollView>
2168
- </View>);
2169
- })()}
3193
+ </View>)}
2170
3194
 
2171
- <View style={styles.detailInfoBar}>
2172
- <View style={styles.detailInfoTop}>
2173
- <View style={{
2174
- flexDirection: 'row',
2175
- alignItems: 'center',
2176
- gap: 10,
2177
- }}>
2178
- <View style={[
2179
- styles.methodBadge,
2180
- {
2181
- backgroundColor: `${METHOD_COLORS[selected.method] ??
2182
- METHOD_COLORS.ALL}15`,
2183
- },
2184
- ]}>
2185
- <Text style={[
2186
- styles.methodBadgeText,
2187
- {
2188
- color: METHOD_COLORS[selected.method] ??
2189
- METHOD_COLORS.ALL,
2190
- },
2191
- ]}>
2192
- {selected.method}
2193
- </Text>
2194
- </View>
3195
+ {selected.duration != null && (<View style={[styles.chip, { backgroundColor: 'rgba(104,75,155,0.08)', borderColor: 'rgba(104,75,155,0.18)' }]}>
3196
+ <Text style={[styles.chipText, { color: AppColors.purple }]}>
3197
+ {selected.duration}ms
3198
+ </Text>
3199
+ </View>)}
3200
+ </View>
3201
+ <View style={styles.detailInfoRight}>
3202
+ <TouchableScale style={styles.iconSquareBtn} onPress={() => Linking.openURL(detailDisplayUrl)} hitSlop={12}>
3203
+ <GlobeIcon color={AppColors.grayTextWeak} size={14}/>
3204
+ </TouchableScale>
3205
+ <CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
3206
+ <CopyButton value={getCurlCommand(selected)} label="cURL" iconType="terminal"/>
3207
+ <CopyButton value={detailDisplayUrl} label="URL"/>
3208
+ </View>
3209
+ </View>
2195
3210
 
2196
- {selected.status != null && (<View style={[
2197
- styles.chip,
2198
- {
2199
- backgroundColor: selected.status === 0
2200
- ? `${AppColors.errorColor}15`
2201
- : `${getStatusColor(selected.status)}15`,
2202
- borderColor: selected.status === 0
2203
- ? `${AppColors.errorColor}40`
2204
- : `${getStatusColor(selected.status)}40`,
2205
- },
2206
- ]}>
2207
- {selected.status === 0 ? (<FailIcon size={8} color={AppColors.errorColor}/>) : (<Svg width={6} height={6} viewBox="0 0 10 10" fill="none">
2208
- <Circle cx="5" cy="5" r="5" fill={getStatusColor(selected.status)}/>
2209
- </Svg>)}
2210
- <Text style={[
2211
- styles.chipText,
2212
- {
2213
- color: selected.status === 0
2214
- ? AppColors.errorColor
2215
- : getStatusColor(selected.status),
2216
- },
2217
- ]}>
2218
- {selected.status === 0
2219
- ? 'Failed'
2220
- : String(selected.status)}
3211
+ <Pressable style={{
3212
+ backgroundColor: AppColors.grayBackground,
3213
+ borderRadius: 10,
3214
+ borderWidth: 1,
3215
+ borderColor: AppColors.dividerColor,
3216
+ padding: 10,
3217
+ marginTop: 6,
3218
+ }} onPress={() => Linking.openURL(detailDisplayUrl)}>
3219
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 2 }}>
3220
+ <Text style={{ fontFamily: AppFonts.interMedium, fontSize: 10, color: AppColors.grayTextWeak, flex: 1 }} numberOfLines={1}>
3221
+ {hostStr || 'API Endpoint'}
3222
+ </Text>
3223
+ {queryStr ? (<View style={{ backgroundColor: 'rgba(104,75,155,0.08)', paddingHorizontal: 5, paddingVertical: 1, borderRadius: 4 }}>
3224
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 8.5, color: AppColors.purple }}>Query Params</Text>
3225
+ </View>) : null}
3226
+ </View>
3227
+ <Text selectable={true} style={{ fontFamily: AppFonts.interBold, fontSize: 12, color: AppColors.primaryBlack, marginTop: 2 }} numberOfLines={2}>
3228
+ {pathStr}
2221
3229
  </Text>
2222
- </View>)}
2223
- </View>
2224
- <View style={styles.detailInfoRight}>
2225
- <TouchableScale style={styles.iconSquareBtn} onPress={() => Linking.openURL(detailDisplayUrl)} hitSlop={12}>
2226
- <GlobeIcon color={AppColors.grayTextWeak} size={14}/>
2227
- </TouchableScale>
2228
- <CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
2229
- <CopyButton value={getCurlCommand(selected)} label="cURL" iconType="terminal"/>
2230
- <CopyButton value={detailDisplayUrl} label="URL"/>
2231
- </View>
2232
- </View>
2233
-
2234
- <Pressable style={styles.detailUrlContainer} onPress={() => Linking.openURL(detailDisplayUrl)}>
2235
- <Text selectable={true} style={styles.detailUrl}>
2236
- {detailDisplayUrl}
2237
- </Text>
2238
- </Pressable>
3230
+ {queryStr ? (<Text selectable={true} style={{ fontFamily: AppFonts.interRegular, fontSize: 10, color: AppColors.grayTextWeak, marginTop: 4 }} numberOfLines={1}>
3231
+ {queryStr}
3232
+ </Text>) : null}
3233
+ </Pressable>
3234
+ </>);
3235
+ })()}
2239
3236
  </View>
2240
3237
  </View>
2241
3238
 
@@ -2264,16 +3261,30 @@ const NetworkInspector = () => {
2264
3261
  return 'Request';
2265
3262
  return 'Response';
2266
3263
  };
3264
+ const getIcon = () => {
3265
+ const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
3266
+ if (tab === 'metadata')
3267
+ return <StatusIcon color={iconColor}/>;
3268
+ if (tab === 'headers')
3269
+ return <HeadersIcon color={iconColor}/>;
3270
+ if (tab === 'request')
3271
+ return <RequestIcon color={iconColor}/>;
3272
+ return <ResponseIcon color={iconColor}/>;
3273
+ };
2267
3274
  return (<TouchableOpacity key={tab} onPress={() => setApiDetailActiveTab(tab)} style={{
2268
3275
  flex: 1,
2269
3276
  paddingVertical: 6,
3277
+ flexDirection: 'row',
2270
3278
  alignItems: 'center',
3279
+ justifyContent: 'center',
2271
3280
  borderRadius: 8,
2272
3281
  backgroundColor: isActive ? AppColors.purple : 'transparent',
3282
+ gap: 4,
2273
3283
  }}>
3284
+ {getIcon()}
2274
3285
  <Text style={{
2275
3286
  fontFamily: AppFonts.interBold,
2276
- fontSize: 11,
3287
+ fontSize: 10,
2277
3288
  color: isActive ? '#FFFFFF' : AppColors.grayText,
2278
3289
  }}>
2279
3290
  {getLabel()}
@@ -2285,7 +3296,8 @@ const NetworkInspector = () => {
2285
3296
  {/* Scrollable Tab Content */}
2286
3297
  <ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingHorizontal: 6, paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
2287
3298
  {apiDetailActiveTab === 'metadata' && (<>
2288
- <MetaAccordion status={selected.status} statusColor={getStatusColor(selected.status)} duration={selected.duration} size={getSize(selected.response)} triggeredAt={formatDateTime(selected.startTime)}/>
3299
+ <MetaAccordion status={selected.status} statusColor={getStatusColor(selected.status)} duration={selected.duration} size={getSize(selected.response)} triggeredAt={formatDateTime(selected.startTime)} method={selected.method} contentType={selected.responseHeaders?.['content-type'] ||
3300
+ selected.responseHeaders?.['Content-Type']} url={selected.url}/>
2289
3301
 
2290
3302
  {(() => {
2291
3303
  const routeInfo = logRouteMapRef.current.get(selected.id);
@@ -2369,6 +3381,10 @@ const NetworkInspector = () => {
2369
3381
  Loading logs...
2370
3382
  </Text>
2371
3383
  </View>)}
3384
+
3385
+ {settingsPage !== null && (<View style={[StyleSheet.absoluteFill, { backgroundColor: AppColors.grayBackground, zIndex: 99999 }]}>
3386
+ {renderSettings()}
3387
+ </View>)}
2372
3388
  </View>
2373
3389
  </View>
2374
3390
  </ErrorBoundary>)}