react-native-inapp-inspector 1.1.26 → 1.1.28
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/android/build.gradle +1 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +34 -0
- package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
- package/dist/commonjs/components/ErrorBoundary.js +530 -100
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +176 -126
- package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
- package/dist/commonjs/components/Inspector/BundleTab.js +145 -21
- package/dist/commonjs/components/Inspector/CrashTab.js +0 -8
- package/dist/commonjs/components/Inspector/NetworkDetail.js +41 -0
- package/dist/commonjs/components/Inspector/PerformanceTab.d.ts +5 -0
- package/dist/commonjs/components/Inspector/PerformanceTab.js +588 -155
- package/dist/commonjs/components/Inspector/SettingsPanel.js +57 -22
- package/dist/commonjs/components/Inspector/TabBar.js +13 -1
- package/dist/commonjs/components/LogCard.js +75 -34
- package/dist/commonjs/components/NetworkIcons.d.ts +112 -102
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -11
- package/dist/commonjs/customHooks/analyticsLogger.js +8 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.js +121 -124
- package/dist/commonjs/customHooks/networkLogger.d.ts +1 -0
- package/dist/commonjs/customHooks/networkLogger.js +16 -0
- package/dist/commonjs/customHooks/performanceTracker.d.ts +74 -0
- package/dist/commonjs/customHooks/performanceTracker.js +546 -392
- package/dist/commonjs/customHooks/reduxLogger.d.ts +1 -0
- package/dist/commonjs/customHooks/reduxLogger.js +3 -1
- package/dist/commonjs/helpers/index.js +27 -3
- package/dist/commonjs/i18n/locales/en.json +225 -4
- package/dist/commonjs/index.d.ts +1 -1
- package/dist/commonjs/index.js +36 -2
- package/dist/commonjs/styles/index.d.ts +3 -1
- package/dist/commonjs/styles/index.js +8 -6
- package/dist/commonjs/types/interfaces.d.ts +3 -0
- package/dist/esm/components/ErrorBoundary.d.ts +1 -1
- package/dist/esm/components/ErrorBoundary.js +498 -101
- package/dist/esm/components/Inspector/AnalyticsTab.js +177 -127
- package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
- package/dist/esm/components/Inspector/BundleTab.js +145 -23
- package/dist/esm/components/Inspector/CrashTab.js +0 -8
- package/dist/esm/components/Inspector/NetworkDetail.js +41 -0
- package/dist/esm/components/Inspector/PerformanceTab.d.ts +5 -0
- package/dist/esm/components/Inspector/PerformanceTab.js +591 -158
- package/dist/esm/components/Inspector/SettingsPanel.js +57 -22
- package/dist/esm/components/Inspector/TabBar.js +13 -1
- package/dist/esm/components/LogCard.js +75 -34
- package/dist/esm/components/NetworkIcons.d.ts +112 -102
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/analyticsLogger.d.ts +1 -11
- package/dist/esm/customHooks/analyticsLogger.js +6 -0
- package/dist/esm/customHooks/bundleAnalyzer.js +121 -124
- package/dist/esm/customHooks/networkLogger.d.ts +1 -0
- package/dist/esm/customHooks/networkLogger.js +16 -0
- package/dist/esm/customHooks/performanceTracker.d.ts +74 -0
- package/dist/esm/customHooks/performanceTracker.js +531 -391
- package/dist/esm/customHooks/reduxLogger.d.ts +1 -0
- package/dist/esm/customHooks/reduxLogger.js +1 -0
- package/dist/esm/helpers/index.js +27 -3
- package/dist/esm/i18n/locales/en.json +225 -4
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +24 -3
- package/dist/esm/styles/index.d.ts +3 -1
- package/dist/esm/styles/index.js +8 -6
- package/dist/esm/types/interfaces.d.ts +3 -0
- package/ios/NetworkInspectorModule.h +2 -0
- package/ios/NetworkInspectorModule.m +10 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
|
|
3
|
+
import Svg, { Path, Defs, LinearGradient, Stop, Line, Circle, G, } from 'react-native-svg';
|
|
3
4
|
import TouchableScale from '../TouchableScale';
|
|
4
5
|
import CopyButton from '../CopyButton';
|
|
5
6
|
import HighlightText from '../HighlightText';
|
|
@@ -8,8 +9,8 @@ import { AppColors } from '../../styles/AppColors';
|
|
|
8
9
|
import { AppFonts } from '../../styles/AppFonts';
|
|
9
10
|
import { useTranslation } from '../../i18n';
|
|
10
11
|
import { getRuntimeDiagnostics } from '../../helpers';
|
|
11
|
-
import { SearchIcon, CircleXIcon, PerformanceIcon, BoltIcon, LightbulbIcon,
|
|
12
|
-
import {
|
|
12
|
+
import { SearchIcon, CircleXIcon, PerformanceIcon, BoltIcon, LightbulbIcon, } from '../NetworkIcons';
|
|
13
|
+
import { getPerformanceEvents, subscribePerformanceEvents, logPerformanceEvent, clearPerformanceEvents, } from '../../customHooks/performanceTracker';
|
|
13
14
|
const PerformanceTab = React.memo(() => {
|
|
14
15
|
const { t } = useTranslation();
|
|
15
16
|
// ─── Real-Time FPS Tracking ────────────────────────────────────────────────
|
|
@@ -35,8 +36,8 @@ const PerformanceTab = React.memo(() => {
|
|
|
35
36
|
const [fpsHistory, setFpsHistory] = useState([
|
|
36
37
|
60, 59, 60, 60, 58, 60, 59, 60, 60, 60, 57, 60, 59, 60, 60, 58, 60, 60, 59, 60,
|
|
37
38
|
]);
|
|
38
|
-
// Performance Log Events (
|
|
39
|
-
const [events, setEvents] = useState(
|
|
39
|
+
// Performance Log Events (Dynamic live subscription)
|
|
40
|
+
const [events, setEvents] = useState(() => getPerformanceEvents());
|
|
40
41
|
const [filterCategory, setFilterCategory] = useState('ALL');
|
|
41
42
|
const [search, setSearch] = useState('');
|
|
42
43
|
useEffect(() => {
|
|
@@ -211,9 +212,88 @@ const PerformanceTab = React.memo(() => {
|
|
|
211
212
|
headroomPercent,
|
|
212
213
|
};
|
|
213
214
|
}, [avgFps, jsLagMs]);
|
|
215
|
+
// ─── Dynamic SVG FPS Area & Trend Curve Data ────────────────────────────
|
|
216
|
+
const [selectedFpsPointIdx, setSelectedFpsPointIdx] = useState(null);
|
|
217
|
+
const svgGraphWidth = 320;
|
|
218
|
+
const svgGraphHeight = 64;
|
|
219
|
+
const fpsGraphData = useMemo(() => {
|
|
220
|
+
const points = fpsHistory.length > 0 ? fpsHistory : [60, 60];
|
|
221
|
+
const stepX = svgGraphWidth / Math.max(1, points.length - 1);
|
|
222
|
+
const coords = points.map((val, idx) => {
|
|
223
|
+
const clamped = Math.max(0, Math.min(60, val));
|
|
224
|
+
const x = Number((idx * stepX).toFixed(1));
|
|
225
|
+
const y = Number((svgGraphHeight - (clamped / 60) * (svgGraphHeight - 14) - 6).toFixed(1));
|
|
226
|
+
return { x, y, val };
|
|
227
|
+
});
|
|
228
|
+
let linePath = `M ${coords[0].x} ${coords[0].y}`;
|
|
229
|
+
for (let i = 0; i < coords.length - 1; i++) {
|
|
230
|
+
const p0 = coords[i];
|
|
231
|
+
const p1 = coords[i + 1];
|
|
232
|
+
const midX = Number(((p0.x + p1.x) / 2).toFixed(1));
|
|
233
|
+
linePath += ` C ${midX} ${p0.y}, ${midX} ${p1.y}, ${p1.x} ${p1.y}`;
|
|
234
|
+
}
|
|
235
|
+
const last = coords[coords.length - 1];
|
|
236
|
+
const first = coords[0];
|
|
237
|
+
const areaPath = `${linePath} L ${last.x} ${svgGraphHeight} L ${first.x} ${svgGraphHeight} Z`;
|
|
238
|
+
return { linePath, areaPath, coords };
|
|
239
|
+
}, [fpsHistory]);
|
|
240
|
+
const latencyBuckets = useMemo(() => {
|
|
241
|
+
let optimal = 0;
|
|
242
|
+
let minorJank = 0;
|
|
243
|
+
let noticeableJank = 0;
|
|
244
|
+
let severeFreeze = 0;
|
|
245
|
+
fpsHistory.forEach(fps => {
|
|
246
|
+
if (fps >= 55)
|
|
247
|
+
optimal++;
|
|
248
|
+
else if (fps >= 30)
|
|
249
|
+
minorJank++;
|
|
250
|
+
else if (fps >= 20)
|
|
251
|
+
noticeableJank++;
|
|
252
|
+
else
|
|
253
|
+
severeFreeze++;
|
|
254
|
+
});
|
|
255
|
+
const total = Math.max(1, fpsHistory.length);
|
|
256
|
+
return [
|
|
257
|
+
{
|
|
258
|
+
key: 'optimal',
|
|
259
|
+
label: t('performance.optimalBucket'),
|
|
260
|
+
count: optimal,
|
|
261
|
+
percent: Number(((optimal / total) * 100).toFixed(0)),
|
|
262
|
+
color: AppColors.emerald500,
|
|
263
|
+
bgColor: AppColors.emerald100,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
key: 'minorJank',
|
|
267
|
+
label: t('performance.minorJankBucket'),
|
|
268
|
+
count: minorJank,
|
|
269
|
+
percent: Number(((minorJank / total) * 100).toFixed(0)),
|
|
270
|
+
color: AppColors.amber500,
|
|
271
|
+
bgColor: AppColors.amber100,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
key: 'noticeableJank',
|
|
275
|
+
label: t('performance.noticeableJankBucket'),
|
|
276
|
+
count: noticeableJank,
|
|
277
|
+
percent: Number(((noticeableJank / total) * 100).toFixed(0)),
|
|
278
|
+
color: AppColors.firebaseOrange,
|
|
279
|
+
bgColor: AppColors.lightOrange,
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
key: 'severeFreeze',
|
|
283
|
+
label: t('performance.severeFreezeBucket'),
|
|
284
|
+
count: severeFreeze,
|
|
285
|
+
percent: Number(((severeFreeze / total) * 100).toFixed(0)),
|
|
286
|
+
color: AppColors.errorColor,
|
|
287
|
+
bgColor: AppColors.errorCardBg,
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
}, [fpsHistory, t]);
|
|
214
291
|
const clearEvents = () => {
|
|
215
292
|
clearPerformanceEvents();
|
|
216
293
|
};
|
|
294
|
+
const activeScrubPoint = selectedFpsPointIdx !== null && fpsGraphData.coords[selectedFpsPointIdx]
|
|
295
|
+
? fpsGraphData.coords[selectedFpsPointIdx]
|
|
296
|
+
: null;
|
|
217
297
|
return (<View style={perfStyles.container}>
|
|
218
298
|
{/* Search & Live Control Header */}
|
|
219
299
|
<View style={perfStyles.headerBar}>
|
|
@@ -225,99 +305,81 @@ const PerformanceTab = React.memo(() => {
|
|
|
225
305
|
</TouchableScale>) : null}
|
|
226
306
|
</View>
|
|
227
307
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
perfStyles.
|
|
231
|
-
isRecording
|
|
308
|
+
{/* Live FPS Telemetry Recorder Toggle */}
|
|
309
|
+
<TouchableScale onPress={() => setIsRecording(prev => !prev)} style={[
|
|
310
|
+
perfStyles.recordToggleBtn,
|
|
311
|
+
isRecording
|
|
312
|
+
? perfStyles.recordToggleActive
|
|
313
|
+
: perfStyles.recordToggleInactive,
|
|
232
314
|
]}>
|
|
233
|
-
|
|
234
|
-
perfStyles.
|
|
235
|
-
{
|
|
315
|
+
<View style={[
|
|
316
|
+
perfStyles.recordingDot,
|
|
317
|
+
{
|
|
318
|
+
backgroundColor: isRecording
|
|
319
|
+
? AppColors.errorColor
|
|
320
|
+
: AppColors.grayTextWeak,
|
|
321
|
+
},
|
|
236
322
|
]}/>
|
|
237
|
-
|
|
238
|
-
perfStyles.
|
|
239
|
-
{
|
|
323
|
+
<Text style={[
|
|
324
|
+
perfStyles.recordToggleText,
|
|
325
|
+
{
|
|
326
|
+
color: isRecording
|
|
327
|
+
? AppColors.errorColor
|
|
328
|
+
: AppColors.grayTextWeak,
|
|
329
|
+
},
|
|
240
330
|
]}>
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
currentFps,
|
|
247
|
-
minFps,
|
|
248
|
-
maxFps,
|
|
249
|
-
avgFps,
|
|
250
|
-
jankyFrameCount,
|
|
251
|
-
jsLagMs,
|
|
252
|
-
platform: Platform.OS,
|
|
253
|
-
engine: runtimeDiagnostics.jsEngineName,
|
|
254
|
-
totalEvents: events.length,
|
|
255
|
-
events,
|
|
256
|
-
})} label={t('common.copyJson')}/>
|
|
257
|
-
</View>
|
|
331
|
+
{isRecording
|
|
332
|
+
? t('performance.recording')
|
|
333
|
+
: t('performance.paused')}
|
|
334
|
+
</Text>
|
|
335
|
+
</TouchableScale>
|
|
258
336
|
</View>
|
|
259
337
|
|
|
260
|
-
{/*
|
|
261
|
-
<View style={perfStyles.
|
|
262
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={perfStyles.
|
|
338
|
+
{/* Filter Category Tabs Carousel */}
|
|
339
|
+
<View style={perfStyles.categoryScrollWrapper}>
|
|
340
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={perfStyles.categoryScrollView} contentContainerStyle={perfStyles.categoryScroll}>
|
|
263
341
|
{[
|
|
264
|
-
{ key: 'ALL', label: t('performance.
|
|
265
|
-
{
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
count: events.filter(e => e.category === 'navigation').length,
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
key: 'RENDER',
|
|
279
|
-
label: t('performance.components'),
|
|
280
|
-
Icon: AtomIcon,
|
|
281
|
-
count: events.filter(e => e.category === 'render').length,
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
key: 'MEMORY',
|
|
285
|
-
label: t('performance.memoryGc'),
|
|
286
|
-
Icon: BrainIcon,
|
|
287
|
-
count: events.filter(e => e.category === 'memory').length,
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
key: 'IO',
|
|
291
|
-
label: t('performance.networkIo'),
|
|
292
|
-
Icon: GlobeIcon,
|
|
293
|
-
count: events.filter(e => e.category === 'io' || e.category === 'bridge').length,
|
|
294
|
-
},
|
|
295
|
-
].map(tab => {
|
|
296
|
-
const isActive = filterCategory === tab.key;
|
|
297
|
-
return (<TouchableScale key={tab.key} onPress={() => setFilterCategory(tab.key)} style={[
|
|
298
|
-
perfStyles.filterPill,
|
|
299
|
-
isActive && perfStyles.filterPillActive,
|
|
342
|
+
{ key: 'ALL', label: t('performance.catAll') },
|
|
343
|
+
{ key: 'JANKY', label: t('performance.catJanky') },
|
|
344
|
+
{ key: 'NAVIGATION', label: t('performance.catNavigation') },
|
|
345
|
+
{ key: 'RENDER', label: t('performance.catRender') },
|
|
346
|
+
{ key: 'MEMORY', label: t('performance.catMemory') },
|
|
347
|
+
{ key: 'IO', label: t('performance.catIo') },
|
|
348
|
+
].map(cat => {
|
|
349
|
+
const isSelected = filterCategory === cat.key;
|
|
350
|
+
return (<TouchableScale key={cat.key} onPress={() => setFilterCategory(cat.key)} style={[
|
|
351
|
+
perfStyles.categoryChip,
|
|
352
|
+
isSelected && perfStyles.categoryChipActive,
|
|
300
353
|
]}>
|
|
301
|
-
<tab.Icon color={isActive ? AppColors.white : AppColors.grayTextStrong} size={12}/>
|
|
302
354
|
<Text style={[
|
|
303
|
-
perfStyles.
|
|
304
|
-
|
|
355
|
+
perfStyles.categoryChipText,
|
|
356
|
+
isSelected && perfStyles.categoryChipTextActive,
|
|
305
357
|
]}>
|
|
306
|
-
{
|
|
358
|
+
{cat.label}
|
|
307
359
|
</Text>
|
|
308
360
|
</TouchableScale>);
|
|
309
361
|
})}
|
|
310
362
|
</ScrollView>
|
|
311
363
|
</View>
|
|
312
364
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
365
|
+
{/* Main Content Scroll View */}
|
|
366
|
+
<ScrollView style={perfStyles.scrollBody} contentContainerStyle={perfStyles.scrollContent} showsVerticalScrollIndicator={false}>
|
|
367
|
+
{/* ── 1. REAL-TIME FPS SCORECARD & SMOOTH AREA GRAPH ── */}
|
|
368
|
+
<View style={perfStyles.heroCard}>
|
|
369
|
+
<View style={perfStyles.heroHeaderRow}>
|
|
317
370
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
318
|
-
<PerformanceIcon color={AppColors.purple} size={
|
|
319
|
-
<
|
|
371
|
+
<PerformanceIcon color={AppColors.purple} size={17}/>
|
|
372
|
+
<View>
|
|
373
|
+
<Text style={perfStyles.heroTitle}>
|
|
374
|
+
{t('performance.fpsAreaChartTitle')}
|
|
375
|
+
</Text>
|
|
376
|
+
<Text style={perfStyles.heroSubTitle}>
|
|
377
|
+
{t('performance.fpsAreaChartSub')}
|
|
378
|
+
</Text>
|
|
379
|
+
</View>
|
|
320
380
|
</View>
|
|
381
|
+
|
|
382
|
+
{/* Health Grade Score Pill */}
|
|
321
383
|
<View style={[
|
|
322
384
|
perfStyles.scorePill,
|
|
323
385
|
{ backgroundColor: performanceScore.bg },
|
|
@@ -351,55 +413,169 @@ const PerformanceTab = React.memo(() => {
|
|
|
351
413
|
|
|
352
414
|
<View style={perfStyles.fpsStatsGrid}>
|
|
353
415
|
<View style={perfStyles.statItem}>
|
|
354
|
-
<Text style={perfStyles.statLabel}>
|
|
416
|
+
<Text style={perfStyles.statLabel}>
|
|
417
|
+
{t('performance.avgFrameTime')}
|
|
418
|
+
</Text>
|
|
355
419
|
<Text style={perfStyles.statValue}>{avgFps}</Text>
|
|
356
420
|
</View>
|
|
357
421
|
<View style={perfStyles.statItem}>
|
|
358
|
-
<Text style={perfStyles.statLabel}>
|
|
359
|
-
|
|
422
|
+
<Text style={perfStyles.statLabel}>
|
|
423
|
+
{t('performance.peakTime')}
|
|
424
|
+
</Text>
|
|
425
|
+
<Text style={[
|
|
426
|
+
perfStyles.statValue,
|
|
427
|
+
minFps < 50 && { color: AppColors.firebaseOrange },
|
|
428
|
+
]}>
|
|
360
429
|
{minFps}
|
|
361
430
|
</Text>
|
|
362
431
|
</View>
|
|
363
432
|
<View style={perfStyles.statItem}>
|
|
364
|
-
<Text style={perfStyles.statLabel}>
|
|
365
|
-
|
|
433
|
+
<Text style={perfStyles.statLabel}>
|
|
434
|
+
{t('performance.jsLag')}
|
|
435
|
+
</Text>
|
|
436
|
+
<Text style={[
|
|
437
|
+
perfStyles.statValue,
|
|
438
|
+
jsLagMs > 5 && { color: AppColors.firebaseOrange },
|
|
439
|
+
]}>
|
|
366
440
|
{jsLagMs}ms
|
|
367
441
|
</Text>
|
|
368
442
|
</View>
|
|
369
443
|
<View style={perfStyles.statItem}>
|
|
370
|
-
<Text style={perfStyles.statLabel}>
|
|
444
|
+
<Text style={perfStyles.statLabel}>
|
|
445
|
+
{t('performance.jankRate')}
|
|
446
|
+
</Text>
|
|
371
447
|
<Text style={perfStyles.statValue}>
|
|
372
448
|
{totalFrames > 0
|
|
373
|
-
? `${((jankyFrameCount / Math.max(1, totalFrames / 60)) *
|
|
449
|
+
? `${((jankyFrameCount / Math.max(1, totalFrames / 60)) *
|
|
450
|
+
100).toFixed(0)}%`
|
|
374
451
|
: '0%'}
|
|
375
452
|
</Text>
|
|
376
453
|
</View>
|
|
377
454
|
</View>
|
|
378
455
|
</View>
|
|
379
456
|
|
|
380
|
-
{/*
|
|
381
|
-
<View style={perfStyles.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
457
|
+
{/* Interactive Scrub Tooltip */}
|
|
458
|
+
{activeScrubPoint && (<View style={perfStyles.graphScrubTooltip}>
|
|
459
|
+
<Text style={perfStyles.graphScrubTooltipText}>
|
|
460
|
+
{activeScrubPoint.val} FPS •{' '}
|
|
461
|
+
{Number((1000 / Math.max(1, activeScrubPoint.val)).toFixed(1))}
|
|
462
|
+
ms
|
|
463
|
+
</Text>
|
|
464
|
+
</View>)}
|
|
465
|
+
|
|
466
|
+
{/* Real-Time SVG Smooth Area & Trend Wave Graph */}
|
|
467
|
+
<View style={perfStyles.svgGraphContainer}>
|
|
468
|
+
<Svg width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`}>
|
|
469
|
+
<Defs>
|
|
470
|
+
<LinearGradient id="fpsAreaGradient" x1="0" y1="0" x2="0" y2="1">
|
|
471
|
+
<Stop offset="0%" stopColor={avgFps >= 55
|
|
472
|
+
? AppColors.emerald500
|
|
473
|
+
: avgFps >= 40
|
|
474
|
+
? AppColors.amber500
|
|
475
|
+
: AppColors.errorColor} stopOpacity="0.32"/>
|
|
476
|
+
<Stop offset="100%" stopColor={avgFps >= 55
|
|
477
|
+
? AppColors.emerald500
|
|
478
|
+
: avgFps >= 40
|
|
479
|
+
? AppColors.amber500
|
|
480
|
+
: AppColors.errorColor} stopOpacity="0.02"/>
|
|
481
|
+
</LinearGradient>
|
|
482
|
+
</Defs>
|
|
483
|
+
|
|
484
|
+
{/* 60 FPS Target Baseline */}
|
|
485
|
+
<Line x1="0" y1="7" x2={svgGraphWidth} y2="7" stroke={AppColors.emerald500} strokeWidth="1" strokeDasharray="4,4" opacity="0.4"/>
|
|
486
|
+
|
|
487
|
+
{/* 30 FPS Warning Baseline */}
|
|
488
|
+
<Line x1="0" y1={svgGraphHeight / 2} x2={svgGraphWidth} y2={svgGraphHeight / 2} stroke={AppColors.amber500} strokeWidth="0.8" strokeDasharray="3,3" opacity="0.3"/>
|
|
489
|
+
|
|
490
|
+
{/* Shaded Area Under Curve */}
|
|
491
|
+
{fpsGraphData.areaPath ? (<Path d={fpsGraphData.areaPath} fill="url(#fpsAreaGradient)"/>) : null}
|
|
492
|
+
|
|
493
|
+
{/* Bezier Trend Line */}
|
|
494
|
+
{fpsGraphData.linePath ? (<Path d={fpsGraphData.linePath} fill="none" stroke={avgFps >= 55
|
|
495
|
+
? AppColors.emerald600
|
|
496
|
+
: avgFps >= 40
|
|
497
|
+
? AppColors.amber600
|
|
498
|
+
: AppColors.errorColor} strokeWidth="2.2"/>) : null}
|
|
499
|
+
|
|
500
|
+
{/* Sample Nodes */}
|
|
501
|
+
{fpsGraphData.coords.map((c, idx) => {
|
|
502
|
+
const isSelected = selectedFpsPointIdx === idx;
|
|
503
|
+
const isDip = c.val < 50;
|
|
504
|
+
return (<G key={idx}>
|
|
505
|
+
{isDip && (<Circle cx={c.x} cy={c.y} r="4" fill={AppColors.errorColor} opacity="0.25"/>)}
|
|
506
|
+
<Circle cx={c.x} cy={c.y} r={isSelected ? 4 : isDip ? 2.5 : 1.8} fill={isSelected
|
|
507
|
+
? AppColors.brandPurple
|
|
508
|
+
: isDip
|
|
509
|
+
? AppColors.errorColor
|
|
510
|
+
: AppColors.emerald600} stroke={AppColors.white} strokeWidth={isSelected ? 1.5 : 0.8}/>
|
|
511
|
+
</G>);
|
|
394
512
|
})}
|
|
513
|
+
</Svg>
|
|
514
|
+
|
|
515
|
+
{/* Invisible Touch Column Targets for Interactive Inspection */}
|
|
516
|
+
<View style={perfStyles.svgTouchOverlay}>
|
|
517
|
+
{fpsGraphData.coords.map((_, idx) => (<TouchableOpacity key={idx} style={perfStyles.svgTouchCol} activeOpacity={0.7} onPress={() => setSelectedFpsPointIdx(selectedFpsPointIdx === idx ? null : idx)}/>))}
|
|
518
|
+
</View>
|
|
395
519
|
</View>
|
|
520
|
+
|
|
396
521
|
<View style={perfStyles.sparklineFooter}>
|
|
397
|
-
<Text style={perfStyles.sparklineFooterText}>
|
|
398
|
-
|
|
522
|
+
<Text style={perfStyles.sparklineFooterText}>
|
|
523
|
+
{t('performance.realtimeWindow')}
|
|
524
|
+
</Text>
|
|
525
|
+
<Text style={perfStyles.sparklineFooterTarget}>
|
|
526
|
+
{t('performance.target60Fps', { fps: avgFps })}
|
|
527
|
+
</Text>
|
|
399
528
|
</View>
|
|
400
529
|
</View>
|
|
401
530
|
|
|
402
|
-
{/* ── 2. FRAME
|
|
531
|
+
{/* ── 2. FRAME LATENCY DISTRIBUTION HISTOGRAM CARD ── */}
|
|
532
|
+
<View style={perfStyles.budgetCard}>
|
|
533
|
+
<View style={perfStyles.budgetHeader}>
|
|
534
|
+
<View>
|
|
535
|
+
<Text style={perfStyles.budgetTitle}>
|
|
536
|
+
{t('performance.frameLatencyDistTitle')}
|
|
537
|
+
</Text>
|
|
538
|
+
<Text style={perfStyles.heroSubTitle}>
|
|
539
|
+
{t('performance.frameLatencyDistSub')}
|
|
540
|
+
</Text>
|
|
541
|
+
</View>
|
|
542
|
+
</View>
|
|
543
|
+
|
|
544
|
+
<View style={perfStyles.histogramContainer}>
|
|
545
|
+
{latencyBuckets.map(b => (<View key={b.key} style={perfStyles.histogramRow}>
|
|
546
|
+
<View style={perfStyles.histogramLabelCol}>
|
|
547
|
+
<View style={[perfStyles.legendDot, { backgroundColor: b.color }]}/>
|
|
548
|
+
<Text style={perfStyles.histogramLabelText}>{b.label}</Text>
|
|
549
|
+
</View>
|
|
550
|
+
|
|
551
|
+
<View style={perfStyles.histogramBarCol}>
|
|
552
|
+
<View style={perfStyles.histogramTrack}>
|
|
553
|
+
<View style={[
|
|
554
|
+
perfStyles.histogramBarFill,
|
|
555
|
+
{
|
|
556
|
+
width: `${Math.max(b.percent > 0 ? 4 : 0, b.percent)}%`,
|
|
557
|
+
backgroundColor: b.color,
|
|
558
|
+
},
|
|
559
|
+
]}/>
|
|
560
|
+
</View>
|
|
561
|
+
</View>
|
|
562
|
+
|
|
563
|
+
<View style={perfStyles.histogramStatsCol}>
|
|
564
|
+
<Text style={[
|
|
565
|
+
perfStyles.histogramPercentText,
|
|
566
|
+
{ color: b.color },
|
|
567
|
+
]}>
|
|
568
|
+
{b.percent}%
|
|
569
|
+
</Text>
|
|
570
|
+
<Text style={perfStyles.histogramCountText}>
|
|
571
|
+
({b.count})
|
|
572
|
+
</Text>
|
|
573
|
+
</View>
|
|
574
|
+
</View>))}
|
|
575
|
+
</View>
|
|
576
|
+
</View>
|
|
577
|
+
|
|
578
|
+
{/* ── 3. FRAME BUDGET BREAKDOWN CARD ── */}
|
|
403
579
|
<View style={perfStyles.budgetCard}>
|
|
404
580
|
<View style={perfStyles.budgetHeader}>
|
|
405
581
|
<Text style={perfStyles.budgetTitle}>{t('performance.budgetUsage')}</Text>
|
|
@@ -509,20 +685,45 @@ const PerformanceTab = React.memo(() => {
|
|
|
509
685
|
isCritical && { borderColor: AppColors.errorBorder, backgroundColor: AppColors.errorCardBg },
|
|
510
686
|
]}>
|
|
511
687
|
<View style={perfStyles.eventTopRow}>
|
|
512
|
-
<View style={
|
|
688
|
+
<View style={perfStyles.fpsBadgeGroup}>
|
|
689
|
+
<View style={[
|
|
513
690
|
perfStyles.fpsBadge,
|
|
514
691
|
{ backgroundColor: badgeBg, borderColor: badgeBorder },
|
|
515
692
|
]}>
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
693
|
+
<Text style={[perfStyles.fpsBadgeText, { color: badgeColor }]}>
|
|
694
|
+
{event.fps} FPS
|
|
695
|
+
</Text>
|
|
696
|
+
</View>
|
|
520
697
|
|
|
521
|
-
|
|
522
|
-
<
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
698
|
+
{/* Dropped Frames Badge */}
|
|
699
|
+
<View style={[
|
|
700
|
+
perfStyles.droppedBadge,
|
|
701
|
+
(event.droppedFrames ?? Math.max(0, 60 - event.fps)) > 0
|
|
702
|
+
? perfStyles.droppedBadgeWarning
|
|
703
|
+
: perfStyles.droppedBadgeOptimal,
|
|
704
|
+
]}>
|
|
705
|
+
<Text style={[
|
|
706
|
+
perfStyles.droppedBadgeText,
|
|
707
|
+
(event.droppedFrames ?? Math.max(0, 60 - event.fps)) > 0
|
|
708
|
+
? { color: AppColors.red600 }
|
|
709
|
+
: { color: AppColors.emerald600 },
|
|
710
|
+
]}>
|
|
711
|
+
{(event.droppedFrames ?? Math.max(0, 60 - event.fps)) > 0
|
|
712
|
+
? t('performance.droppedFrames', { count: event.droppedFrames ?? Math.max(0, 60 - event.fps) })
|
|
713
|
+
: t('performance.zeroDropped')}
|
|
714
|
+
</Text>
|
|
715
|
+
</View>
|
|
716
|
+
|
|
717
|
+
{/* Bottleneck Thread Tag */}
|
|
718
|
+
{event.bottleneckThread && (<View style={perfStyles.bottleneckTag}>
|
|
719
|
+
<Text style={perfStyles.bottleneckTagText}>
|
|
720
|
+
{event.bottleneckThread === 'JS Thread'
|
|
721
|
+
? `⚡ ${t('performance.jsBound')}`
|
|
722
|
+
: event.bottleneckThread === 'UI Thread'
|
|
723
|
+
? `🎨 ${t('performance.uiBound')}`
|
|
724
|
+
: t('performance.balanced')}
|
|
725
|
+
</Text>
|
|
726
|
+
</View>)}
|
|
526
727
|
</View>
|
|
527
728
|
|
|
528
729
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
@@ -533,18 +734,85 @@ const PerformanceTab = React.memo(() => {
|
|
|
533
734
|
</View>
|
|
534
735
|
</View>
|
|
535
736
|
|
|
737
|
+
{/* Event Label & Meta */}
|
|
738
|
+
<View style={{ marginTop: 4 }}>
|
|
739
|
+
<HighlightText text={event.label} search={search} style={perfStyles.eventLabel} highlightStyle={perfStyles.searchHighlight}/>
|
|
740
|
+
<View style={perfStyles.eventMetaRow}>
|
|
741
|
+
<Text style={perfStyles.eventMeta}>
|
|
742
|
+
{t('performance.msTotal', {
|
|
743
|
+
duration: event.durationMs,
|
|
744
|
+
time: new Date(event.timestamp).toLocaleTimeString(),
|
|
745
|
+
})}
|
|
746
|
+
</Text>
|
|
747
|
+
<Text style={perfStyles.eventMetaDot}>•</Text>
|
|
748
|
+
<Text style={[
|
|
749
|
+
perfStyles.frameBudgetHint,
|
|
750
|
+
event.durationMs > 16.67
|
|
751
|
+
? { color: AppColors.amber800Warm }
|
|
752
|
+
: { color: AppColors.emerald600 },
|
|
753
|
+
]}>
|
|
754
|
+
{t('performance.frameBudgetMs', {
|
|
755
|
+
time: event.durationMs,
|
|
756
|
+
budget: Number(((event.durationMs / 16.67) * 100).toFixed(0)),
|
|
757
|
+
})}
|
|
758
|
+
</Text>
|
|
759
|
+
</View>
|
|
760
|
+
</View>
|
|
761
|
+
|
|
762
|
+
{/* Visual Frame Time Budget Bar */}
|
|
763
|
+
<View style={perfStyles.frameBarTrack}>
|
|
764
|
+
<View style={[
|
|
765
|
+
perfStyles.frameBarFill,
|
|
766
|
+
{
|
|
767
|
+
width: `${Math.min(100, Math.max(10, (event.durationMs / 33.33) * 100))}%`,
|
|
768
|
+
backgroundColor: badgeColor,
|
|
769
|
+
},
|
|
770
|
+
]}/>
|
|
771
|
+
</View>
|
|
772
|
+
|
|
536
773
|
<HighlightText text={event.detail} search={search} style={perfStyles.eventDetail} highlightStyle={perfStyles.searchHighlight}/>
|
|
537
774
|
|
|
538
775
|
{/* ── EXPANDABLE IN-DEPTH METRICS ACCORDION ── */}
|
|
539
776
|
{isExpanded && (<View style={perfStyles.expandedDetailBox}>
|
|
540
|
-
{/*
|
|
541
|
-
{event.source && (<View style={perfStyles.sourceRow}>
|
|
542
|
-
<Text style={perfStyles.sourceTag}>{t('
|
|
777
|
+
{/* Screen / Component Context */}
|
|
778
|
+
{(event.screenName || event.source) && (<View style={perfStyles.sourceRow}>
|
|
779
|
+
<Text style={perfStyles.sourceTag}>{t('performance.screenContext')}</Text>
|
|
543
780
|
<Text style={perfStyles.sourceText} numberOfLines={1}>
|
|
544
|
-
{event.source}
|
|
781
|
+
{event.screenName || event.source}
|
|
545
782
|
</Text>
|
|
546
783
|
</View>)}
|
|
547
784
|
|
|
785
|
+
{/* Frame Metrics Grid */}
|
|
786
|
+
<View style={perfStyles.fpsMetricsGrid}>
|
|
787
|
+
<View style={perfStyles.fpsMetricItem}>
|
|
788
|
+
<Text style={perfStyles.fpsMetricLabel}>{t('performance.target60Fps', { fps: event.fps })}</Text>
|
|
789
|
+
<Text style={[perfStyles.fpsMetricVal, { color: badgeColor }]}>
|
|
790
|
+
{event.fps} FPS
|
|
791
|
+
</Text>
|
|
792
|
+
</View>
|
|
793
|
+
<View style={perfStyles.fpsMetricItem}>
|
|
794
|
+
<Text style={perfStyles.fpsMetricLabel}>{t('performance.frameDuration')}</Text>
|
|
795
|
+
<Text style={perfStyles.fpsMetricVal}>
|
|
796
|
+
{event.durationMs} ms
|
|
797
|
+
</Text>
|
|
798
|
+
</View>
|
|
799
|
+
<View style={perfStyles.fpsMetricItem}>
|
|
800
|
+
<Text style={perfStyles.fpsMetricLabel}>{t('performance.jankRate')}</Text>
|
|
801
|
+
<Text style={[
|
|
802
|
+
perfStyles.fpsMetricVal,
|
|
803
|
+
(event.droppedFrames ?? 0) > 0 ? { color: AppColors.red600 } : { color: AppColors.emerald600 },
|
|
804
|
+
]}>
|
|
805
|
+
{event.droppedFrames ?? Math.max(0, 60 - event.fps)} frames
|
|
806
|
+
</Text>
|
|
807
|
+
</View>
|
|
808
|
+
<View style={perfStyles.fpsMetricItem}>
|
|
809
|
+
<Text style={perfStyles.fpsMetricLabel}>{t('performance.bottleneck')}</Text>
|
|
810
|
+
<Text style={perfStyles.fpsMetricVal}>
|
|
811
|
+
{event.bottleneckThread || t('performance.balanced')}
|
|
812
|
+
</Text>
|
|
813
|
+
</View>
|
|
814
|
+
</View>
|
|
815
|
+
|
|
548
816
|
{/* Thread Timing Breakdown */}
|
|
549
817
|
{event.breakdown && (<View style={perfStyles.breakdownGrid}>
|
|
550
818
|
<View style={perfStyles.breakdownCol}>
|
|
@@ -626,7 +894,7 @@ const perfStyles = StyleSheet.create({
|
|
|
626
894
|
color: AppColors.primaryBlack,
|
|
627
895
|
paddingVertical: 0,
|
|
628
896
|
},
|
|
629
|
-
|
|
897
|
+
recordToggleBtn: {
|
|
630
898
|
flexDirection: 'row',
|
|
631
899
|
alignItems: 'center',
|
|
632
900
|
gap: 5,
|
|
@@ -635,63 +903,72 @@ const perfStyles = StyleSheet.create({
|
|
|
635
903
|
borderRadius: 7,
|
|
636
904
|
borderWidth: 1,
|
|
637
905
|
},
|
|
638
|
-
|
|
639
|
-
backgroundColor: AppColors.
|
|
640
|
-
borderColor: AppColors.
|
|
906
|
+
recordToggleActive: {
|
|
907
|
+
backgroundColor: `${AppColors.errorColor}14`,
|
|
908
|
+
borderColor: `${AppColors.errorColor}30`,
|
|
641
909
|
},
|
|
642
|
-
|
|
643
|
-
backgroundColor: AppColors.
|
|
644
|
-
borderColor: AppColors.
|
|
910
|
+
recordToggleInactive: {
|
|
911
|
+
backgroundColor: AppColors.graySurface,
|
|
912
|
+
borderColor: AppColors.dividerColor,
|
|
645
913
|
},
|
|
646
|
-
|
|
914
|
+
recordingDot: {
|
|
647
915
|
width: 6,
|
|
648
916
|
height: 6,
|
|
649
917
|
borderRadius: 3,
|
|
650
918
|
},
|
|
651
|
-
|
|
919
|
+
recordToggleText: {
|
|
652
920
|
fontFamily: AppFonts.interBold,
|
|
653
921
|
fontSize: 11,
|
|
654
922
|
},
|
|
655
|
-
|
|
923
|
+
categoryScrollWrapper: {
|
|
656
924
|
backgroundColor: AppColors.white,
|
|
657
925
|
borderBottomWidth: 1,
|
|
658
926
|
borderBottomColor: AppColors.dividerColor,
|
|
659
|
-
|
|
927
|
+
height: 42,
|
|
928
|
+
},
|
|
929
|
+
categoryScrollView: {
|
|
930
|
+
flexGrow: 0,
|
|
931
|
+
height: 42,
|
|
660
932
|
},
|
|
661
|
-
|
|
933
|
+
categoryScroll: {
|
|
934
|
+
flexDirection: 'row',
|
|
935
|
+
alignItems: 'center',
|
|
662
936
|
gap: 6,
|
|
663
937
|
paddingHorizontal: 12,
|
|
664
|
-
|
|
938
|
+
height: 42,
|
|
665
939
|
},
|
|
666
|
-
|
|
940
|
+
categoryChip: {
|
|
667
941
|
flexDirection: 'row',
|
|
668
942
|
alignItems: 'center',
|
|
669
|
-
|
|
670
|
-
paddingHorizontal:
|
|
671
|
-
|
|
672
|
-
borderRadius:
|
|
943
|
+
justifyContent: 'center',
|
|
944
|
+
paddingHorizontal: 12,
|
|
945
|
+
height: 28,
|
|
946
|
+
borderRadius: 14,
|
|
673
947
|
backgroundColor: AppColors.grayBackground,
|
|
674
948
|
borderWidth: 1,
|
|
675
949
|
borderColor: AppColors.dividerColor,
|
|
676
950
|
},
|
|
677
|
-
|
|
951
|
+
categoryChipActive: {
|
|
678
952
|
backgroundColor: AppColors.brandPurple,
|
|
679
953
|
borderColor: AppColors.brandPurple,
|
|
680
954
|
},
|
|
681
|
-
|
|
955
|
+
categoryChipText: {
|
|
682
956
|
fontFamily: AppFonts.interMedium,
|
|
683
957
|
fontSize: 11,
|
|
684
958
|
color: AppColors.grayTextStrong,
|
|
685
959
|
},
|
|
686
|
-
|
|
960
|
+
categoryChipTextActive: {
|
|
687
961
|
fontFamily: AppFonts.interBold,
|
|
688
962
|
color: AppColors.white,
|
|
689
963
|
},
|
|
964
|
+
scrollBody: {
|
|
965
|
+
flex: 1,
|
|
966
|
+
},
|
|
690
967
|
scrollContent: {
|
|
691
968
|
padding: 12,
|
|
692
969
|
gap: 10,
|
|
693
970
|
},
|
|
694
|
-
|
|
971
|
+
heroCard: {
|
|
695
972
|
backgroundColor: AppColors.white,
|
|
696
973
|
borderRadius: 12,
|
|
697
974
|
padding: 14,
|
|
@@ -703,17 +980,23 @@ const perfStyles = StyleSheet.create({
|
|
|
703
980
|
shadowRadius: 3,
|
|
704
981
|
elevation: 1,
|
|
705
982
|
},
|
|
706
|
-
|
|
983
|
+
heroHeaderRow: {
|
|
707
984
|
flexDirection: 'row',
|
|
708
985
|
alignItems: 'center',
|
|
709
986
|
justifyContent: 'space-between',
|
|
710
987
|
marginBottom: 10,
|
|
711
988
|
},
|
|
712
|
-
|
|
989
|
+
heroTitle: {
|
|
713
990
|
fontFamily: AppFonts.interBold,
|
|
714
|
-
fontSize: 13
|
|
991
|
+
fontSize: 13,
|
|
715
992
|
color: AppColors.primaryBlack,
|
|
716
993
|
},
|
|
994
|
+
heroSubTitle: {
|
|
995
|
+
fontFamily: AppFonts.interRegular,
|
|
996
|
+
fontSize: 9.5,
|
|
997
|
+
color: AppColors.grayTextWeak,
|
|
998
|
+
marginTop: 1,
|
|
999
|
+
},
|
|
717
1000
|
scorePill: {
|
|
718
1001
|
paddingHorizontal: 8,
|
|
719
1002
|
paddingVertical: 3,
|
|
@@ -768,26 +1051,85 @@ const perfStyles = StyleSheet.create({
|
|
|
768
1051
|
marginTop: 2,
|
|
769
1052
|
textAlign: 'center',
|
|
770
1053
|
},
|
|
771
|
-
|
|
1054
|
+
graphScrubTooltip: {
|
|
1055
|
+
alignSelf: 'center',
|
|
1056
|
+
backgroundColor: AppColors.primaryBlack,
|
|
1057
|
+
paddingHorizontal: 8,
|
|
1058
|
+
paddingVertical: 3,
|
|
1059
|
+
borderRadius: 6,
|
|
1060
|
+
marginBottom: 4,
|
|
1061
|
+
},
|
|
1062
|
+
graphScrubTooltipText: {
|
|
1063
|
+
fontFamily: AppFonts.interBold,
|
|
1064
|
+
fontSize: 10,
|
|
1065
|
+
color: AppColors.white,
|
|
1066
|
+
},
|
|
1067
|
+
svgGraphContainer: {
|
|
1068
|
+
width: '100%',
|
|
1069
|
+
height: 64,
|
|
1070
|
+
marginTop: 6,
|
|
1071
|
+
position: 'relative',
|
|
1072
|
+
},
|
|
1073
|
+
svgTouchOverlay: {
|
|
1074
|
+
position: 'absolute',
|
|
1075
|
+
top: 0,
|
|
1076
|
+
left: 0,
|
|
1077
|
+
right: 0,
|
|
1078
|
+
bottom: 0,
|
|
772
1079
|
flexDirection: 'row',
|
|
773
|
-
alignItems: 'flex-end',
|
|
774
|
-
justifyContent: 'space-between',
|
|
775
|
-
height: 38,
|
|
776
|
-
marginTop: 10,
|
|
777
|
-
paddingTop: 4,
|
|
778
|
-
borderBottomWidth: 1,
|
|
779
|
-
borderBottomColor: AppColors.dividerColor,
|
|
780
1080
|
},
|
|
781
|
-
|
|
1081
|
+
svgTouchCol: {
|
|
782
1082
|
flex: 1,
|
|
1083
|
+
height: '100%',
|
|
1084
|
+
},
|
|
1085
|
+
histogramContainer: {
|
|
1086
|
+
gap: 8,
|
|
1087
|
+
marginTop: 4,
|
|
1088
|
+
},
|
|
1089
|
+
histogramRow: {
|
|
1090
|
+
flexDirection: 'row',
|
|
783
1091
|
alignItems: 'center',
|
|
784
|
-
|
|
1092
|
+
gap: 8,
|
|
1093
|
+
},
|
|
1094
|
+
histogramLabelCol: {
|
|
1095
|
+
flexDirection: 'row',
|
|
1096
|
+
alignItems: 'center',
|
|
1097
|
+
gap: 6,
|
|
1098
|
+
width: 140,
|
|
1099
|
+
},
|
|
1100
|
+
histogramLabelText: {
|
|
1101
|
+
fontFamily: AppFonts.interMedium,
|
|
1102
|
+
fontSize: 10,
|
|
1103
|
+
color: AppColors.grayTextStrong,
|
|
1104
|
+
},
|
|
1105
|
+
histogramBarCol: {
|
|
1106
|
+
flex: 1,
|
|
1107
|
+
},
|
|
1108
|
+
histogramTrack: {
|
|
1109
|
+
height: 8,
|
|
1110
|
+
backgroundColor: AppColors.grayBackground,
|
|
1111
|
+
borderRadius: 4,
|
|
1112
|
+
overflow: 'hidden',
|
|
1113
|
+
},
|
|
1114
|
+
histogramBarFill: {
|
|
785
1115
|
height: '100%',
|
|
786
|
-
|
|
1116
|
+
borderRadius: 4,
|
|
787
1117
|
},
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1118
|
+
histogramStatsCol: {
|
|
1119
|
+
flexDirection: 'row',
|
|
1120
|
+
alignItems: 'center',
|
|
1121
|
+
gap: 4,
|
|
1122
|
+
minWidth: 52,
|
|
1123
|
+
justifyContent: 'flex-end',
|
|
1124
|
+
},
|
|
1125
|
+
histogramPercentText: {
|
|
1126
|
+
fontFamily: AppFonts.interBold,
|
|
1127
|
+
fontSize: 10.5,
|
|
1128
|
+
},
|
|
1129
|
+
histogramCountText: {
|
|
1130
|
+
fontFamily: AppFonts.interRegular,
|
|
1131
|
+
fontSize: 9.5,
|
|
1132
|
+
color: AppColors.grayTextWeak,
|
|
791
1133
|
},
|
|
792
1134
|
sparklineFooter: {
|
|
793
1135
|
flexDirection: 'row',
|
|
@@ -920,6 +1262,12 @@ const perfStyles = StyleSheet.create({
|
|
|
920
1262
|
alignItems: 'center',
|
|
921
1263
|
justifyContent: 'space-between',
|
|
922
1264
|
},
|
|
1265
|
+
fpsBadgeGroup: {
|
|
1266
|
+
flexDirection: 'row',
|
|
1267
|
+
alignItems: 'center',
|
|
1268
|
+
gap: 6,
|
|
1269
|
+
flexWrap: 'wrap',
|
|
1270
|
+
},
|
|
923
1271
|
fpsBadge: {
|
|
924
1272
|
paddingHorizontal: 6,
|
|
925
1273
|
paddingVertical: 2,
|
|
@@ -930,6 +1278,91 @@ const perfStyles = StyleSheet.create({
|
|
|
930
1278
|
fontFamily: AppFonts.interBold,
|
|
931
1279
|
fontSize: 10,
|
|
932
1280
|
},
|
|
1281
|
+
droppedBadge: {
|
|
1282
|
+
paddingHorizontal: 6,
|
|
1283
|
+
paddingVertical: 2,
|
|
1284
|
+
borderRadius: 5,
|
|
1285
|
+
borderWidth: 1,
|
|
1286
|
+
},
|
|
1287
|
+
droppedBadgeWarning: {
|
|
1288
|
+
backgroundColor: AppColors.red100,
|
|
1289
|
+
borderColor: AppColors.errorBorder,
|
|
1290
|
+
},
|
|
1291
|
+
droppedBadgeOptimal: {
|
|
1292
|
+
backgroundColor: AppColors.green100,
|
|
1293
|
+
borderColor: AppColors.greenBorder,
|
|
1294
|
+
},
|
|
1295
|
+
droppedBadgeText: {
|
|
1296
|
+
fontFamily: AppFonts.interBold,
|
|
1297
|
+
fontSize: 9.5,
|
|
1298
|
+
},
|
|
1299
|
+
bottleneckTag: {
|
|
1300
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1301
|
+
paddingHorizontal: 5,
|
|
1302
|
+
paddingVertical: 2,
|
|
1303
|
+
borderRadius: 4,
|
|
1304
|
+
borderWidth: 1,
|
|
1305
|
+
borderColor: AppColors.purpleTint2,
|
|
1306
|
+
},
|
|
1307
|
+
bottleneckTagText: {
|
|
1308
|
+
fontFamily: AppFonts.interMedium,
|
|
1309
|
+
fontSize: 9.5,
|
|
1310
|
+
color: AppColors.purple,
|
|
1311
|
+
},
|
|
1312
|
+
eventMetaRow: {
|
|
1313
|
+
flexDirection: 'row',
|
|
1314
|
+
alignItems: 'center',
|
|
1315
|
+
gap: 6,
|
|
1316
|
+
marginTop: 2,
|
|
1317
|
+
flexWrap: 'wrap',
|
|
1318
|
+
},
|
|
1319
|
+
eventMetaDot: {
|
|
1320
|
+
fontSize: 9,
|
|
1321
|
+
color: AppColors.grayTextWeak,
|
|
1322
|
+
},
|
|
1323
|
+
frameBudgetHint: {
|
|
1324
|
+
fontFamily: AppFonts.interMedium,
|
|
1325
|
+
fontSize: 10,
|
|
1326
|
+
},
|
|
1327
|
+
frameBarTrack: {
|
|
1328
|
+
height: 4,
|
|
1329
|
+
borderRadius: 2,
|
|
1330
|
+
backgroundColor: AppColors.graySurface,
|
|
1331
|
+
overflow: 'hidden',
|
|
1332
|
+
marginVertical: 4,
|
|
1333
|
+
},
|
|
1334
|
+
frameBarFill: {
|
|
1335
|
+
height: '100%',
|
|
1336
|
+
borderRadius: 2,
|
|
1337
|
+
},
|
|
1338
|
+
fpsMetricsGrid: {
|
|
1339
|
+
flexDirection: 'row',
|
|
1340
|
+
alignItems: 'center',
|
|
1341
|
+
backgroundColor: AppColors.white,
|
|
1342
|
+
borderRadius: 6,
|
|
1343
|
+
padding: 6,
|
|
1344
|
+
borderWidth: 1,
|
|
1345
|
+
borderColor: AppColors.dividerColor,
|
|
1346
|
+
justifyContent: 'space-between',
|
|
1347
|
+
gap: 4,
|
|
1348
|
+
},
|
|
1349
|
+
fpsMetricItem: {
|
|
1350
|
+
alignItems: 'center',
|
|
1351
|
+
flex: 1,
|
|
1352
|
+
},
|
|
1353
|
+
fpsMetricLabel: {
|
|
1354
|
+
fontFamily: AppFonts.interRegular,
|
|
1355
|
+
fontSize: 8.5,
|
|
1356
|
+
color: AppColors.grayTextWeak,
|
|
1357
|
+
textAlign: 'center',
|
|
1358
|
+
},
|
|
1359
|
+
fpsMetricVal: {
|
|
1360
|
+
fontFamily: AppFonts.interBold,
|
|
1361
|
+
fontSize: 10.5,
|
|
1362
|
+
color: AppColors.primaryBlack,
|
|
1363
|
+
marginTop: 1,
|
|
1364
|
+
textAlign: 'center',
|
|
1365
|
+
},
|
|
933
1366
|
eventLabel: {
|
|
934
1367
|
fontFamily: AppFonts.interBold,
|
|
935
1368
|
fontSize: 12,
|