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