react-native-inapp-inspector 1.1.35 → 2.0.1
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 +890 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
- package/dist/commonjs/components/ConsoleLogCard.js +5 -5
- package/dist/commonjs/components/CopyButton.js +3 -0
- package/dist/commonjs/components/EndOfListFooter.js +1 -1
- package/dist/commonjs/components/ErrorBoundary.js +3 -0
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +25 -73
- package/dist/commonjs/components/Inspector/BundleTab.js +671 -189
- package/dist/commonjs/components/Inspector/ConsoleTab.js +110 -99
- package/dist/commonjs/components/Inspector/CrashTab.js +99 -91
- package/dist/commonjs/components/Inspector/InspectorHeader.js +107 -20
- package/dist/commonjs/components/Inspector/MainScreen.js +111 -68
- package/dist/commonjs/components/Inspector/NetworkTab.js +151 -30
- package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
- package/dist/commonjs/components/Inspector/ReduxTab.js +18 -10
- package/dist/commonjs/components/Inspector/SettingsPanel.js +1218 -1238
- package/dist/commonjs/components/Inspector/TabBar.js +4 -2
- package/dist/commonjs/components/JsonViewer.js +9 -4
- package/dist/commonjs/components/LogCard.js +31 -1
- package/dist/commonjs/components/LogSyntaxHighlighter.d.ts +16 -0
- package/dist/commonjs/components/LogSyntaxHighlighter.js +130 -0
- package/dist/commonjs/components/NetworkIcons.d.ts +3 -0
- package/dist/commonjs/components/NetworkIcons.js +15 -1
- package/dist/commonjs/components/Slider.d.ts +13 -0
- package/dist/commonjs/components/Slider.js +261 -0
- package/dist/commonjs/components/TouchableScale.d.ts +1 -0
- 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 +4 -0
- package/dist/commonjs/customHooks/analyticsLogger.js +8 -6
- package/dist/commonjs/customHooks/crashHandler.js +8 -1
- package/dist/commonjs/helpers/index.d.ts +1 -0
- package/dist/commonjs/helpers/index.js +25 -1
- package/dist/commonjs/helpers/searchQueryParser.d.ts +6 -0
- package/dist/commonjs/helpers/searchQueryParser.js +236 -0
- package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
- package/dist/commonjs/helpers/settingsStore.js +59 -3
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +220 -43
- 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/index.js +41 -41
- package/dist/commonjs/types/enums.d.ts +1 -1
- package/dist/commonjs/types/interfaces.d.ts +9 -0
- package/dist/esm/components/ConsoleLogCard.js +5 -5
- package/dist/esm/components/CopyButton.js +3 -0
- package/dist/esm/components/EndOfListFooter.js +2 -2
- package/dist/esm/components/ErrorBoundary.js +3 -0
- package/dist/esm/components/Inspector/AnalyticsTab.js +27 -75
- package/dist/esm/components/Inspector/BundleTab.js +671 -189
- package/dist/esm/components/Inspector/ConsoleTab.js +111 -100
- package/dist/esm/components/Inspector/CrashTab.js +99 -91
- package/dist/esm/components/Inspector/InspectorHeader.js +108 -21
- package/dist/esm/components/Inspector/MainScreen.js +113 -70
- package/dist/esm/components/Inspector/NetworkTab.js +152 -31
- package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
- package/dist/esm/components/Inspector/ReduxTab.js +18 -10
- package/dist/esm/components/Inspector/SettingsPanel.js +1222 -1242
- package/dist/esm/components/Inspector/TabBar.js +4 -2
- package/dist/esm/components/JsonViewer.js +9 -4
- package/dist/esm/components/LogCard.js +32 -2
- package/dist/esm/components/LogSyntaxHighlighter.d.ts +16 -0
- package/dist/esm/components/LogSyntaxHighlighter.js +123 -0
- package/dist/esm/components/NetworkIcons.d.ts +3 -0
- package/dist/esm/components/NetworkIcons.js +11 -0
- package/dist/esm/components/Slider.d.ts +13 -0
- package/dist/esm/components/Slider.js +222 -0
- package/dist/esm/components/TouchableScale.d.ts +1 -0
- 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 +4 -0
- package/dist/esm/customHooks/analyticsLogger.js +6 -5
- package/dist/esm/customHooks/crashHandler.js +8 -1
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +11 -1
- package/dist/esm/helpers/searchQueryParser.d.ts +6 -0
- package/dist/esm/helpers/searchQueryParser.js +233 -0
- package/dist/esm/helpers/settingsStore.d.ts +9 -0
- package/dist/esm/helpers/settingsStore.js +58 -3
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +207 -44
- 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/index.js +41 -41
- 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 +760 -5
- package/package.json +13 -2
- 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
|
@@ -13,43 +13,74 @@ import { AppColors } from '../../styles/AppColors';
|
|
|
13
13
|
import { AppFonts } from '../../styles/AppFonts';
|
|
14
14
|
import { STATUS_FILTERS, METHOD_COLORS } from '../../constants';
|
|
15
15
|
import { useTranslation } from '../../i18n';
|
|
16
|
-
import { SearchIcon, ClearIcon, TrashIcon, SortArrowIcon, FilterIcon, ChevronIcon,
|
|
16
|
+
import { SearchIcon, ClearIcon, TrashIcon, SortArrowIcon, FilterIcon, ChevronIcon, CircleCheckIcon, CircleXIcon, CircleAlertIcon, LayersIcon, HeaderPauseIcon, ClockIcon, } from '../NetworkIcons';
|
|
17
17
|
const STATUS_META = {
|
|
18
18
|
ALL: { color: AppColors.grayText, Icon: LayersIcon },
|
|
19
19
|
'2xx': { color: AppColors.greenColor, Icon: CircleCheckIcon },
|
|
20
|
+
'200': { color: AppColors.greenColor, Icon: CircleCheckIcon },
|
|
20
21
|
'3xx': { color: AppColors.warningIconGold, Icon: CircleAlertIcon },
|
|
22
|
+
'300': { color: AppColors.warningIconGold, Icon: CircleAlertIcon },
|
|
21
23
|
'4xx': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
24
|
+
'400': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
25
|
+
'404': { color: AppColors.darkOrange, Icon: CircleAlertIcon },
|
|
22
26
|
'5xx': { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
27
|
+
'500': { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
23
28
|
Failed: { color: AppColors.errorColor, Icon: CircleXIcon },
|
|
24
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
|
+
};
|
|
25
54
|
const FilterChip = React.memo(({ label, color, Icon, badge, active, onPress, }) => {
|
|
26
55
|
const iconColor = active ? color : AppColors.grayTextWeak;
|
|
27
56
|
return (<TouchableScale style={styles.statusFilterWrap} onPress={onPress} hitSlop={10}>
|
|
28
57
|
<View style={[
|
|
29
58
|
styles.statusFilterChip,
|
|
59
|
+
{
|
|
60
|
+
paddingHorizontal: 9,
|
|
61
|
+
paddingVertical: 5,
|
|
62
|
+
borderRadius: 7,
|
|
63
|
+
borderWidth: 1,
|
|
64
|
+
borderColor: active ? color : `${AppColors.grayBorderSecondary}`,
|
|
65
|
+
backgroundColor: active ? `${color}14` : AppColors.grayBackground,
|
|
66
|
+
},
|
|
30
67
|
active && {
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
shadowColor: color,
|
|
69
|
+
shadowOffset: { width: 0, height: 1 },
|
|
70
|
+
shadowOpacity: 0.2,
|
|
71
|
+
shadowRadius: 2,
|
|
72
|
+
elevation: 1.5,
|
|
33
73
|
},
|
|
34
74
|
]}>
|
|
35
|
-
<View style={[
|
|
36
|
-
styles.filterCheckbox,
|
|
37
|
-
active && [
|
|
38
|
-
styles.filterCheckboxActive,
|
|
39
|
-
{ backgroundColor: color },
|
|
40
|
-
],
|
|
41
|
-
]}>
|
|
42
|
-
{active && (<CheckIcon color={AppColors.white} size={10}/>)}
|
|
43
|
-
</View>
|
|
44
75
|
{badge != null ? (<View style={[
|
|
45
76
|
styles.methodBadgeMini,
|
|
46
77
|
{ backgroundColor: badge },
|
|
47
78
|
]}/>) : (Icon && (<Icon color={iconColor} size={13}/>))}
|
|
48
79
|
<Text numberOfLines={1} style={[
|
|
49
80
|
styles.statusFilterText,
|
|
50
|
-
|
|
51
|
-
color,
|
|
52
|
-
fontFamily: AppFonts.interBold,
|
|
81
|
+
{
|
|
82
|
+
color: active ? color : AppColors.grayText,
|
|
83
|
+
fontFamily: active ? AppFonts.interBold : AppFonts.interMedium,
|
|
53
84
|
},
|
|
54
85
|
]}>
|
|
55
86
|
{label}
|
|
@@ -62,6 +93,19 @@ const NetworkTab = React.memo(() => {
|
|
|
62
93
|
const { t } = useTranslation();
|
|
63
94
|
const filtersAccordion = useAccordion(false, 300, 260);
|
|
64
95
|
const apisListRef = useRef(null);
|
|
96
|
+
const [isSearchFocused, setIsSearchFocused] = React.useState(false);
|
|
97
|
+
const QUICK_API_QUERY_TAGS = useMemo(() => [
|
|
98
|
+
{ label: '⚠️ Failed', query: 'is:error' },
|
|
99
|
+
{ label: '🐢 Slow >1s', query: 'slow:>1s' },
|
|
100
|
+
{ label: 'POST', query: 'm:POST' },
|
|
101
|
+
{ label: 'GET', query: 'm:GET' },
|
|
102
|
+
{ label: 'GraphQL', query: 'is:graphql' },
|
|
103
|
+
{ label: 'Axios', query: 'client:axios' },
|
|
104
|
+
{ label: '200 OK', query: 'status:200' },
|
|
105
|
+
{ label: '404', query: 'status:404' },
|
|
106
|
+
{ label: '500', query: 'status:500' },
|
|
107
|
+
{ label: '🔒 HTTPS', query: 'is:https' },
|
|
108
|
+
], []);
|
|
65
109
|
const networkMetrics = useMemo(() => {
|
|
66
110
|
if (!logs || logs.length === 0)
|
|
67
111
|
return null;
|
|
@@ -134,7 +178,7 @@ const NetworkTab = React.memo(() => {
|
|
|
134
178
|
setSelected,
|
|
135
179
|
]);
|
|
136
180
|
return (<View style={{ flex: 1 }}>
|
|
137
|
-
<FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={
|
|
181
|
+
<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 }}>
|
|
138
182
|
{/* Network Health & Telemetry Strip */}
|
|
139
183
|
{networkMetrics != null && networkMetrics.totalCount > 0 && (<View style={{
|
|
140
184
|
flexDirection: 'row',
|
|
@@ -196,12 +240,38 @@ const NetworkTab = React.memo(() => {
|
|
|
196
240
|
</View>)}
|
|
197
241
|
|
|
198
242
|
<View style={styles.toolbarRow}>
|
|
199
|
-
<View style={
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
243
|
+
<View style={[
|
|
244
|
+
styles.searchContainer,
|
|
245
|
+
isSearchFocused && {
|
|
246
|
+
borderColor: AppColors.purple,
|
|
247
|
+
borderWidth: 1.5,
|
|
248
|
+
},
|
|
249
|
+
]}>
|
|
250
|
+
<SearchIcon color={isSearchFocused ? AppColors.purple : AppColors.grayTextWeak} size={16}/>
|
|
251
|
+
<TextInput placeholder="Search url, body, is:error, slow:>1s..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} onFocus={() => setIsSearchFocused(true)} onBlur={() => setIsSearchFocused(false)} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
252
|
+
{search.length > 0 && (<View style={{
|
|
253
|
+
flexDirection: 'row',
|
|
254
|
+
alignItems: 'center',
|
|
255
|
+
gap: 4,
|
|
256
|
+
}}>
|
|
257
|
+
<View style={{
|
|
258
|
+
backgroundColor: `${AppColors.purple}20`,
|
|
259
|
+
borderRadius: 10,
|
|
260
|
+
paddingHorizontal: 6,
|
|
261
|
+
paddingVertical: 2,
|
|
262
|
+
}}>
|
|
263
|
+
<Text style={{
|
|
264
|
+
color: AppColors.purple,
|
|
265
|
+
fontSize: 10,
|
|
266
|
+
fontFamily: AppFonts.interBold,
|
|
267
|
+
}}>
|
|
268
|
+
{filteredLogs.length}
|
|
269
|
+
</Text>
|
|
270
|
+
</View>
|
|
271
|
+
<Pressable onPress={() => setSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
272
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
273
|
+
</Pressable>
|
|
274
|
+
</View>)}
|
|
205
275
|
</View>
|
|
206
276
|
|
|
207
277
|
<View style={styles.toolbarRight}>
|
|
@@ -248,6 +318,49 @@ const NetworkTab = React.memo(() => {
|
|
|
248
318
|
</View>
|
|
249
319
|
</View>
|
|
250
320
|
|
|
321
|
+
{/* Advanced Search Quick Query Suggestions Bar */}
|
|
322
|
+
{(isSearchFocused || search.length > 0) && (<ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginBottom: 6, maxHeight: 30 }} contentContainerStyle={{
|
|
323
|
+
paddingHorizontal: 12,
|
|
324
|
+
flexDirection: 'row',
|
|
325
|
+
alignItems: 'center',
|
|
326
|
+
gap: 6,
|
|
327
|
+
}}>
|
|
328
|
+
{QUICK_API_QUERY_TAGS.map(item => {
|
|
329
|
+
const isSelected = search.includes(item.query);
|
|
330
|
+
return (<TouchableScale key={item.query} onPress={() => {
|
|
331
|
+
if (isSelected) {
|
|
332
|
+
setSearch(prev => prev.replace(item.query, '').trim());
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
setSearch(prev => prev ? `${prev} ${item.query}`.trim() : item.query);
|
|
336
|
+
}
|
|
337
|
+
}}>
|
|
338
|
+
<View style={{
|
|
339
|
+
paddingHorizontal: 8,
|
|
340
|
+
paddingVertical: 3.5,
|
|
341
|
+
borderRadius: 6,
|
|
342
|
+
backgroundColor: isSelected
|
|
343
|
+
? AppColors.purple
|
|
344
|
+
: `${AppColors.purple}14`,
|
|
345
|
+
borderWidth: 1,
|
|
346
|
+
borderColor: isSelected
|
|
347
|
+
? AppColors.purple
|
|
348
|
+
: `${AppColors.purple}30`,
|
|
349
|
+
}}>
|
|
350
|
+
<Text style={{
|
|
351
|
+
fontFamily: AppFonts.interBold,
|
|
352
|
+
fontSize: 10,
|
|
353
|
+
color: isSelected
|
|
354
|
+
? AppColors.white
|
|
355
|
+
: AppColors.purple,
|
|
356
|
+
}}>
|
|
357
|
+
{item.label}
|
|
358
|
+
</Text>
|
|
359
|
+
</View>
|
|
360
|
+
</TouchableScale>);
|
|
361
|
+
})}
|
|
362
|
+
</ScrollView>)}
|
|
363
|
+
|
|
251
364
|
<Animated.View style={[
|
|
252
365
|
filtersAccordion.bodyStyle,
|
|
253
366
|
{ overflow: 'hidden' },
|
|
@@ -262,10 +375,7 @@ const NetworkTab = React.memo(() => {
|
|
|
262
375
|
const active = isAll
|
|
263
376
|
? statusFilters.size === 0
|
|
264
377
|
: statusFilters.has(filter);
|
|
265
|
-
const meta =
|
|
266
|
-
color: AppColors.grayText,
|
|
267
|
-
Icon: LayersIcon,
|
|
268
|
-
};
|
|
378
|
+
const meta = getStatusMeta(filter);
|
|
269
379
|
return (<FilterChip key={filter} label={filter} color={meta.color} Icon={meta.Icon} active={active} onPress={() => {
|
|
270
380
|
if (isAll) {
|
|
271
381
|
setStatusFilters(new Set());
|
|
@@ -329,11 +439,22 @@ const NetworkTab = React.memo(() => {
|
|
|
329
439
|
]} keyboardShouldPersistTaps="handled"/>
|
|
330
440
|
{/* #2 — always-visible scroll-to-top, bottom right */}
|
|
331
441
|
<TouchableScale onPress={() => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
442
|
+
try {
|
|
443
|
+
apisListRef.current?.scrollToOffset({
|
|
444
|
+
offset: 0,
|
|
445
|
+
animated: true,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
try {
|
|
450
|
+
apisListRef.current?.scrollToIndex({
|
|
451
|
+
index: 0,
|
|
452
|
+
animated: true,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
catch { }
|
|
456
|
+
}
|
|
457
|
+
}} hitSlop={12} style={styles.scrollTopBtn}>
|
|
337
458
|
<View style={{ transform: [{ rotate: '180deg' }] }}>
|
|
338
459
|
<ChevronIcon color={AppColors.white} size={18}/>
|
|
339
460
|
</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',
|
|
@@ -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)
|
|
@@ -438,7 +446,7 @@ const ReduxTab = React.memo(() => {
|
|
|
438
446
|
</View>
|
|
439
447
|
|
|
440
448
|
{/* ─── FlatList of Slices ─── */}
|
|
441
|
-
<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={[
|
|
442
450
|
reduxTabStyles.listContent,
|
|
443
451
|
filteredSlices.length === 0 && { flexGrow: 1 },
|
|
444
452
|
]} keyboardShouldPersistTaps="handled"/>
|