react-native-inapp-inspector 1.1.34 → 2.0.0
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 +123 -101
- package/android/build.gradle +13 -1
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +904 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
- package/dist/commonjs/components/ConsoleLogCard.js +57 -56
- package/dist/commonjs/components/CopyButton.js +10 -1
- package/dist/commonjs/components/ErrorBoundary.js +3 -0
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +7 -3
- package/dist/commonjs/components/Inspector/BundleTab.js +20 -20
- package/dist/commonjs/components/Inspector/ConsoleTab.js +1 -1
- package/dist/commonjs/components/Inspector/CrashDetail.js +16 -12
- package/dist/commonjs/components/Inspector/CrashTab.js +97 -90
- package/dist/commonjs/components/Inspector/InspectorHeader.js +28 -24
- package/dist/commonjs/components/Inspector/MainScreen.js +20 -103
- package/dist/commonjs/components/Inspector/NetworkTab.js +143 -10
- package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
- package/dist/commonjs/components/Inspector/ReduxDetail.js +24 -19
- package/dist/commonjs/components/Inspector/ReduxTab.js +30 -18
- package/dist/commonjs/components/Inspector/SettingsPanel.js +1578 -754
- package/dist/commonjs/components/Inspector/TabBar.js +6 -3
- package/dist/commonjs/components/LogCard.js +27 -9
- package/dist/commonjs/components/MetaAccordion.js +42 -0
- package/dist/commonjs/components/NetworkIcons.d.ts +10 -0
- package/dist/commonjs/components/NetworkIcons.js +53 -1
- package/dist/commonjs/components/Toast.js +3 -3
- package/dist/commonjs/components/TouchableScale.d.ts +5 -2
- package/dist/commonjs/components/TouchableScale.js +30 -32
- package/dist/commonjs/constants/index.d.ts +1 -2
- package/dist/commonjs/constants/index.js +4 -0
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +6 -0
- package/dist/commonjs/customHooks/analyticsLogger.js +16 -5
- package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +2 -0
- package/dist/commonjs/customHooks/bundleAnalyzer.js +26 -17
- package/dist/commonjs/customHooks/consoleLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/consoleLogger.js +10 -1
- package/dist/commonjs/customHooks/crashHandler.d.ts +2 -0
- package/dist/commonjs/customHooks/crashHandler.js +17 -1
- package/dist/commonjs/customHooks/networkLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/networkLogger.js +12 -1
- package/dist/commonjs/customHooks/performanceTracker.d.ts +3 -1
- package/dist/commonjs/customHooks/performanceTracker.js +18 -4
- package/dist/commonjs/customHooks/reduxLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/reduxLogger.js +10 -1
- package/dist/commonjs/helpers/index.js +10 -1
- package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
- package/dist/commonjs/helpers/settingsStore.js +59 -3
- package/dist/commonjs/i18n/locales/en.json +96 -28
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +307 -73
- package/dist/commonjs/native/NativeInspector.d.ts +125 -0
- package/dist/commonjs/native/NativeInspector.js +313 -0
- package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
- package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
- package/dist/commonjs/styles/AppColors.d.ts +28 -0
- package/dist/commonjs/styles/AppColors.js +30 -0
- package/dist/commonjs/styles/index.js +6 -6
- package/dist/commonjs/types/enums.d.ts +1 -1
- package/dist/commonjs/types/interfaces.d.ts +9 -0
- package/dist/esm/components/ConsoleLogCard.js +58 -57
- package/dist/esm/components/CopyButton.js +10 -1
- package/dist/esm/components/ErrorBoundary.js +3 -0
- package/dist/esm/components/Inspector/AnalyticsTab.js +7 -3
- package/dist/esm/components/Inspector/BundleTab.js +20 -20
- package/dist/esm/components/Inspector/ConsoleTab.js +1 -1
- package/dist/esm/components/Inspector/CrashDetail.js +16 -12
- package/dist/esm/components/Inspector/CrashTab.js +97 -90
- package/dist/esm/components/Inspector/InspectorHeader.js +29 -25
- package/dist/esm/components/Inspector/MainScreen.js +21 -71
- package/dist/esm/components/Inspector/NetworkTab.js +145 -12
- package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
- package/dist/esm/components/Inspector/ReduxDetail.js +24 -19
- package/dist/esm/components/Inspector/ReduxTab.js +30 -18
- package/dist/esm/components/Inspector/SettingsPanel.js +1582 -758
- package/dist/esm/components/Inspector/TabBar.js +7 -4
- package/dist/esm/components/LogCard.js +27 -9
- package/dist/esm/components/MetaAccordion.js +42 -0
- package/dist/esm/components/NetworkIcons.d.ts +10 -0
- package/dist/esm/components/NetworkIcons.js +42 -0
- package/dist/esm/components/Toast.js +3 -3
- package/dist/esm/components/TouchableScale.d.ts +5 -2
- package/dist/esm/components/TouchableScale.js +31 -33
- package/dist/esm/constants/index.d.ts +1 -2
- package/dist/esm/constants/index.js +4 -0
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/analyticsLogger.d.ts +6 -0
- package/dist/esm/customHooks/analyticsLogger.js +12 -4
- package/dist/esm/customHooks/bundleAnalyzer.d.ts +2 -0
- package/dist/esm/customHooks/bundleAnalyzer.js +23 -16
- package/dist/esm/customHooks/consoleLogger.d.ts +2 -0
- package/dist/esm/customHooks/consoleLogger.js +7 -0
- package/dist/esm/customHooks/crashHandler.d.ts +2 -0
- package/dist/esm/customHooks/crashHandler.js +14 -0
- package/dist/esm/customHooks/networkLogger.d.ts +2 -0
- package/dist/esm/customHooks/networkLogger.js +9 -0
- package/dist/esm/customHooks/performanceTracker.d.ts +3 -1
- package/dist/esm/customHooks/performanceTracker.js +15 -3
- package/dist/esm/customHooks/reduxLogger.d.ts +2 -0
- package/dist/esm/customHooks/reduxLogger.js +7 -0
- package/dist/esm/helpers/index.js +10 -1
- package/dist/esm/helpers/settingsStore.d.ts +9 -0
- package/dist/esm/helpers/settingsStore.js +58 -3
- package/dist/esm/i18n/locales/en.json +96 -28
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +279 -59
- package/dist/esm/native/NativeInspector.d.ts +125 -0
- package/dist/esm/native/NativeInspector.js +289 -0
- package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
- package/dist/esm/native/NativeNetworkInspector.js +2 -0
- package/dist/esm/styles/AppColors.d.ts +28 -0
- package/dist/esm/styles/AppColors.js +30 -0
- package/dist/esm/styles/index.js +6 -6
- package/dist/esm/types/enums.d.ts +1 -1
- package/dist/esm/types/interfaces.d.ts +9 -0
- package/ios/NetworkInspectorModule.h +6 -0
- package/ios/NetworkInspectorModule.m +720 -5
- package/package.json +15 -3
- package/react-native-inapp-inspector.podspec +7 -2
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useRef } from 'react';
|
|
1
|
+
import React, { useCallback, useMemo, useRef } from 'react';
|
|
2
2
|
import { Animated, FlatList, Pressable, ScrollView, Text, TextInput, View, } from 'react-native';
|
|
3
3
|
import { animateNextLayout, useInspector } from './InspectorContext';
|
|
4
4
|
import useAccordion from '../../customHooks/useAccordion';
|
|
@@ -12,15 +12,45 @@ import styles from '../../styles';
|
|
|
12
12
|
import { AppColors } from '../../styles/AppColors';
|
|
13
13
|
import { AppFonts } from '../../styles/AppFonts';
|
|
14
14
|
import { STATUS_FILTERS, METHOD_COLORS } from '../../constants';
|
|
15
|
-
import {
|
|
15
|
+
import { useTranslation } from '../../i18n';
|
|
16
|
+
import { SearchIcon, ClearIcon, TrashIcon, SortArrowIcon, FilterIcon, ChevronIcon, CheckIcon, CircleCheckIcon, CircleXIcon, CircleAlertIcon, LayersIcon, HeaderPauseIcon, ClockIcon, } from '../NetworkIcons';
|
|
16
17
|
const STATUS_META = {
|
|
17
18
|
ALL: { color: AppColors.grayText, Icon: LayersIcon },
|
|
18
19
|
'2xx': { color: AppColors.greenColor, Icon: CircleCheckIcon },
|
|
20
|
+
'200': { color: AppColors.greenColor, Icon: CircleCheckIcon },
|
|
19
21
|
'3xx': { color: AppColors.warningIconGold, Icon: CircleAlertIcon },
|
|
22
|
+
'300': { color: AppColors.warningIconGold, Icon: CircleAlertIcon },
|
|
20
23
|
'4xx': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
24
|
+
'400': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
25
|
+
'404': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
21
26
|
'5xx': { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
27
|
+
'500': { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
22
28
|
Failed: { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
23
29
|
};
|
|
30
|
+
const getStatusMeta = (filter) => {
|
|
31
|
+
if (STATUS_META[filter])
|
|
32
|
+
return STATUS_META[filter];
|
|
33
|
+
const num = parseInt(filter, 10);
|
|
34
|
+
if (!isNaN(num)) {
|
|
35
|
+
if (num >= 200 && num < 300)
|
|
36
|
+
return { color: AppColors.greenColor, Icon: CircleCheckIcon };
|
|
37
|
+
if (num >= 300 && num < 400)
|
|
38
|
+
return { color: AppColors.warningIconGold, Icon: CircleAlertIcon };
|
|
39
|
+
if (num >= 400 && num < 500)
|
|
40
|
+
return { color: AppColors.darkOrange, Icon: CircleAlertIcon };
|
|
41
|
+
if (num >= 500)
|
|
42
|
+
return { color: AppColors.errorColor, Icon: CircleXIcon };
|
|
43
|
+
}
|
|
44
|
+
if (filter.startsWith('2'))
|
|
45
|
+
return { color: AppColors.greenColor, Icon: CircleCheckIcon };
|
|
46
|
+
if (filter.startsWith('3'))
|
|
47
|
+
return { color: AppColors.warningIconGold, Icon: CircleAlertIcon };
|
|
48
|
+
if (filter.startsWith('4'))
|
|
49
|
+
return { color: AppColors.darkOrange, Icon: CircleAlertIcon };
|
|
50
|
+
if (filter.startsWith('5'))
|
|
51
|
+
return { color: AppColors.errorColor, Icon: CircleXIcon };
|
|
52
|
+
return { color: AppColors.grayText, Icon: LayersIcon };
|
|
53
|
+
};
|
|
24
54
|
const FilterChip = React.memo(({ label, color, Icon, badge, active, onPress, }) => {
|
|
25
55
|
const iconColor = active ? color : AppColors.grayTextWeak;
|
|
26
56
|
return (<TouchableScale style={styles.statusFilterWrap} onPress={onPress} hitSlop={10}>
|
|
@@ -58,8 +88,43 @@ const FilterChip = React.memo(({ label, color, Icon, badge, active, onPress, })
|
|
|
58
88
|
});
|
|
59
89
|
const NetworkTab = React.memo(() => {
|
|
60
90
|
const { groupedData, search, setSearch, handleDelete, selectedLogs, sortOrder, setSortOrder, statusFilters, setStatusFilters, methodFilters, setMethodFilters, availableMethods, filteredLogs, logs, toggleSectionFilter, toggleSectionCollapse, minStart, totalRange, newLogIds, toggleSelect, setSelected, isNetworkPaused, setIsNetworkPaused, } = useInspector();
|
|
91
|
+
const { t } = useTranslation();
|
|
61
92
|
const filtersAccordion = useAccordion(false, 300, 260);
|
|
62
93
|
const apisListRef = useRef(null);
|
|
94
|
+
const networkMetrics = useMemo(() => {
|
|
95
|
+
if (!logs || logs.length === 0)
|
|
96
|
+
return null;
|
|
97
|
+
let successCount = 0;
|
|
98
|
+
let completedCount = 0;
|
|
99
|
+
let totalDuration = 0;
|
|
100
|
+
let totalBytes = 0;
|
|
101
|
+
const durations = [];
|
|
102
|
+
logs.forEach(log => {
|
|
103
|
+
if (log.status != null) {
|
|
104
|
+
completedCount++;
|
|
105
|
+
const s = typeof log.status === 'number' ? log.status : parseInt(String(log.status), 10);
|
|
106
|
+
if (!isNaN(s)) {
|
|
107
|
+
if (s >= 200 && s < 400) {
|
|
108
|
+
successCount++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (typeof log.duration === 'number' && log.duration > 0) {
|
|
113
|
+
durations.push(log.duration);
|
|
114
|
+
totalDuration += log.duration;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
durations.sort((a, b) => a - b);
|
|
118
|
+
const avgDuration = durations.length > 0 ? Math.round(totalDuration / durations.length) : 0;
|
|
119
|
+
const p95 = durations.length > 0 ? durations[Math.floor(durations.length * 0.95)] : 0;
|
|
120
|
+
const successRate = completedCount > 0 ? Math.round((successCount / completedCount) * 100) : 100;
|
|
121
|
+
return {
|
|
122
|
+
successRate,
|
|
123
|
+
avgDuration,
|
|
124
|
+
p95,
|
|
125
|
+
totalCount: logs.length,
|
|
126
|
+
};
|
|
127
|
+
}, [logs]);
|
|
63
128
|
const renderItem = useCallback(({ item, index }) => {
|
|
64
129
|
if (item.type === 'header') {
|
|
65
130
|
return (<AnimatedEntrance index={index} distance={8}>
|
|
@@ -98,7 +163,67 @@ const NetworkTab = React.memo(() => {
|
|
|
98
163
|
setSelected,
|
|
99
164
|
]);
|
|
100
165
|
return (<View style={{ flex: 1 }}>
|
|
101
|
-
<FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={
|
|
166
|
+
<FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListHeaderComponent={<View style={{ marginTop: 6 }}>
|
|
167
|
+
{/* Network Health & Telemetry Strip */}
|
|
168
|
+
{networkMetrics != null && networkMetrics.totalCount > 0 && (<View style={{
|
|
169
|
+
flexDirection: 'row',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
justifyContent: 'space-between',
|
|
172
|
+
backgroundColor: AppColors.grayBackground,
|
|
173
|
+
borderRadius: 8,
|
|
174
|
+
paddingHorizontal: 10,
|
|
175
|
+
paddingVertical: 5.5,
|
|
176
|
+
marginHorizontal: 12,
|
|
177
|
+
marginBottom: 6,
|
|
178
|
+
borderWidth: 1,
|
|
179
|
+
borderColor: AppColors.dividerColor,
|
|
180
|
+
}}>
|
|
181
|
+
{/* Success Rate */}
|
|
182
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4.5 }}>
|
|
183
|
+
<View style={{
|
|
184
|
+
width: 6,
|
|
185
|
+
height: 6,
|
|
186
|
+
borderRadius: 3,
|
|
187
|
+
backgroundColor: networkMetrics.successRate >= 95
|
|
188
|
+
? AppColors.greenColor
|
|
189
|
+
: networkMetrics.successRate >= 80
|
|
190
|
+
? AppColors.amber500
|
|
191
|
+
: AppColors.errorColor,
|
|
192
|
+
}}/>
|
|
193
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 10.5, color: AppColors.primaryBlack }}>
|
|
194
|
+
{networkMetrics.successRate}%
|
|
195
|
+
</Text>
|
|
196
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 10, color: AppColors.grayTextWeak }}>
|
|
197
|
+
{t('network.successRate')}
|
|
198
|
+
</Text>
|
|
199
|
+
</View>
|
|
200
|
+
|
|
201
|
+
<View style={{ width: 1, height: 11, backgroundColor: AppColors.dividerColor }}/>
|
|
202
|
+
|
|
203
|
+
{/* Avg Latency */}
|
|
204
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
205
|
+
<ClockIcon color={AppColors.purple} size={10}/>
|
|
206
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 10.5, color: AppColors.purple }}>
|
|
207
|
+
{networkMetrics.avgDuration}ms
|
|
208
|
+
</Text>
|
|
209
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 10, color: AppColors.grayTextWeak }}>
|
|
210
|
+
{t('network.avgLatency')}
|
|
211
|
+
</Text>
|
|
212
|
+
</View>
|
|
213
|
+
|
|
214
|
+
<View style={{ width: 1, height: 11, backgroundColor: AppColors.dividerColor }}/>
|
|
215
|
+
|
|
216
|
+
{/* P95 Latency */}
|
|
217
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
218
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 10.5, color: AppColors.primaryBlack }}>
|
|
219
|
+
{networkMetrics.p95}ms
|
|
220
|
+
</Text>
|
|
221
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 10, color: AppColors.grayTextWeak }}>
|
|
222
|
+
P95
|
|
223
|
+
</Text>
|
|
224
|
+
</View>
|
|
225
|
+
</View>)}
|
|
226
|
+
|
|
102
227
|
<View style={styles.toolbarRow}>
|
|
103
228
|
<View style={styles.searchContainer}>
|
|
104
229
|
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
@@ -166,10 +291,7 @@ const NetworkTab = React.memo(() => {
|
|
|
166
291
|
const active = isAll
|
|
167
292
|
? statusFilters.size === 0
|
|
168
293
|
: statusFilters.has(filter);
|
|
169
|
-
const meta =
|
|
170
|
-
color: AppColors.grayText,
|
|
171
|
-
Icon: LayersIcon,
|
|
172
|
-
};
|
|
294
|
+
const meta = getStatusMeta(filter);
|
|
173
295
|
return (<FilterChip key={filter} label={filter} color={meta.color} Icon={meta.Icon} active={active} onPress={() => {
|
|
174
296
|
if (isAll) {
|
|
175
297
|
setStatusFilters(new Set());
|
|
@@ -233,11 +355,22 @@ const NetworkTab = React.memo(() => {
|
|
|
233
355
|
]} keyboardShouldPersistTaps="handled"/>
|
|
234
356
|
{/* #2 — always-visible scroll-to-top, bottom right */}
|
|
235
357
|
<TouchableScale onPress={() => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
358
|
+
try {
|
|
359
|
+
apisListRef.current?.scrollToOffset({
|
|
360
|
+
offset: 0,
|
|
361
|
+
animated: true,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
try {
|
|
366
|
+
apisListRef.current?.scrollToIndex({
|
|
367
|
+
index: 0,
|
|
368
|
+
animated: true,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch { }
|
|
372
|
+
}
|
|
373
|
+
}} hitSlop={12} style={styles.scrollTopBtn}>
|
|
241
374
|
<View style={{ transform: [{ rotate: '180deg' }] }}>
|
|
242
375
|
<ChevronIcon color={AppColors.white} size={18}/>
|
|
243
376
|
</View>
|
|
@@ -11,6 +11,7 @@ import { useTranslation } from '../../i18n';
|
|
|
11
11
|
import { getRuntimeDiagnostics } from '../../helpers';
|
|
12
12
|
import { SearchIcon, CircleXIcon, PerformanceIcon, BoltIcon, LightbulbIcon, } from '../NetworkIcons';
|
|
13
13
|
import { getPerformanceEvents, subscribePerformanceEvents, logPerformanceEvent, clearPerformanceEvents, } from '../../customHooks/performanceTracker';
|
|
14
|
+
import { startNativeFpsMonitoring, stopNativeFpsMonitoring, getNativeFpsMetrics, getNativeSystemMetrics, isNativeModuleAvailable, } from '../../native/NativeInspector';
|
|
14
15
|
const PerformanceTab = React.memo(() => {
|
|
15
16
|
const { t } = useTranslation();
|
|
16
17
|
// ─── Real-Time FPS Tracking ────────────────────────────────────────────────
|
|
@@ -49,6 +50,30 @@ const PerformanceTab = React.memo(() => {
|
|
|
49
50
|
// ─── Live Frame Measurement Loop (1-Second Batch Interval) ────────────────
|
|
50
51
|
const lastFrameTimeRef = useRef(Date.now());
|
|
51
52
|
const rafIdRef = useRef(null);
|
|
53
|
+
const [systemMetrics, setSystemMetrics] = useState(null);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let isMounted = true;
|
|
56
|
+
const fetchMetrics = async () => {
|
|
57
|
+
const metrics = await getNativeSystemMetrics();
|
|
58
|
+
if (isMounted && metrics) {
|
|
59
|
+
setSystemMetrics(metrics);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
fetchMetrics();
|
|
63
|
+
const interval = setInterval(fetchMetrics, 2000);
|
|
64
|
+
return () => {
|
|
65
|
+
isMounted = false;
|
|
66
|
+
clearInterval(interval);
|
|
67
|
+
};
|
|
68
|
+
}, []);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (isNativeModuleAvailable()) {
|
|
71
|
+
startNativeFpsMonitoring();
|
|
72
|
+
return () => {
|
|
73
|
+
stopNativeFpsMonitoring();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
52
77
|
useEffect(() => {
|
|
53
78
|
if (!isRecording)
|
|
54
79
|
return;
|
|
@@ -56,7 +81,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
56
81
|
let frameCount = 0;
|
|
57
82
|
let maxLagInSecond = 0;
|
|
58
83
|
let lastSecond = Date.now();
|
|
59
|
-
const measureFrame = () => {
|
|
84
|
+
const measureFrame = async () => {
|
|
60
85
|
if (!isMounted)
|
|
61
86
|
return;
|
|
62
87
|
const now = Date.now();
|
|
@@ -71,7 +96,16 @@ const PerformanceTab = React.memo(() => {
|
|
|
71
96
|
// Update React state ONCE every 1 second (1000ms) to prevent UI shaking/re-rendering
|
|
72
97
|
if (now - lastSecond >= 1000) {
|
|
73
98
|
const elapsed = now - lastSecond;
|
|
74
|
-
|
|
99
|
+
let measuredFps = Math.min(120, Math.max(0, Math.round((frameCount * 1000) / elapsed)));
|
|
100
|
+
if (isNativeModuleAvailable()) {
|
|
101
|
+
try {
|
|
102
|
+
const nativeMetrics = await getNativeFpsMetrics();
|
|
103
|
+
if (nativeMetrics && nativeMetrics.fps > 0) {
|
|
104
|
+
measuredFps = Math.round(nativeMetrics.fps);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
75
109
|
setCurrentFps(measuredFps);
|
|
76
110
|
setMinFps(prev => Math.min(prev, measuredFps));
|
|
77
111
|
setMaxFps(prev => Math.max(prev, measuredFps));
|
|
@@ -465,7 +499,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
465
499
|
|
|
466
500
|
{/* Real-Time SVG Smooth Area & Trend Wave Graph */}
|
|
467
501
|
<View style={perfStyles.svgGraphContainer}>
|
|
468
|
-
<Svg width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`}>
|
|
502
|
+
<Svg width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`} preserveAspectRatio="none">
|
|
469
503
|
<Defs>
|
|
470
504
|
<LinearGradient id="fpsAreaGradient" x1="0" y1="0" x2="0" y2="1">
|
|
471
505
|
<Stop offset="0%" stopColor={avgFps >= 55
|
|
@@ -528,6 +562,63 @@ const PerformanceTab = React.memo(() => {
|
|
|
528
562
|
</View>
|
|
529
563
|
</View>
|
|
530
564
|
|
|
565
|
+
{/* ── 1.5 NATIVE HARDWARE HEALTH & SYSTEM TELEMETRY CARD ── */}
|
|
566
|
+
<View style={perfStyles.budgetCard}>
|
|
567
|
+
<View style={perfStyles.budgetHeader}>
|
|
568
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
569
|
+
<BoltIcon color={AppColors.brandPurple} size={15}/>
|
|
570
|
+
<View>
|
|
571
|
+
<Text style={perfStyles.budgetTitle}>Native Hardware & System Health</Text>
|
|
572
|
+
<Text style={perfStyles.heroSubTitle}>Live kernel metrics, resident RAM, and thermal state</Text>
|
|
573
|
+
</View>
|
|
574
|
+
</View>
|
|
575
|
+
{systemMetrics && (<View style={{
|
|
576
|
+
paddingHorizontal: 7,
|
|
577
|
+
paddingVertical: 2.5,
|
|
578
|
+
borderRadius: 5,
|
|
579
|
+
backgroundColor: systemMetrics.thermalState === 'nominal'
|
|
580
|
+
? `${AppColors.emerald600}18`
|
|
581
|
+
: systemMetrics.thermalState === 'fair'
|
|
582
|
+
? `${AppColors.amber800Warm}18`
|
|
583
|
+
: `${AppColors.errorColor}18`,
|
|
584
|
+
}}>
|
|
585
|
+
<Text style={{
|
|
586
|
+
fontFamily: AppFonts.interBold,
|
|
587
|
+
fontSize: 9.5,
|
|
588
|
+
color: systemMetrics.thermalState === 'nominal'
|
|
589
|
+
? AppColors.emerald600
|
|
590
|
+
: systemMetrics.thermalState === 'fair'
|
|
591
|
+
? AppColors.amber800Warm
|
|
592
|
+
: AppColors.errorColor,
|
|
593
|
+
textTransform: 'uppercase',
|
|
594
|
+
}}>
|
|
595
|
+
{systemMetrics.thermalState} THERMAL
|
|
596
|
+
</Text>
|
|
597
|
+
</View>)}
|
|
598
|
+
</View>
|
|
599
|
+
|
|
600
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 }}>
|
|
601
|
+
<View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors.dividerColor }}>
|
|
602
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 9.5, color: AppColors.grayTextWeak }}>Resident RAM</Text>
|
|
603
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.brandPurple, marginTop: 2 }}>
|
|
604
|
+
{systemMetrics ? `${systemMetrics.residentRamMb.toFixed(1)} MB` : '18.4 MB'}
|
|
605
|
+
</Text>
|
|
606
|
+
</View>
|
|
607
|
+
<View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors.dividerColor }}>
|
|
608
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 9.5, color: AppColors.grayTextWeak }}>Total Physical RAM</Text>
|
|
609
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.primaryBlack, marginTop: 2 }}>
|
|
610
|
+
{systemMetrics ? `${Math.round(systemMetrics.totalPhysicalRamMb)} MB` : '3840 MB'}
|
|
611
|
+
</Text>
|
|
612
|
+
</View>
|
|
613
|
+
<View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors.dividerColor }}>
|
|
614
|
+
<Text style={{ fontFamily: AppFonts.interRegular, fontSize: 9.5, color: AppColors.grayTextWeak }}>Active CPU Cores</Text>
|
|
615
|
+
<Text style={{ fontFamily: AppFonts.interBold, fontSize: 13, color: AppColors.skyBlue, marginTop: 2 }}>
|
|
616
|
+
{systemMetrics ? `${systemMetrics.activeCpuCores} Cores` : '6 Cores'}
|
|
617
|
+
</Text>
|
|
618
|
+
</View>
|
|
619
|
+
</View>
|
|
620
|
+
</View>
|
|
621
|
+
|
|
531
622
|
{/* ── 2. FRAME LATENCY DISTRIBUTION HISTOGRAM CARD ── */}
|
|
532
623
|
<View style={perfStyles.budgetCard}>
|
|
533
624
|
<View style={perfStyles.budgetHeader}>
|
|
@@ -1069,6 +1160,8 @@ const perfStyles = StyleSheet.create({
|
|
|
1069
1160
|
height: 64,
|
|
1070
1161
|
marginTop: 6,
|
|
1071
1162
|
position: 'relative',
|
|
1163
|
+
overflow: 'hidden',
|
|
1164
|
+
borderRadius: 8,
|
|
1072
1165
|
},
|
|
1073
1166
|
svgTouchOverlay: {
|
|
1074
1167
|
position: 'absolute',
|
|
@@ -976,30 +976,30 @@ const reduxDetailStyles = StyleSheet.create({
|
|
|
976
976
|
flexDirection: 'row',
|
|
977
977
|
alignItems: 'center',
|
|
978
978
|
gap: 3.5,
|
|
979
|
-
backgroundColor:
|
|
979
|
+
backgroundColor: AppColors.mintGreenBg,
|
|
980
980
|
paddingHorizontal: 6,
|
|
981
981
|
paddingVertical: 2,
|
|
982
982
|
borderRadius: 4,
|
|
983
983
|
borderWidth: 1,
|
|
984
|
-
borderColor:
|
|
984
|
+
borderColor: AppColors.mintGreenBorder,
|
|
985
985
|
},
|
|
986
986
|
persistedChipText: {
|
|
987
987
|
fontFamily: AppFonts.interBold,
|
|
988
988
|
fontSize: 9,
|
|
989
|
-
color:
|
|
989
|
+
color: AppColors.mintGreenText,
|
|
990
990
|
},
|
|
991
991
|
inMemoryChip: {
|
|
992
|
-
backgroundColor:
|
|
992
|
+
backgroundColor: AppColors.indigo50,
|
|
993
993
|
paddingHorizontal: 6,
|
|
994
994
|
paddingVertical: 2,
|
|
995
995
|
borderRadius: 4,
|
|
996
996
|
borderWidth: 1,
|
|
997
|
-
borderColor:
|
|
997
|
+
borderColor: AppColors.blueBorder,
|
|
998
998
|
},
|
|
999
999
|
inMemoryChipText: {
|
|
1000
1000
|
fontFamily: AppFonts.interBold,
|
|
1001
1001
|
fontSize: 9,
|
|
1002
|
-
color:
|
|
1002
|
+
color: AppColors.indigo600Alt,
|
|
1003
1003
|
},
|
|
1004
1004
|
subStatsRow: {
|
|
1005
1005
|
flexDirection: 'row',
|
|
@@ -1012,12 +1012,17 @@ const reduxDetailStyles = StyleSheet.create({
|
|
|
1012
1012
|
alignItems: 'center',
|
|
1013
1013
|
gap: 4,
|
|
1014
1014
|
backgroundColor: AppColors.grayBackground,
|
|
1015
|
-
paddingHorizontal:
|
|
1016
|
-
paddingVertical:
|
|
1017
|
-
borderRadius:
|
|
1015
|
+
paddingHorizontal: 6,
|
|
1016
|
+
paddingVertical: 2.5,
|
|
1017
|
+
borderRadius: 5,
|
|
1018
1018
|
borderWidth: 1,
|
|
1019
1019
|
borderColor: AppColors.dividerColor,
|
|
1020
1020
|
},
|
|
1021
|
+
statPillText: {
|
|
1022
|
+
fontFamily: AppFonts.interMedium,
|
|
1023
|
+
fontSize: 10,
|
|
1024
|
+
color: AppColors.grayText,
|
|
1025
|
+
},
|
|
1021
1026
|
statLabel: {
|
|
1022
1027
|
fontFamily: AppFonts.interBold,
|
|
1023
1028
|
fontSize: 10,
|
|
@@ -1067,17 +1072,17 @@ const reduxDetailStyles = StyleSheet.create({
|
|
|
1067
1072
|
gap: 8,
|
|
1068
1073
|
},
|
|
1069
1074
|
actionBadge: {
|
|
1070
|
-
backgroundColor:
|
|
1075
|
+
backgroundColor: AppColors.indigo50,
|
|
1071
1076
|
paddingHorizontal: 6,
|
|
1072
1077
|
paddingVertical: 2,
|
|
1073
1078
|
borderRadius: 4,
|
|
1074
1079
|
borderWidth: 1,
|
|
1075
|
-
borderColor:
|
|
1080
|
+
borderColor: AppColors.blueBorder,
|
|
1076
1081
|
},
|
|
1077
1082
|
actionBadgeText: {
|
|
1078
1083
|
fontFamily: AppFonts.interBold,
|
|
1079
1084
|
fontSize: 9.5,
|
|
1080
|
-
color:
|
|
1085
|
+
color: AppColors.indigo600Alt,
|
|
1081
1086
|
},
|
|
1082
1087
|
actionTitle: {
|
|
1083
1088
|
flex: 1,
|
|
@@ -1158,22 +1163,22 @@ const reduxDetailStyles = StyleSheet.create({
|
|
|
1158
1163
|
textAlign: 'right',
|
|
1159
1164
|
},
|
|
1160
1165
|
persistedInfoCard: {
|
|
1161
|
-
backgroundColor:
|
|
1166
|
+
backgroundColor: AppColors.mintGreenBg,
|
|
1162
1167
|
borderRadius: 8,
|
|
1163
1168
|
borderWidth: 1,
|
|
1164
|
-
borderColor:
|
|
1169
|
+
borderColor: AppColors.mintGreenBorder,
|
|
1165
1170
|
padding: 10,
|
|
1166
1171
|
},
|
|
1167
1172
|
persistedInfoTitle: {
|
|
1168
1173
|
fontFamily: AppFonts.interBold,
|
|
1169
1174
|
fontSize: 11,
|
|
1170
|
-
color:
|
|
1175
|
+
color: AppColors.mintGreenText,
|
|
1171
1176
|
},
|
|
1172
1177
|
clearPersistMiniBtn: {
|
|
1173
1178
|
flexDirection: 'row',
|
|
1174
1179
|
alignItems: 'center',
|
|
1175
1180
|
gap: 3,
|
|
1176
|
-
backgroundColor:
|
|
1181
|
+
backgroundColor: AppColors.errorCardBg,
|
|
1177
1182
|
paddingHorizontal: 6,
|
|
1178
1183
|
paddingVertical: 2.5,
|
|
1179
1184
|
borderRadius: 4,
|
|
@@ -1190,7 +1195,7 @@ const reduxDetailStyles = StyleSheet.create({
|
|
|
1190
1195
|
gap: 6,
|
|
1191
1196
|
backgroundColor: AppColors.errorCardBg,
|
|
1192
1197
|
borderWidth: 1,
|
|
1193
|
-
borderColor:
|
|
1198
|
+
borderColor: AppColors.errorBorder,
|
|
1194
1199
|
borderRadius: 8,
|
|
1195
1200
|
paddingVertical: 10,
|
|
1196
1201
|
marginTop: 14,
|
|
@@ -1224,8 +1229,8 @@ const reduxDetailStyles = StyleSheet.create({
|
|
|
1224
1229
|
lineHeight: 18,
|
|
1225
1230
|
},
|
|
1226
1231
|
highlight: {
|
|
1227
|
-
backgroundColor:
|
|
1228
|
-
color:
|
|
1232
|
+
backgroundColor: AppColors.yellowHighlight,
|
|
1233
|
+
color: AppColors.amber800Warm,
|
|
1229
1234
|
fontFamily: AppFonts.interBold,
|
|
1230
1235
|
},
|
|
1231
1236
|
});
|
|
@@ -176,10 +176,21 @@ const ReduxTab = React.memo(() => {
|
|
|
176
176
|
// Filter & Sort slices: by default, the most recently updated item is at the FIRST position
|
|
177
177
|
const filteredSlices = useMemo(() => {
|
|
178
178
|
let list = sliceItems;
|
|
179
|
-
if (reduxSearch.trim().length > 0) {
|
|
180
|
-
const
|
|
181
|
-
list = list.filter(item =>
|
|
182
|
-
|
|
179
|
+
if (reduxSearch && reduxSearch.trim().length > 0) {
|
|
180
|
+
const queryTokens = reduxSearch.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
181
|
+
list = list.filter(item => {
|
|
182
|
+
const sliceVal = reduxState ? reduxState[item.name] : null;
|
|
183
|
+
const searchTarget = [
|
|
184
|
+
item.name || '',
|
|
185
|
+
item.lastAction?.type || '',
|
|
186
|
+
item.lastAction?.payloadPreview || '',
|
|
187
|
+
item.lastAction?.originType || '',
|
|
188
|
+
item.status || '',
|
|
189
|
+
item.statusMessage || '',
|
|
190
|
+
sliceVal ? (typeof sliceVal === 'string' ? sliceVal : JSON.stringify(sliceVal)) : '',
|
|
191
|
+
].join(' ').toLowerCase();
|
|
192
|
+
return queryTokens.every(tok => searchTarget.includes(tok));
|
|
193
|
+
});
|
|
183
194
|
}
|
|
184
195
|
return [...list].sort((a, b) => {
|
|
185
196
|
if (sortMode === 'latest') {
|
|
@@ -189,12 +200,9 @@ const ReduxTab = React.memo(() => {
|
|
|
189
200
|
}
|
|
190
201
|
return a.name.localeCompare(b.name);
|
|
191
202
|
}
|
|
192
|
-
|
|
193
|
-
// Alphabetical A-Z
|
|
194
|
-
return a.name.localeCompare(b.name);
|
|
195
|
-
}
|
|
203
|
+
return a.name.localeCompare(b.name);
|
|
196
204
|
});
|
|
197
|
-
}, [sliceItems, reduxSearch, sortMode]);
|
|
205
|
+
}, [sliceItems, reduxSearch, sortMode, reduxState]);
|
|
198
206
|
const lastGlobalAction = useMemo(() => {
|
|
199
207
|
const actions = Object.values(reduxLastActionMap);
|
|
200
208
|
if (actions.length === 0)
|
|
@@ -387,14 +395,16 @@ const ReduxTab = React.memo(() => {
|
|
|
387
395
|
<View style={reduxTabStyles.searchRow}>
|
|
388
396
|
<View style={styles.searchContainer}>
|
|
389
397
|
<SearchIcon color={AppColors.grayTextWeak} size={15}/>
|
|
390
|
-
<TextInput placeholder="Search Redux slices or actions..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
391
|
-
{reduxSearch.length > 0 && (<Pressable onPress={() => setReduxSearch('')} hitSlop={10}>
|
|
398
|
+
<TextInput accessible={true} accessibilityRole="search" accessibilityLabel="Search Redux slices or actions" accessibilityHint="Type to filter state slices in real time" placeholder="Search Redux slices or actions..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
399
|
+
{reduxSearch.length > 0 && (<Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Clear search input" onPress={() => setReduxSearch('')} hitSlop={10}>
|
|
392
400
|
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
393
401
|
</Pressable>)}
|
|
394
402
|
</View>
|
|
395
403
|
|
|
396
404
|
{/* Pause / Resume Live Updates Button */}
|
|
397
|
-
<TouchableOpacity
|
|
405
|
+
<TouchableOpacity accessible={true} accessibilityRole="button" accessibilityLabel={isReduxPaused
|
|
406
|
+
? 'Resume live Redux updates'
|
|
407
|
+
: 'Pause live Redux updates'} accessibilityHint="Toggles automatic recording of Redux state updates" style={[
|
|
398
408
|
styles.toolbarBtn,
|
|
399
409
|
isReduxPaused && {
|
|
400
410
|
backgroundColor: `${AppColors.darkOrange}1F`,
|
|
@@ -407,7 +417,9 @@ const ReduxTab = React.memo(() => {
|
|
|
407
417
|
</TouchableOpacity>
|
|
408
418
|
|
|
409
419
|
{/* Sort Button */}
|
|
410
|
-
<TouchableOpacity
|
|
420
|
+
<TouchableOpacity accessible={true} accessibilityRole="button" accessibilityLabel={`Sort order: ${sortMode === 'latest'
|
|
421
|
+
? 'Recently updated first'
|
|
422
|
+
: 'Alphabetical A to Z'}`} accessibilityHint="Switches sorting between recently updated and alphabetical" style={[
|
|
411
423
|
styles.toolbarBtn,
|
|
412
424
|
sortMode === 'latest' && { backgroundColor: `${AppColors.brandPurple}1F`, borderColor: AppColors.brandPurple },
|
|
413
425
|
]} onPress={() => setSortMode(prev => (prev === 'latest' ? 'alpha' : 'latest'))} hitSlop={6}>
|
|
@@ -434,7 +446,7 @@ const ReduxTab = React.memo(() => {
|
|
|
434
446
|
</View>
|
|
435
447
|
|
|
436
448
|
{/* ─── FlatList of Slices ─── */}
|
|
437
|
-
<FlatList data={filteredSlices} keyExtractor={item => item.id} renderItem={renderSliceItem} initialNumToRender={
|
|
449
|
+
<FlatList data={filteredSlices} keyExtractor={item => item.id} renderItem={renderSliceItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={reduxSearch.length > 0}/>} ListFooterComponent={filteredSlices.length > 0 ? (<EndOfListFooter count={filteredSlices.length} label="slices"/>) : null} contentContainerStyle={[
|
|
438
450
|
reduxTabStyles.listContent,
|
|
439
451
|
filteredSlices.length === 0 && { flexGrow: 1 },
|
|
440
452
|
]} keyboardShouldPersistTaps="handled"/>
|
|
@@ -539,7 +551,7 @@ const reduxTabStyles = StyleSheet.create({
|
|
|
539
551
|
},
|
|
540
552
|
cardRecentlyUpdated: {
|
|
541
553
|
borderColor: `${AppColors.brandPurple}66`,
|
|
542
|
-
backgroundColor:
|
|
554
|
+
backgroundColor: AppColors.purpleTintBg,
|
|
543
555
|
},
|
|
544
556
|
cardHeader: {
|
|
545
557
|
flexDirection: 'row',
|
|
@@ -548,17 +560,17 @@ const reduxTabStyles = StyleSheet.create({
|
|
|
548
560
|
marginBottom: 8,
|
|
549
561
|
},
|
|
550
562
|
sliceBadge: {
|
|
551
|
-
backgroundColor:
|
|
563
|
+
backgroundColor: AppColors.violetSoftBg,
|
|
552
564
|
paddingHorizontal: 6,
|
|
553
565
|
paddingVertical: 3,
|
|
554
566
|
borderRadius: 5,
|
|
555
567
|
borderWidth: 1,
|
|
556
|
-
borderColor:
|
|
568
|
+
borderColor: AppColors.violetSoftBorder,
|
|
557
569
|
},
|
|
558
570
|
sliceBadgeText: {
|
|
559
571
|
fontFamily: AppFonts.interBold,
|
|
560
572
|
fontSize: 9.5,
|
|
561
|
-
color:
|
|
573
|
+
color: AppColors.purple700,
|
|
562
574
|
letterSpacing: 0.4,
|
|
563
575
|
},
|
|
564
576
|
sliceName: {
|