react-native-inapp-inspector 1.0.8 → 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.
- package/README.md +5 -6
- package/assets/social_preview.png +0 -0
- package/dist/commonjs/components/AnalyticsDetail.js +28 -23
- package/dist/commonjs/components/AnalyticsEventCard.js +9 -9
- package/dist/commonjs/components/BrandCircleIcon.d.ts +5 -0
- package/dist/commonjs/components/BrandCircleIcon.js +180 -0
- package/dist/commonjs/components/BrandSquareIcon.d.ts +5 -0
- package/dist/commonjs/components/BrandSquareIcon.js +180 -0
- package/dist/commonjs/components/CodeSnippet.js +32 -24
- package/dist/commonjs/components/ConsoleLogCard.js +127 -70
- package/dist/commonjs/components/JsonViewer.d.ts +2 -1
- package/dist/commonjs/components/JsonViewer.js +2 -2
- package/dist/commonjs/components/MetaAccordion.d.ts +1 -1
- package/dist/commonjs/components/MetaAccordion.js +45 -2
- package/dist/commonjs/components/NetworkIcons.d.ts +8 -0
- package/dist/commonjs/components/NetworkIcons.js +44 -1
- package/dist/commonjs/components/ReduxTreeView.d.ts +6 -0
- package/dist/commonjs/components/ReduxTreeView.js +403 -0
- package/dist/commonjs/components/TouchableScale.js +15 -1
- package/dist/commonjs/components/TreeNode.js +3 -3
- package/dist/commonjs/customHooks/reduxLogger.d.ts +4 -0
- package/dist/commonjs/customHooks/reduxLogger.js +48 -2
- package/dist/commonjs/index.js +1520 -506
- package/dist/commonjs/styles/index.d.ts +11 -1
- package/dist/commonjs/styles/index.js +19 -9
- package/dist/commonjs/types/index.d.ts +4 -0
- package/dist/esm/components/AnalyticsDetail.js +28 -23
- package/dist/esm/components/AnalyticsEventCard.js +9 -9
- package/dist/esm/components/BrandCircleIcon.d.ts +5 -0
- package/dist/esm/components/BrandCircleIcon.js +140 -0
- package/dist/esm/components/BrandSquareIcon.d.ts +5 -0
- package/dist/esm/components/BrandSquareIcon.js +140 -0
- package/dist/esm/components/CodeSnippet.js +32 -24
- package/dist/esm/components/ConsoleLogCard.js +127 -70
- package/dist/esm/components/JsonViewer.d.ts +2 -1
- package/dist/esm/components/JsonViewer.js +2 -2
- package/dist/esm/components/MetaAccordion.d.ts +1 -1
- package/dist/esm/components/MetaAccordion.js +46 -3
- package/dist/esm/components/NetworkIcons.d.ts +8 -0
- package/dist/esm/components/NetworkIcons.js +35 -0
- package/dist/esm/components/ReduxTreeView.d.ts +6 -0
- package/dist/esm/components/ReduxTreeView.js +366 -0
- package/dist/esm/components/TouchableScale.js +16 -2
- package/dist/esm/components/TreeNode.js +3 -3
- package/dist/esm/customHooks/reduxLogger.d.ts +4 -0
- package/dist/esm/customHooks/reduxLogger.js +43 -1
- package/dist/esm/index.js +1523 -509
- package/dist/esm/styles/index.d.ts +11 -1
- package/dist/esm/styles/index.js +19 -9
- package/dist/esm/types/index.d.ts +4 -0
- package/example/App.tsx +46 -0
- 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,
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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.
|
|
764
|
-
<
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
-
</
|
|
770
|
-
</TouchableScale>
|
|
1543
|
+
</TouchableScale>)}
|
|
771
1544
|
|
|
772
1545
|
{/* Module 2: Logs */}
|
|
773
|
-
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('logs')}>
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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.
|
|
787
|
-
<
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
<
|
|
792
|
-
{
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
</
|
|
803
|
-
</TouchableScale>
|
|
1576
|
+
</TouchableScale>)}
|
|
804
1577
|
|
|
805
1578
|
{/* Module 3: Analytics */}
|
|
806
|
-
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('analytics')}>
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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.
|
|
828
|
-
<
|
|
829
|
-
{
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
</
|
|
834
|
-
</TouchableScale>
|
|
1607
|
+
</TouchableScale>)}
|
|
835
1608
|
|
|
836
1609
|
{/* Module 4: WebView */}
|
|
837
|
-
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('webview')}>
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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.
|
|
855
|
-
<
|
|
856
|
-
{
|
|
857
|
-
|
|
858
|
-
|
|
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
|
-
</
|
|
861
|
-
</TouchableScale>
|
|
1634
|
+
</TouchableScale>)}
|
|
862
1635
|
|
|
863
1636
|
{/* Module 5: Redux Store */}
|
|
864
|
-
<TouchableScale style={styles.dashboardModuleCard} onPress={() => setActiveTab('redux')}>
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
<
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
-
|
|
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
|
-
|
|
926
|
-
paddingHorizontal: 16,
|
|
927
|
-
paddingVertical: 12,
|
|
928
|
-
borderBottomWidth: 1,
|
|
929
|
-
borderBottomColor: AppColors.dividerColor,
|
|
930
|
-
backgroundColor: AppColors.primaryLight,
|
|
1709
|
+
gap: 12,
|
|
931
1710
|
}}>
|
|
932
|
-
<
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
940
|
-
</
|
|
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
|
-
|
|
951
|
-
marginBottom: 8,
|
|
1739
|
+
marginBottom: 12,
|
|
952
1740
|
paddingHorizontal: 10,
|
|
953
1741
|
borderWidth: 1,
|
|
954
1742
|
borderColor: AppColors.dividerColor,
|
|
@@ -966,68 +1754,24 @@ const NetworkInspector = () => {
|
|
|
966
1754
|
</Pressable>)}
|
|
967
1755
|
</View>
|
|
968
1756
|
|
|
969
|
-
{
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
<
|
|
1029
|
-
<DebugIcon color="#FFFFFF" size={28}/>
|
|
1030
|
-
</LinearGradient>
|
|
1774
|
+
<BrandCircleIcon size={62}/>
|
|
1031
1775
|
{(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
|
|
1032
1776
|
styles.fabBadge,
|
|
1033
1777
|
hasErrors ? styles.fabBadgeError : styles.fabBadgeNormal,
|
|
@@ -1072,7 +1816,33 @@ const NetworkInspector = () => {
|
|
|
1072
1816
|
<WhiteBackNavigation />
|
|
1073
1817
|
</TouchableScale>
|
|
1074
1818
|
|
|
1075
|
-
{selected == null && selectedEvent == 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}
|
|
1076
1846
|
</View>
|
|
1077
1847
|
|
|
1078
1848
|
<View style={styles.headerCenter}>
|
|
@@ -1148,15 +1918,11 @@ const NetworkInspector = () => {
|
|
|
1148
1918
|
</View>
|
|
1149
1919
|
|
|
1150
1920
|
<View style={styles.headerRight}>
|
|
1151
|
-
<TouchableScale onPress={() => {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
toggleGlobalTheme(newTheme);
|
|
1155
|
-
}} hitSlop={15} style={[styles.closeButtonCircle, { marginRight: 8 }]}>
|
|
1156
|
-
{isDark ? (<SunIcon color="#FFFFFF" size={16}/>) : (<MoonIcon color="#FFFFFF" size={16}/>)}
|
|
1157
|
-
</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>)}
|
|
1158
1924
|
|
|
1159
|
-
<TouchableScale onPress={closeModal} hitSlop={15} style={styles.
|
|
1925
|
+
<TouchableScale onPress={closeModal} hitSlop={15} style={styles.closeButtonSquare}>
|
|
1160
1926
|
<CloseWhite size={16}/>
|
|
1161
1927
|
</TouchableScale>
|
|
1162
1928
|
</View>
|
|
@@ -1164,7 +1930,7 @@ const NetworkInspector = () => {
|
|
|
1164
1930
|
</LinearGradient>
|
|
1165
1931
|
|
|
1166
1932
|
{/* ─── Horizontal Scrollable Tab Bar inside Content ─── */}
|
|
1167
|
-
{selected == null && selectedEvent == null ? (<View style={styles.tabBarContainer}>
|
|
1933
|
+
{selected == null && selectedEvent == null && settingsPage === null ? (<View style={styles.tabBarContainer}>
|
|
1168
1934
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingRight: 16 }}>
|
|
1169
1935
|
{[
|
|
1170
1936
|
{ key: 'insights', label: 'Insights', count: 0, icon: 'insights' },
|
|
@@ -1173,10 +1939,12 @@ const NetworkInspector = () => {
|
|
|
1173
1939
|
{ key: 'analytics', label: 'Analytics', count: analyticsEvents.length, icon: 'analytics' },
|
|
1174
1940
|
{ key: 'webview', label: 'WebView', count: webViewNavHistory.length, icon: 'webview' },
|
|
1175
1941
|
{ key: 'redux', label: 'Redux', count: 0, icon: 'redux' },
|
|
1176
|
-
].map(tab => {
|
|
1942
|
+
].filter(tab => tabVisibility[tab.key]).map(tab => {
|
|
1177
1943
|
const isActive = activeTab === tab.key;
|
|
1178
1944
|
const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
|
|
1179
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;
|
|
1180
1948
|
return (<TouchableScale key={tab.key} onPress={() => {
|
|
1181
1949
|
requestAnimationFrame(() => {
|
|
1182
1950
|
setActiveTab(tab.key);
|
|
@@ -1198,6 +1966,15 @@ const NetworkInspector = () => {
|
|
|
1198
1966
|
]}>
|
|
1199
1967
|
{tab.label} {tab.count > 0 ? `(${countLabel})` : ''}
|
|
1200
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
|
+
}}/>)}
|
|
1201
1978
|
</View>
|
|
1202
1979
|
</TouchableScale>);
|
|
1203
1980
|
})}
|
|
@@ -1820,156 +2597,250 @@ const NetworkInspector = () => {
|
|
|
1820
2597
|
styles.listContent,
|
|
1821
2598
|
filteredConsoleLogs.length === 0 && { flexGrow: 1 },
|
|
1822
2599
|
]} keyboardShouldPersistTaps="handled"/>
|
|
1823
|
-
</View>) : activeTab === 'webview' ? (<View style={
|
|
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) ─── */}
|
|
1824
2670
|
<View style={{
|
|
1825
|
-
backgroundColor:
|
|
2671
|
+
backgroundColor: AppColors.primaryLight,
|
|
1826
2672
|
borderBottomWidth: 1,
|
|
1827
2673
|
borderBottomColor: AppColors.dividerColor,
|
|
1828
|
-
|
|
2674
|
+
paddingVertical: 6,
|
|
1829
2675
|
}}>
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
marginHorizontal: 16,
|
|
1833
|
-
marginTop: webViewSubTab === 'navigation' ? 12 : 4,
|
|
1834
|
-
marginBottom: 8,
|
|
1835
|
-
backgroundColor: AppColors.grayBackground,
|
|
1836
|
-
borderRadius: 8,
|
|
1837
|
-
padding: 4,
|
|
2676
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
|
|
2677
|
+
paddingHorizontal: 12,
|
|
1838
2678
|
flexDirection: 'row',
|
|
1839
|
-
|
|
1840
|
-
borderColor: AppColors.grayBorderSecondary,
|
|
2679
|
+
gap: 8,
|
|
1841
2680
|
}}>
|
|
2681
|
+
{/* Sub-tab 1: Preview */}
|
|
1842
2682
|
<Pressable style={[
|
|
1843
2683
|
{
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
borderRadius:
|
|
2684
|
+
paddingVertical: 6,
|
|
2685
|
+
paddingHorizontal: 14,
|
|
2686
|
+
borderRadius: 8,
|
|
1847
2687
|
alignItems: 'center',
|
|
2688
|
+
flexDirection: 'row',
|
|
2689
|
+
gap: 6,
|
|
2690
|
+
backgroundColor: 'rgba(0, 0, 0, 0.03)',
|
|
2691
|
+
borderWidth: 1,
|
|
2692
|
+
borderColor: 'transparent',
|
|
1848
2693
|
},
|
|
1849
|
-
webViewSubTab === '
|
|
1850
|
-
backgroundColor: AppColors.
|
|
1851
|
-
|
|
1852
|
-
shadowOpacity: 0.1,
|
|
1853
|
-
shadowRadius: 3,
|
|
1854
|
-
shadowOffset: { width: 0, height: 1 },
|
|
1855
|
-
elevation: 2,
|
|
2694
|
+
webViewSubTab === 'preview' && {
|
|
2695
|
+
backgroundColor: AppColors.purple,
|
|
2696
|
+
borderColor: AppColors.purple,
|
|
1856
2697
|
},
|
|
1857
|
-
]} onPress={() => setWebViewSubTab('
|
|
1858
|
-
<
|
|
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={[
|
|
1859
2711
|
{
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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',
|
|
1863
2721
|
},
|
|
1864
2722
|
webViewSubTab === 'html' && {
|
|
1865
|
-
|
|
1866
|
-
|
|
2723
|
+
backgroundColor: AppColors.purple,
|
|
2724
|
+
borderColor: AppColors.purple,
|
|
1867
2725
|
},
|
|
1868
|
-
]}>
|
|
1869
|
-
|
|
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
|
|
1870
2734
|
</Text>
|
|
1871
2735
|
</Pressable>
|
|
2736
|
+
|
|
2737
|
+
{/* Sub-tab 3: History */}
|
|
1872
2738
|
<Pressable style={[
|
|
1873
2739
|
{
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
borderRadius:
|
|
2740
|
+
paddingVertical: 6,
|
|
2741
|
+
paddingHorizontal: 14,
|
|
2742
|
+
borderRadius: 8,
|
|
1877
2743
|
alignItems: 'center',
|
|
2744
|
+
flexDirection: 'row',
|
|
2745
|
+
gap: 6,
|
|
2746
|
+
backgroundColor: 'rgba(0, 0, 0, 0.03)',
|
|
2747
|
+
borderWidth: 1,
|
|
2748
|
+
borderColor: 'transparent',
|
|
1878
2749
|
},
|
|
1879
2750
|
webViewSubTab === 'navigation' && {
|
|
1880
|
-
backgroundColor: AppColors.
|
|
1881
|
-
|
|
1882
|
-
shadowOpacity: 0.1,
|
|
1883
|
-
shadowRadius: 3,
|
|
1884
|
-
shadowOffset: { width: 0, height: 1 },
|
|
1885
|
-
elevation: 2,
|
|
2751
|
+
backgroundColor: AppColors.purple,
|
|
2752
|
+
borderColor: AppColors.purple,
|
|
1886
2753
|
},
|
|
1887
2754
|
]} onPress={() => setWebViewSubTab('navigation')}>
|
|
1888
|
-
<
|
|
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={[
|
|
1889
2767
|
{
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
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',
|
|
1893
2777
|
},
|
|
1894
|
-
webViewSubTab === '
|
|
1895
|
-
|
|
1896
|
-
|
|
2778
|
+
webViewSubTab === 'console' && {
|
|
2779
|
+
backgroundColor: AppColors.purple,
|
|
2780
|
+
borderColor: AppColors.purple,
|
|
1897
2781
|
},
|
|
1898
|
-
]}>
|
|
1899
|
-
|
|
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})
|
|
1900
2790
|
</Text>
|
|
1901
2791
|
</Pressable>
|
|
1902
|
-
</
|
|
2792
|
+
</ScrollView>
|
|
1903
2793
|
</View>
|
|
1904
2794
|
|
|
1905
|
-
{/* ─── Current Page Address Bar (Always visible at the top) ─── */}
|
|
1906
|
-
{(() => {
|
|
1907
|
-
const currentUrl = webViewNavHistory[0]?.url;
|
|
1908
|
-
if (!currentUrl)
|
|
1909
|
-
return null;
|
|
1910
|
-
return (<View style={{
|
|
1911
|
-
paddingHorizontal: 16,
|
|
1912
|
-
paddingTop: 10,
|
|
1913
|
-
paddingBottom: 10,
|
|
1914
|
-
backgroundColor: '#FFFFFF',
|
|
1915
|
-
borderBottomWidth: 1,
|
|
1916
|
-
borderBottomColor: AppColors.dividerColor,
|
|
1917
|
-
}}>
|
|
1918
|
-
<Text style={{
|
|
1919
|
-
fontFamily: AppFonts.interBold,
|
|
1920
|
-
fontSize: 10,
|
|
1921
|
-
color: '#64748B',
|
|
1922
|
-
textTransform: 'uppercase',
|
|
1923
|
-
letterSpacing: 0.5,
|
|
1924
|
-
marginBottom: 6,
|
|
1925
|
-
}}>
|
|
1926
|
-
Currently debugging for URL
|
|
1927
|
-
</Text>
|
|
1928
|
-
<View style={{
|
|
1929
|
-
flexDirection: 'row',
|
|
1930
|
-
alignItems: 'center',
|
|
1931
|
-
backgroundColor: '#F1F5F9',
|
|
1932
|
-
borderRadius: 20,
|
|
1933
|
-
borderWidth: 1,
|
|
1934
|
-
borderColor: '#E2E8F0',
|
|
1935
|
-
paddingHorizontal: 12,
|
|
1936
|
-
paddingVertical: 6,
|
|
1937
|
-
gap: 8,
|
|
1938
|
-
}}>
|
|
1939
|
-
{/* Left: Clickable Globe Icon to open browser */}
|
|
1940
|
-
<TouchableScale onPress={() => Linking.openURL(currentUrl)} hitSlop={8} style={{
|
|
1941
|
-
width: 24,
|
|
1942
|
-
height: 24,
|
|
1943
|
-
borderRadius: 12,
|
|
1944
|
-
backgroundColor: '#E2E8F0',
|
|
1945
|
-
alignItems: 'center',
|
|
1946
|
-
justifyContent: 'center',
|
|
1947
|
-
}} children={<GlobeIcon size={12} color="#475569"/>}/>
|
|
1948
|
-
|
|
1949
|
-
{/* Middle: URL text (Address style) */}
|
|
1950
|
-
<View style={{ flex: 1 }}>
|
|
1951
|
-
<HighlightText text={currentUrl} search={webViewSearch} numberOfLines={1} ellipsizeMode="tail" style={{
|
|
1952
|
-
fontFamily: AppFonts.interMedium,
|
|
1953
|
-
fontSize: 11,
|
|
1954
|
-
color: '#475569',
|
|
1955
|
-
}} highlightStyle={styles.highlight} detectLinks={false}/>
|
|
1956
|
-
</View>
|
|
1957
|
-
|
|
1958
|
-
{/* Right: Copy Button */}
|
|
1959
|
-
<CopyButton value={currentUrl} label="URL"/>
|
|
1960
|
-
</View>
|
|
1961
|
-
</View>);
|
|
1962
|
-
})()}
|
|
1963
|
-
|
|
1964
2795
|
{webViewSubTab === 'html' ? (<View style={{ flex: 1 }}>
|
|
1965
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
|
+
<{inspectedElement.tagName}
|
|
2817
|
+
{inspectedElement.id ? ` id="${inspectedElement.id}"` : ''}
|
|
2818
|
+
{inspectedElement.className ? ` class="${inspectedElement.className.trim().split(/\s+/)[0]}"` : ''}
|
|
2819
|
+
>
|
|
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
|
+
|
|
1966
2836
|
{/* Inner sub-tabs inside HTML source view */}
|
|
1967
2837
|
<View style={{
|
|
1968
2838
|
flexDirection: 'row',
|
|
1969
2839
|
borderBottomWidth: 1,
|
|
1970
|
-
borderBottomColor:
|
|
1971
|
-
backgroundColor:
|
|
1972
|
-
paddingHorizontal:
|
|
2840
|
+
borderBottomColor: AppColors.dividerColor,
|
|
2841
|
+
backgroundColor: AppColors.primaryLight,
|
|
2842
|
+
paddingHorizontal: 12,
|
|
2843
|
+
gap: 12,
|
|
1973
2844
|
}}>
|
|
1974
2845
|
{['html', 'css', 'javascript'].map(tab => {
|
|
1975
2846
|
const active = htmlSubTab === tab;
|
|
@@ -1977,23 +2848,34 @@ const NetworkInspector = () => {
|
|
|
1977
2848
|
? 'HTML'
|
|
1978
2849
|
: tab === 'css'
|
|
1979
2850
|
? 'CSS'
|
|
1980
|
-
: '
|
|
2851
|
+
: 'JavaScript';
|
|
2852
|
+
const activeColor = tab === 'html'
|
|
2853
|
+
? '#EA580C' // Orange
|
|
2854
|
+
: tab === 'css'
|
|
2855
|
+
? '#2563EB' // Blue
|
|
2856
|
+
: '#D97706'; // Dark Yellow/Amber
|
|
1981
2857
|
return (<Pressable key={tab} onPress={() => setHtmlSubTab(tab)} style={{
|
|
1982
|
-
paddingVertical:
|
|
1983
|
-
|
|
2858
|
+
paddingVertical: 8,
|
|
2859
|
+
paddingHorizontal: 4,
|
|
1984
2860
|
borderBottomWidth: 2,
|
|
1985
2861
|
borderBottomColor: active
|
|
1986
|
-
?
|
|
2862
|
+
? activeColor
|
|
1987
2863
|
: 'transparent',
|
|
2864
|
+
flexDirection: 'row',
|
|
2865
|
+
alignItems: 'center',
|
|
2866
|
+
gap: 4,
|
|
1988
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}/>)}
|
|
1989
2871
|
<Text style={{
|
|
1990
2872
|
fontFamily: active
|
|
1991
2873
|
? AppFonts.interBold
|
|
1992
2874
|
: AppFonts.interMedium,
|
|
1993
|
-
fontSize:
|
|
2875
|
+
fontSize: 13,
|
|
1994
2876
|
color: active
|
|
1995
|
-
?
|
|
1996
|
-
:
|
|
2877
|
+
? activeColor
|
|
2878
|
+
: AppColors.grayTextWeak,
|
|
1997
2879
|
}}>
|
|
1998
2880
|
{label}
|
|
1999
2881
|
</Text>
|
|
@@ -2003,21 +2885,21 @@ const NetworkInspector = () => {
|
|
|
2003
2885
|
<View style={{ flex: 1, padding: 12 }}>
|
|
2004
2886
|
{!isHtmlTabReady ? (<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
|
|
2005
2887
|
<ActivityIndicator size="large" color={AppColors.purple}/>
|
|
2006
|
-
</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={{
|
|
2007
2889
|
fontFamily: 'monospace',
|
|
2008
2890
|
fontSize: 11,
|
|
2009
2891
|
color: '#94A3B8',
|
|
2010
2892
|
padding: 12,
|
|
2011
2893
|
}}>
|
|
2012
2894
|
No HTML content captured.
|
|
2013
|
-
</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={{
|
|
2014
2896
|
fontFamily: 'monospace',
|
|
2015
2897
|
fontSize: 11,
|
|
2016
2898
|
color: '#94A3B8',
|
|
2017
2899
|
padding: 12,
|
|
2018
2900
|
}}>
|
|
2019
2901
|
No CSS styles detected on this page.
|
|
2020
|
-
</Text>)) : webViewJs ? (<CodeSnippet code={webViewJs} language="javascript"/>) : (<Text style={{
|
|
2902
|
+
</Text>)) : webViewJs ? (<CodeSnippet code={webViewJs} language="javascript" search={getSearchTermForTab()}/>) : (<Text style={{
|
|
2021
2903
|
fontFamily: 'monospace',
|
|
2022
2904
|
fontSize: 11,
|
|
2023
2905
|
color: '#94A3B8',
|
|
@@ -2038,7 +2920,7 @@ const NetworkInspector = () => {
|
|
|
2038
2920
|
or Javascript source.
|
|
2039
2921
|
</Text>
|
|
2040
2922
|
</View>)}
|
|
2041
|
-
</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={{
|
|
2042
2924
|
paddingHorizontal: 16,
|
|
2043
2925
|
paddingTop: 12,
|
|
2044
2926
|
paddingBottom: 8,
|
|
@@ -2053,191 +2935,304 @@ const NetworkInspector = () => {
|
|
|
2053
2935
|
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
|
2054
2936
|
};
|
|
2055
2937
|
return (<View style={{
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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,
|
|
2061
2945
|
flexDirection: 'row',
|
|
2062
2946
|
alignItems: 'center',
|
|
2063
2947
|
justifyContent: 'space-between',
|
|
2064
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,
|
|
2065
2954
|
}}>
|
|
2066
|
-
<View style={{ flex: 1, gap:
|
|
2955
|
+
<View style={{ flex: 1, gap: 8 }}>
|
|
2956
|
+
{/* Top row: Title and Badge */}
|
|
2067
2957
|
<View style={{
|
|
2068
2958
|
flexDirection: 'row',
|
|
2069
2959
|
alignItems: 'center',
|
|
2070
|
-
gap:
|
|
2960
|
+
gap: 8,
|
|
2071
2961
|
flexWrap: 'wrap',
|
|
2072
2962
|
}}>
|
|
2073
2963
|
<Text numberOfLines={1} ellipsizeMode="tail" style={{
|
|
2074
2964
|
fontFamily: AppFonts.interBold,
|
|
2075
|
-
fontSize:
|
|
2076
|
-
color:
|
|
2965
|
+
fontSize: 14,
|
|
2966
|
+
color: AppColors.primaryBlack,
|
|
2077
2967
|
flexShrink: 1,
|
|
2078
2968
|
}}>
|
|
2079
2969
|
{item.title || 'Untitled Page'}
|
|
2080
2970
|
</Text>
|
|
2081
2971
|
{isLatest && (<View style={{
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2972
|
+
flexDirection: 'row',
|
|
2973
|
+
alignItems: 'center',
|
|
2974
|
+
gap: 4,
|
|
2975
|
+
backgroundColor: AppColors.greenStatus,
|
|
2976
|
+
paddingHorizontal: 8,
|
|
2977
|
+
paddingVertical: 3,
|
|
2978
|
+
borderRadius: 12,
|
|
2086
2979
|
}}>
|
|
2980
|
+
<View style={{
|
|
2981
|
+
width: 6,
|
|
2982
|
+
height: 6,
|
|
2983
|
+
borderRadius: 3,
|
|
2984
|
+
backgroundColor: AppColors.greenBaggageText,
|
|
2985
|
+
}}/>
|
|
2087
2986
|
<Text style={{
|
|
2088
2987
|
fontFamily: AppFonts.interBold,
|
|
2089
|
-
fontSize: 9,
|
|
2090
|
-
color:
|
|
2988
|
+
fontSize: 9.5,
|
|
2989
|
+
color: AppColors.greenBaggageText,
|
|
2091
2990
|
}}>
|
|
2092
2991
|
Active
|
|
2093
2992
|
</Text>
|
|
2094
2993
|
</View>)}
|
|
2095
2994
|
</View>
|
|
2096
|
-
|
|
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={{
|
|
2097
3000
|
fontFamily: AppFonts.interRegular,
|
|
2098
|
-
fontSize:
|
|
2099
|
-
color:
|
|
2100
|
-
|
|
3001
|
+
fontSize: 12,
|
|
3002
|
+
color: AppColors.grayText,
|
|
3003
|
+
flex: 1,
|
|
2101
3004
|
}} highlightStyle={styles.highlight} detectLinks={true}/>
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
{
|
|
2108
|
-
|
|
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>
|
|
2109
3014
|
</View>
|
|
2110
|
-
<CopyButton value={item.url} label="
|
|
3015
|
+
<CopyButton value={item.url} label="URL"/>
|
|
2111
3016
|
</View>);
|
|
2112
|
-
}} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={
|
|
3017
|
+
}} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={webViewSearch.length > 0}/>} contentContainerStyle={[
|
|
2113
3018
|
styles.listContent,
|
|
2114
3019
|
filteredNavHistory.length === 0 && { flexGrow: 1 },
|
|
2115
|
-
]} keyboardShouldPersistTaps="handled"/>)}
|
|
2116
|
-
|
|
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 }}>
|
|
2117
3111
|
{/* Non-scrollable details header */}
|
|
2118
3112
|
<View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
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={{
|
|
2138
3145
|
flexDirection: 'row',
|
|
2139
3146
|
alignItems: 'center',
|
|
2140
|
-
gap:
|
|
3147
|
+
gap: 8,
|
|
2141
3148
|
}}>
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
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)}
|
|
2165
3192
|
</Text>
|
|
2166
|
-
</View>
|
|
2167
|
-
</React.Fragment>);
|
|
2168
|
-
})}
|
|
2169
|
-
</ScrollView>
|
|
2170
|
-
</View>);
|
|
2171
|
-
})()}
|
|
3193
|
+
</View>)}
|
|
2172
3194
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
styles.methodBadgeText,
|
|
2189
|
-
{
|
|
2190
|
-
color: METHOD_COLORS[selected.method] ??
|
|
2191
|
-
METHOD_COLORS.ALL,
|
|
2192
|
-
},
|
|
2193
|
-
]}>
|
|
2194
|
-
{selected.method}
|
|
2195
|
-
</Text>
|
|
2196
|
-
</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>
|
|
2197
3210
|
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
? AppColors.errorColor
|
|
2217
|
-
: getStatusColor(selected.status),
|
|
2218
|
-
},
|
|
2219
|
-
]}>
|
|
2220
|
-
{selected.status === 0
|
|
2221
|
-
? 'Failed'
|
|
2222
|
-
: 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}
|
|
2223
3229
|
</Text>
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
<CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
|
|
2231
|
-
<CopyButton value={getCurlCommand(selected)} label="cURL" iconType="terminal"/>
|
|
2232
|
-
<CopyButton value={detailDisplayUrl} label="URL"/>
|
|
2233
|
-
</View>
|
|
2234
|
-
</View>
|
|
2235
|
-
|
|
2236
|
-
<Pressable style={styles.detailUrlContainer} onPress={() => Linking.openURL(detailDisplayUrl)}>
|
|
2237
|
-
<Text selectable={true} style={styles.detailUrl}>
|
|
2238
|
-
{detailDisplayUrl}
|
|
2239
|
-
</Text>
|
|
2240
|
-
</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
|
+
})()}
|
|
2241
3236
|
</View>
|
|
2242
3237
|
</View>
|
|
2243
3238
|
|
|
@@ -2266,16 +3261,30 @@ const NetworkInspector = () => {
|
|
|
2266
3261
|
return 'Request';
|
|
2267
3262
|
return 'Response';
|
|
2268
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
|
+
};
|
|
2269
3274
|
return (<TouchableOpacity key={tab} onPress={() => setApiDetailActiveTab(tab)} style={{
|
|
2270
3275
|
flex: 1,
|
|
2271
3276
|
paddingVertical: 6,
|
|
3277
|
+
flexDirection: 'row',
|
|
2272
3278
|
alignItems: 'center',
|
|
3279
|
+
justifyContent: 'center',
|
|
2273
3280
|
borderRadius: 8,
|
|
2274
3281
|
backgroundColor: isActive ? AppColors.purple : 'transparent',
|
|
3282
|
+
gap: 4,
|
|
2275
3283
|
}}>
|
|
3284
|
+
{getIcon()}
|
|
2276
3285
|
<Text style={{
|
|
2277
3286
|
fontFamily: AppFonts.interBold,
|
|
2278
|
-
fontSize:
|
|
3287
|
+
fontSize: 10,
|
|
2279
3288
|
color: isActive ? '#FFFFFF' : AppColors.grayText,
|
|
2280
3289
|
}}>
|
|
2281
3290
|
{getLabel()}
|
|
@@ -2287,7 +3296,8 @@ const NetworkInspector = () => {
|
|
|
2287
3296
|
{/* Scrollable Tab Content */}
|
|
2288
3297
|
<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingHorizontal: 6, paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
|
|
2289
3298
|
{apiDetailActiveTab === 'metadata' && (<>
|
|
2290
|
-
<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}/>
|
|
2291
3301
|
|
|
2292
3302
|
{(() => {
|
|
2293
3303
|
const routeInfo = logRouteMapRef.current.get(selected.id);
|
|
@@ -2371,6 +3381,10 @@ const NetworkInspector = () => {
|
|
|
2371
3381
|
Loading logs...
|
|
2372
3382
|
</Text>
|
|
2373
3383
|
</View>)}
|
|
3384
|
+
|
|
3385
|
+
{settingsPage !== null && (<View style={[StyleSheet.absoluteFill, { backgroundColor: AppColors.grayBackground, zIndex: 99999 }]}>
|
|
3386
|
+
{renderSettings()}
|
|
3387
|
+
</View>)}
|
|
2374
3388
|
</View>
|
|
2375
3389
|
</View>
|
|
2376
3390
|
</ErrorBoundary>)}
|