react-native-inapp-inspector 1.1.25 → 1.1.26

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.
Files changed (70) hide show
  1. package/android/build.gradle +36 -0
  2. package/android/src/main/AndroidManifest.xml +3 -0
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +114 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +28 -0
  5. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  6. package/dist/commonjs/components/ErrorBoundary.js +9 -145
  7. package/dist/commonjs/components/Inspector/CrashDetail.d.ts +3 -0
  8. package/dist/commonjs/components/Inspector/CrashDetail.js +796 -0
  9. package/dist/commonjs/components/Inspector/CrashFilterModal.d.ts +20 -0
  10. package/dist/commonjs/components/Inspector/CrashFilterModal.js +597 -0
  11. package/dist/commonjs/components/Inspector/CrashTab.d.ts +3 -0
  12. package/dist/commonjs/components/Inspector/CrashTab.js +727 -0
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +52 -23
  14. package/dist/commonjs/components/Inspector/LogDetail.js +4 -3
  15. package/dist/commonjs/components/Inspector/MainScreen.js +12 -3
  16. package/dist/commonjs/components/Inspector/SettingsPanel.js +90 -1
  17. package/dist/commonjs/components/Inspector/TabBar.js +15 -3
  18. package/dist/commonjs/components/NetworkIcons.d.ts +11 -0
  19. package/dist/commonjs/components/NetworkIcons.js +96 -1
  20. package/dist/commonjs/components/SegmentedTabs.js +2 -1
  21. package/dist/commonjs/constants/version.d.ts +1 -1
  22. package/dist/commonjs/constants/version.js +1 -1
  23. package/dist/commonjs/customHooks/analyticsLogger.js +70 -44
  24. package/dist/commonjs/customHooks/consoleLogger.d.ts +1 -0
  25. package/dist/commonjs/customHooks/consoleLogger.js +70 -6
  26. package/dist/commonjs/customHooks/crashHandler.d.ts +64 -0
  27. package/dist/commonjs/customHooks/crashHandler.js +681 -0
  28. package/dist/commonjs/customHooks/networkLogger.js +64 -48
  29. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +1 -1
  30. package/dist/commonjs/i18n/locales/en.json +131 -0
  31. package/dist/commonjs/index.d.ts +3 -1
  32. package/dist/commonjs/index.js +95 -1
  33. package/dist/commonjs/types/enums.d.ts +41 -0
  34. package/dist/commonjs/types/enums.js +37 -1
  35. package/dist/commonjs/types/interfaces.d.ts +51 -1
  36. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  37. package/dist/esm/components/ErrorBoundary.js +10 -113
  38. package/dist/esm/components/Inspector/CrashDetail.d.ts +3 -0
  39. package/dist/esm/components/Inspector/CrashDetail.js +758 -0
  40. package/dist/esm/components/Inspector/CrashFilterModal.d.ts +20 -0
  41. package/dist/esm/components/Inspector/CrashFilterModal.js +557 -0
  42. package/dist/esm/components/Inspector/CrashTab.d.ts +3 -0
  43. package/dist/esm/components/Inspector/CrashTab.js +689 -0
  44. package/dist/esm/components/Inspector/InspectorHeader.js +52 -23
  45. package/dist/esm/components/Inspector/LogDetail.js +4 -3
  46. package/dist/esm/components/Inspector/MainScreen.js +12 -3
  47. package/dist/esm/components/Inspector/SettingsPanel.js +91 -2
  48. package/dist/esm/components/Inspector/TabBar.js +16 -4
  49. package/dist/esm/components/NetworkIcons.d.ts +11 -0
  50. package/dist/esm/components/NetworkIcons.js +83 -0
  51. package/dist/esm/components/SegmentedTabs.js +2 -1
  52. package/dist/esm/constants/version.d.ts +1 -1
  53. package/dist/esm/constants/version.js +1 -1
  54. package/dist/esm/customHooks/analyticsLogger.js +70 -44
  55. package/dist/esm/customHooks/consoleLogger.d.ts +1 -0
  56. package/dist/esm/customHooks/consoleLogger.js +68 -5
  57. package/dist/esm/customHooks/crashHandler.d.ts +64 -0
  58. package/dist/esm/customHooks/crashHandler.js +659 -0
  59. package/dist/esm/customHooks/networkLogger.js +64 -48
  60. package/dist/esm/helpers/gaAnalyticsRegistry.js +1 -1
  61. package/dist/esm/i18n/locales/en.json +131 -0
  62. package/dist/esm/index.d.ts +3 -1
  63. package/dist/esm/index.js +73 -1
  64. package/dist/esm/types/enums.d.ts +41 -0
  65. package/dist/esm/types/enums.js +36 -0
  66. package/dist/esm/types/interfaces.d.ts +51 -1
  67. package/ios/NetworkInspectorModule.h +6 -0
  68. package/ios/NetworkInspectorModule.m +125 -0
  69. package/package.json +5 -2
  70. package/react-native-inapp-inspector.podspec +18 -0
@@ -0,0 +1,758 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { Platform, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
3
+ import { useTranslation } from '../../i18n';
4
+ import { useInspector } from './InspectorContext';
5
+ import TouchableScale from '../TouchableScale';
6
+ import SegmentedTabs from '../SegmentedTabs';
7
+ import HighlightText from '../HighlightText';
8
+ import JsonViewer from '../JsonViewer';
9
+ import { AppColors } from '../../styles/AppColors';
10
+ import { AppFonts } from '../../styles/AppFonts';
11
+ import { exportCrashReport, parseCrashStackTrace, } from '../../customHooks/crashHandler';
12
+ import { copyToClipboard } from '../../helpers';
13
+ import { SearchIcon, ClearIcon, CopyIcon, TerminalIcon, LayersIcon, ClockIcon, CircleCheckIcon, GlobeIcon, AppleIcon, AndroidIcon, StackTraceIcon, DiagnosticsIcon, TrailIcon, RawJsonIcon, AppFramesIcon, AllFramesIcon, } from '../NetworkIcons';
14
+ const CrashDetail = React.memo(() => {
15
+ const { t } = useTranslation();
16
+ const { selectedCrash } = useInspector();
17
+ const [activeSubTab, setActiveSubTab] = useState('stack');
18
+ const [searchQuery, setSearchQuery] = useState('');
19
+ const [stackFilter, setStackFilter] = useState('app');
20
+ const [copiedSuccess, setCopiedSuccess] = useState(false);
21
+ const [copiedFrameIndex, setCopiedFrameIndex] = useState(null);
22
+ if (!selectedCrash)
23
+ return null;
24
+ const stackFrames = useMemo(() => {
25
+ if (selectedCrash.parsedStack && selectedCrash.parsedStack.length > 0) {
26
+ return selectedCrash.parsedStack;
27
+ }
28
+ if (selectedCrash.stack) {
29
+ return parseCrashStackTrace(selectedCrash.stack);
30
+ }
31
+ return [];
32
+ }, [selectedCrash]);
33
+ // Strip keys holding explicit undefined values (e.g. componentStack) so the
34
+ // Raw JSON tab never renders a literal "undefined".
35
+ const rawCrashData = useMemo(() => {
36
+ try {
37
+ return JSON.parse(JSON.stringify(selectedCrash));
38
+ }
39
+ catch {
40
+ return selectedCrash;
41
+ }
42
+ }, [selectedCrash]);
43
+ const appStackFrames = useMemo(() => {
44
+ return stackFrames.filter(f => f.isAppCode);
45
+ }, [stackFrames]);
46
+ const displayedFrames = useMemo(() => {
47
+ if (stackFilter === 'app' && appStackFrames.length > 0) {
48
+ return appStackFrames;
49
+ }
50
+ return stackFrames;
51
+ }, [stackFilter, appStackFrames, stackFrames]);
52
+ const originFrame = useMemo(() => {
53
+ return appStackFrames[0] || stackFrames[0] || null;
54
+ }, [appStackFrames, stackFrames]);
55
+ const handleCopyReport = () => {
56
+ const report = exportCrashReport(selectedCrash);
57
+ copyToClipboard(report, 'Crash Report');
58
+ setCopiedSuccess(true);
59
+ setTimeout(() => setCopiedSuccess(false), 2000);
60
+ };
61
+ const handleCopyFrame = (frame, index) => {
62
+ const text = frame.raw || `${frame.method}@${frame.file}:${frame.lineNumber}:${frame.column}`;
63
+ copyToClipboard(text, 'Stack Frame');
64
+ setCopiedFrameIndex(index);
65
+ setTimeout(() => setCopiedFrameIndex(null), 1500);
66
+ };
67
+ const tabsConfig = [
68
+ {
69
+ key: 'stack',
70
+ label: t('crash.tabStack', { count: stackFrames.length }),
71
+ icon: (isActive) => (<StackTraceIcon size={12} color={isActive ? AppColors.white : AppColors.grayText}/>),
72
+ },
73
+ {
74
+ key: 'diagnostics',
75
+ label: t('crash.tabDiagnostics'),
76
+ icon: (isActive) => (<DiagnosticsIcon size={12} color={isActive ? AppColors.white : AppColors.grayText}/>),
77
+ },
78
+ {
79
+ key: 'breadcrumbs',
80
+ label: t('crash.tabTrail', { count: selectedCrash.breadcrumbs?.length || 0 }),
81
+ icon: (isActive) => (<TrailIcon size={12} color={isActive ? AppColors.white : AppColors.grayText}/>),
82
+ },
83
+ {
84
+ key: 'raw',
85
+ label: t('crash.tabRawJson'),
86
+ icon: (isActive) => (<RawJsonIcon size={12} color={isActive ? AppColors.white : AppColors.grayText}/>),
87
+ },
88
+ ];
89
+ return (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
90
+ {/* ─── Top Info Bar (Similar to NetworkDetail / LogDetail) ─── */}
91
+ <View style={{ paddingHorizontal: 8, paddingTop: 6 }}>
92
+ <View style={localStyles.infoBar}>
93
+ {/* Top Status Badges Row */}
94
+ <View style={localStyles.badgeRow}>
95
+ {/* Fatal / Non-fatal Pill */}
96
+ <View style={[
97
+ localStyles.pillBadge,
98
+ {
99
+ backgroundColor: selectedCrash.isFatal
100
+ ? '#FEE2E2'
101
+ : '#FEF3C7',
102
+ borderColor: selectedCrash.isFatal ? '#FCA5A5' : '#FCD34D',
103
+ },
104
+ ]}>
105
+ <View style={[
106
+ localStyles.statusDot,
107
+ {
108
+ backgroundColor: selectedCrash.isFatal
109
+ ? '#DC2626'
110
+ : '#D97706',
111
+ },
112
+ ]}/>
113
+ <Text style={[
114
+ localStyles.pillBadgeText,
115
+ {
116
+ color: selectedCrash.isFatal ? '#991B1B' : '#92400E',
117
+ },
118
+ ]}>
119
+ {selectedCrash.isFatal ? t('crash.fatalCrash') : t('crash.handledException')}
120
+ </Text>
121
+ </View>
122
+
123
+ {/* Type Pill */}
124
+ <View style={[
125
+ localStyles.pillBadge,
126
+ {
127
+ backgroundColor: '#EDE9FE',
128
+ borderColor: '#C4B5FD',
129
+ },
130
+ ]}>
131
+ <Text style={[localStyles.pillBadgeText, { color: '#6D28D9' }]}>
132
+ {selectedCrash.type.toUpperCase()}
133
+ </Text>
134
+ </View>
135
+
136
+ <View style={{ flex: 1 }}/>
137
+
138
+ {/* Copy Report Action */}
139
+ <TouchableScale onPress={handleCopyReport} hitSlop={10} style={[
140
+ localStyles.actionButton,
141
+ copiedSuccess && { backgroundColor: '#ECFDF5', borderColor: '#6EE7B7' },
142
+ ]}>
143
+ {copiedSuccess ? (<CircleCheckIcon color="#059669" size={12}/>) : (<CopyIcon color={AppColors.purple} size={12}/>)}
144
+ <Text style={[
145
+ localStyles.actionButtonText,
146
+ copiedSuccess && { color: '#059669' },
147
+ ]}>
148
+ {copiedSuccess ? t('crash.copied') : t('crash.copyReport')}
149
+ </Text>
150
+ </TouchableScale>
151
+ </View>
152
+
153
+ {/* Crash Title / Message */}
154
+ <Text style={localStyles.titleText}>
155
+ {selectedCrash.message}
156
+ </Text>
157
+
158
+ {/* Metadata Row */}
159
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.metaRow}>
160
+ {originFrame && (<View style={localStyles.metaChip}>
161
+ <TerminalIcon color={AppColors.grayText} size={11}/>
162
+ <Text style={localStyles.metaChipText} numberOfLines={2}>
163
+ {originFrame.file}:{originFrame.lineNumber}
164
+ </Text>
165
+ </View>)}
166
+
167
+ <View style={localStyles.metaChip}>
168
+ <ClockIcon color={AppColors.grayText} size={11}/>
169
+ <Text style={localStyles.metaChipText}>
170
+ {selectedCrash.timeStr || new Date(selectedCrash.timestamp).toLocaleTimeString()}
171
+ </Text>
172
+ </View>
173
+
174
+ {selectedCrash.deviceInfo?.platform && (<View style={localStyles.metaChip}>
175
+ {selectedCrash.deviceInfo.platform === 'ios' ? (<AppleIcon color={AppColors.grayText} size={11}/>) : (<AndroidIcon color={AppColors.grayText} size={11}/>)}
176
+ <Text style={localStyles.metaChipText}>
177
+ {selectedCrash.deviceInfo.platform.toUpperCase()} {selectedCrash.deviceInfo.osVersion}
178
+ </Text>
179
+ </View>)}
180
+ </ScrollView>
181
+ </View>
182
+ </View>
183
+
184
+ {/* ─── Segmented Sub Tabs ─── */}
185
+ <View style={{ paddingHorizontal: 8, paddingTop: 6, paddingBottom: 4 }}>
186
+ <SegmentedTabs tabs={tabsConfig} activeKey={activeSubTab} onChange={tab => setActiveSubTab(tab)}/>
187
+ </View>
188
+
189
+ {/* ─── Search Bar (for Stack & Raw tabs) ─── */}
190
+ {(activeSubTab === 'stack' || activeSubTab === 'raw') && (<View style={{ paddingHorizontal: 12, paddingBottom: 6 }}>
191
+ <View style={localStyles.searchContainer}>
192
+ <SearchIcon color={AppColors.grayText} size={14}/>
193
+ <TextInput style={localStyles.searchInput} placeholder={t('crash.searchDetailPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={searchQuery} onChangeText={setSearchQuery} autoCapitalize="none" autoCorrect={false}/>
194
+ {searchQuery.length > 0 && (<TouchableScale onPress={() => setSearchQuery('')} hitSlop={10}>
195
+ <ClearIcon color={AppColors.grayText} size={13}/>
196
+ </TouchableScale>)}
197
+ </View>
198
+ </View>)}
199
+
200
+ {/* ─── Tab Content ScrollView ─── */}
201
+ <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 12, paddingBottom: 40, gap: 12 }}>
202
+ {/* 1. STACK TRACE TAB */}
203
+ {activeSubTab === 'stack' && (<View style={{ gap: 10 }}>
204
+ {/* Filter Chips (App Code vs All Frames) */}
205
+ <View style={localStyles.filterChipsRow}>
206
+ <TouchableScale onPress={() => setStackFilter('app')} style={[
207
+ localStyles.filterChip,
208
+ stackFilter === 'app' && localStyles.filterChipActive,
209
+ ]}>
210
+ <AppFramesIcon size={12} color={stackFilter === 'app'
211
+ ? AppColors.white
212
+ : AppColors.purple}/>
213
+ <Text style={[
214
+ localStyles.filterChipText,
215
+ stackFilter === 'app' && localStyles.filterChipTextActive,
216
+ ]}>
217
+ {t('crash.appFrames', { count: appStackFrames.length })}
218
+ </Text>
219
+ </TouchableScale>
220
+
221
+ <TouchableScale onPress={() => setStackFilter('all')} style={[
222
+ localStyles.filterChip,
223
+ stackFilter === 'all' && localStyles.filterChipActive,
224
+ ]}>
225
+ <AllFramesIcon size={12} color={stackFilter === 'all'
226
+ ? AppColors.white
227
+ : AppColors.purple}/>
228
+ <Text style={[
229
+ localStyles.filterChipText,
230
+ stackFilter === 'all' && localStyles.filterChipTextActive,
231
+ ]}>
232
+ {t('crash.allFrames', { count: stackFrames.length })}
233
+ </Text>
234
+ </TouchableScale>
235
+ </View>
236
+
237
+ {/* Frames List */}
238
+ {displayedFrames.length > 0 ? (displayedFrames
239
+ .filter(frame => {
240
+ if (!searchQuery.trim())
241
+ return true;
242
+ const q = searchQuery.toLowerCase();
243
+ return (frame.method.toLowerCase().includes(q) ||
244
+ frame.file.toLowerCase().includes(q));
245
+ })
246
+ .map((frame, index) => {
247
+ const isOrigin = index === 0 && frame.isAppCode;
248
+ return (<View key={`frame_${index}`} style={[
249
+ localStyles.frameCard,
250
+ isOrigin && localStyles.originFrameCard,
251
+ ]}>
252
+ <View style={localStyles.frameCardHeader}>
253
+ <View style={localStyles.frameIndexBadge}>
254
+ <Text style={localStyles.frameIndexBadgeText}>
255
+ #{index + 1}
256
+ </Text>
257
+ </View>
258
+ <View style={[
259
+ localStyles.frameTypeBadge,
260
+ frame.isAppCode
261
+ ? { backgroundColor: '#ECFDF5', borderColor: '#A7F3D0' }
262
+ : { backgroundColor: '#F1F5F9', borderColor: '#E2E8F0' },
263
+ ]}>
264
+ <Text style={[
265
+ localStyles.frameTypeBadgeText,
266
+ frame.isAppCode
267
+ ? { color: '#059669' }
268
+ : { color: '#64748B' },
269
+ ]}>
270
+ {frame.isAppCode ? t('crash.frameApp') : t('crash.frameLib')}
271
+ </Text>
272
+ </View>
273
+
274
+ <Text style={localStyles.frameMethodText} numberOfLines={1}>
275
+ <HighlightText text={frame.method || t('crash.anonymous')} highlight={searchQuery}/>
276
+ </Text>
277
+
278
+ <TouchableScale onPress={() => handleCopyFrame(frame, index)} hitSlop={8} style={localStyles.frameCopyBtn}>
279
+ {copiedFrameIndex === index ? (<CircleCheckIcon color={AppColors.greenColor} size={12}/>) : (<CopyIcon color={AppColors.grayText} size={12}/>)}
280
+ <Text style={localStyles.frameCopyBtnText}>
281
+ {copiedFrameIndex === index
282
+ ? t('crash.frameCopied')
283
+ : t('crash.frameCopy')}
284
+ </Text>
285
+ </TouchableScale>
286
+ </View>
287
+
288
+ <View style={localStyles.frameLocationRow}>
289
+ <Text style={localStyles.frameFileText} numberOfLines={1}>
290
+ <HighlightText text={frame.file} highlight={searchQuery}/>
291
+ </Text>
292
+ <Text style={localStyles.frameLineText}>
293
+ :{frame.lineNumber}
294
+ {frame.column ? `:${frame.column}` : ''}
295
+ </Text>
296
+ </View>
297
+
298
+ {frame.raw && (<Text style={localStyles.frameRawText} numberOfLines={2}>
299
+ {frame.raw}
300
+ </Text>)}
301
+ </View>);
302
+ })) : (<View style={localStyles.emptyContainer}>
303
+ <Text style={localStyles.emptyText}>
304
+ {selectedCrash.stack
305
+ ? selectedCrash.stack
306
+ : t('crash.noStackTrace')}
307
+ </Text>
308
+ </View>)}
309
+
310
+ {selectedCrash.componentStack && (<View style={localStyles.sectionCard}>
311
+ <View style={localStyles.sectionCardHeader}>
312
+ <LayersIcon color={AppColors.purple} size={14}/>
313
+ <Text style={localStyles.sectionCardTitle}>
314
+ {t('crash.componentHierarchy')}
315
+ </Text>
316
+ </View>
317
+ <Text style={localStyles.componentStackText}>
318
+ {selectedCrash.componentStack}
319
+ </Text>
320
+ </View>)}
321
+ </View>)}
322
+
323
+ {/* 2. DIAGNOSTICS TAB */}
324
+ {activeSubTab === 'diagnostics' && (<View style={{ gap: 12 }}>
325
+ {/* Device & OS Diagnostics Card */}
326
+ <View style={localStyles.sectionCard}>
327
+ <View style={localStyles.sectionCardHeader}>
328
+ <GlobeIcon color={AppColors.purple} size={14}/>
329
+ <Text style={localStyles.sectionCardTitle}>{t('crash.deviceEnvironment')}</Text>
330
+ </View>
331
+
332
+ <View style={localStyles.diagGrid}>
333
+ <View style={localStyles.diagItem}>
334
+ <Text style={localStyles.diagLabel}>{t('crash.platform')}</Text>
335
+ <Text style={localStyles.diagValue}>
336
+ {selectedCrash.deviceInfo?.platform?.toUpperCase() || Platform.OS.toUpperCase()}
337
+ </Text>
338
+ </View>
339
+
340
+ <View style={localStyles.diagItem}>
341
+ <Text style={localStyles.diagLabel}>{t('crash.osVersion')}</Text>
342
+ <Text style={localStyles.diagValue}>
343
+ {selectedCrash.deviceInfo?.osVersion || String(Platform.Version)}
344
+ </Text>
345
+ </View>
346
+
347
+ <View style={localStyles.diagItem}>
348
+ <Text style={localStyles.diagLabel}>{t('crash.reactNative')}</Text>
349
+ <Text style={localStyles.diagValue}>
350
+ {selectedCrash.deviceInfo?.rnVersion || '0.74+'}
351
+ </Text>
352
+ </View>
353
+
354
+ <View style={localStyles.diagItem}>
355
+ <Text style={localStyles.diagLabel}>{t('crash.jsEngine')}</Text>
356
+ <Text style={localStyles.diagValue}>
357
+ {selectedCrash.deviceInfo?.isHermes ? t('crash.hermesEngine') : t('crash.jsc')}
358
+ </Text>
359
+ </View>
360
+
361
+ <View style={localStyles.diagItem}>
362
+ <Text style={localStyles.diagLabel}>{t('crash.architecture')}</Text>
363
+ <Text style={localStyles.diagValue}>
364
+ {selectedCrash.deviceInfo?.isFabric ? t('crash.fabricNewArch') : t('crash.paperLegacy')}
365
+ </Text>
366
+ </View>
367
+
368
+ <View style={localStyles.diagItem}>
369
+ <Text style={localStyles.diagLabel}>{t('crash.appState')}</Text>
370
+ <Text style={localStyles.diagValue}>
371
+ {selectedCrash.deviceInfo?.appState || 'active'}
372
+ </Text>
373
+ </View>
374
+ </View>
375
+ </View>
376
+
377
+ {/* Memory Metrics Card */}
378
+ {selectedCrash.memoryInfo && (<View style={localStyles.sectionCard}>
379
+ <View style={localStyles.sectionCardHeader}>
380
+ <LayersIcon color={AppColors.purple} size={14}/>
381
+ <Text style={localStyles.sectionCardTitle}>{t('crash.jsHeapMemory')}</Text>
382
+ </View>
383
+
384
+ <View style={localStyles.diagGrid}>
385
+ <View style={localStyles.diagItem}>
386
+ <Text style={localStyles.diagLabel}>{t('crash.usedHeap')}</Text>
387
+ <Text style={localStyles.diagValue}>
388
+ {selectedCrash.memoryInfo.usedJSHeapSize} MB
389
+ </Text>
390
+ </View>
391
+
392
+ <View style={localStyles.diagItem}>
393
+ <Text style={localStyles.diagLabel}>{t('crash.totalHeap')}</Text>
394
+ <Text style={localStyles.diagValue}>
395
+ {selectedCrash.memoryInfo.totalJSHeapSize} MB
396
+ </Text>
397
+ </View>
398
+ </View>
399
+ </View>)}
400
+ </View>)}
401
+
402
+ {/* 3. BREADCRUMBS TAB */}
403
+ {activeSubTab === 'breadcrumbs' && (<View style={{ gap: 10 }}>
404
+ {selectedCrash.breadcrumbs && selectedCrash.breadcrumbs.length > 0 ? (selectedCrash.breadcrumbs.map((b, idx) => (<View key={`crumb_${idx}`} style={localStyles.breadcrumbCard}>
405
+ <View style={localStyles.breadcrumbHeader}>
406
+ <View style={localStyles.breadcrumbTag}>
407
+ <Text style={localStyles.breadcrumbTagText}>
408
+ {b.type.toUpperCase()}
409
+ </Text>
410
+ </View>
411
+ <Text style={localStyles.breadcrumbTime}>
412
+ {new Date(b.timestamp).toLocaleTimeString()}
413
+ </Text>
414
+ </View>
415
+ <Text style={localStyles.breadcrumbMessage}>{b.message}</Text>
416
+ {b.data && (<View style={{ marginTop: 6 }}>
417
+ <JsonViewer data={b.data} search="" wrap/>
418
+ </View>)}
419
+ </View>))) : (<View style={localStyles.emptyContainer}>
420
+ <Text style={localStyles.emptyText}>
421
+ {t('crash.noBreadcrumbs')}
422
+ </Text>
423
+ </View>)}
424
+ </View>)}
425
+
426
+ {/* 4. RAW JSON TAB */}
427
+ {activeSubTab === 'raw' && (<View style={localStyles.rawContainer}>
428
+ <JsonViewer data={rawCrashData} search={searchQuery} wrap/>
429
+ </View>)}
430
+ </ScrollView>
431
+ </View>);
432
+ });
433
+ export default CrashDetail;
434
+ const localStyles = StyleSheet.create({
435
+ infoBar: {
436
+ backgroundColor: AppColors.primaryLight,
437
+ borderRadius: 12,
438
+ padding: 12,
439
+ borderWidth: 1,
440
+ borderColor: AppColors.grayBorderSecondary,
441
+ shadowColor: AppColors.black,
442
+ shadowOffset: { width: 0, height: 1 },
443
+ shadowOpacity: 0.05,
444
+ shadowRadius: 3,
445
+ elevation: 2,
446
+ gap: 8,
447
+ },
448
+ badgeRow: {
449
+ flexDirection: 'row',
450
+ alignItems: 'center',
451
+ gap: 8,
452
+ },
453
+ pillBadge: {
454
+ flexDirection: 'row',
455
+ alignItems: 'center',
456
+ paddingHorizontal: 8,
457
+ paddingVertical: 3,
458
+ borderRadius: 12,
459
+ borderWidth: 1,
460
+ gap: 5,
461
+ },
462
+ statusDot: {
463
+ width: 6,
464
+ height: 6,
465
+ borderRadius: 3,
466
+ },
467
+ pillBadgeText: {
468
+ fontFamily: AppFonts.interBold,
469
+ fontSize: 9.5,
470
+ letterSpacing: 0.4,
471
+ },
472
+ actionButton: {
473
+ flexDirection: 'row',
474
+ alignItems: 'center',
475
+ gap: 4,
476
+ backgroundColor: `${AppColors.purple}14`,
477
+ borderColor: `${AppColors.purple}33`,
478
+ borderWidth: 1,
479
+ paddingHorizontal: 8,
480
+ paddingVertical: 4,
481
+ borderRadius: 8,
482
+ },
483
+ actionButtonText: {
484
+ fontFamily: AppFonts.interBold,
485
+ fontSize: 10.5,
486
+ color: AppColors.purple,
487
+ },
488
+ titleText: {
489
+ fontFamily: AppFonts.interBold,
490
+ fontSize: 14,
491
+ color: AppColors.primaryBlack,
492
+ lineHeight: 19,
493
+ },
494
+ metaRow: {
495
+ flexDirection: 'row',
496
+ alignItems: 'center',
497
+ gap: 6,
498
+ paddingVertical: 2,
499
+ },
500
+ metaChip: {
501
+ flexDirection: 'row',
502
+ alignItems: 'center',
503
+ gap: 4,
504
+ backgroundColor: AppColors.grayBackground,
505
+ paddingHorizontal: 7,
506
+ paddingVertical: 3,
507
+ borderRadius: 6,
508
+ borderWidth: 1,
509
+ borderColor: AppColors.dividerColor,
510
+ },
511
+ metaChipText: {
512
+ fontFamily: AppFonts.interMedium,
513
+ fontSize: 10,
514
+ color: AppColors.grayText,
515
+ },
516
+ searchContainer: {
517
+ flexDirection: 'row',
518
+ alignItems: 'center',
519
+ backgroundColor: AppColors.primaryLight,
520
+ borderWidth: 1,
521
+ borderColor: AppColors.grayBorderSecondary,
522
+ borderRadius: 8,
523
+ paddingHorizontal: 10,
524
+ height: 36,
525
+ gap: 8,
526
+ },
527
+ searchInput: {
528
+ flex: 1,
529
+ fontFamily: AppFonts.interRegular,
530
+ fontSize: 12,
531
+ color: AppColors.primaryBlack,
532
+ paddingVertical: 0,
533
+ },
534
+ filterChipsRow: {
535
+ flexDirection: 'row',
536
+ alignItems: 'center',
537
+ gap: 8,
538
+ },
539
+ filterChip: {
540
+ flexDirection: 'row',
541
+ alignItems: 'center',
542
+ gap: 5,
543
+ paddingHorizontal: 10,
544
+ paddingVertical: 5,
545
+ borderRadius: 8,
546
+ backgroundColor: AppColors.primaryLight,
547
+ borderWidth: 1,
548
+ borderColor: AppColors.grayBorderSecondary,
549
+ },
550
+ filterChipActive: {
551
+ backgroundColor: AppColors.purple,
552
+ borderColor: AppColors.purple,
553
+ },
554
+ filterChipText: {
555
+ fontFamily: AppFonts.interMedium,
556
+ fontSize: 11,
557
+ color: AppColors.grayText,
558
+ },
559
+ filterChipTextActive: {
560
+ fontFamily: AppFonts.interBold,
561
+ color: AppColors.white,
562
+ },
563
+ frameCard: {
564
+ backgroundColor: AppColors.primaryLight,
565
+ borderRadius: 8,
566
+ padding: 10,
567
+ borderWidth: 1,
568
+ borderColor: AppColors.grayBorderSecondary,
569
+ gap: 4,
570
+ },
571
+ originFrameCard: {
572
+ borderColor: '#6EE7B7',
573
+ backgroundColor: '#F0FDF4',
574
+ },
575
+ frameCardHeader: {
576
+ flexDirection: 'row',
577
+ alignItems: 'center',
578
+ gap: 8,
579
+ },
580
+ frameIndexBadge: {
581
+ minWidth: 24,
582
+ height: 18,
583
+ paddingHorizontal: 4,
584
+ borderRadius: 4,
585
+ backgroundColor: `${AppColors.purple}14`,
586
+ alignItems: 'center',
587
+ justifyContent: 'center',
588
+ },
589
+ frameIndexBadgeText: {
590
+ fontFamily: AppFonts.interBold,
591
+ fontSize: 9,
592
+ color: AppColors.purple,
593
+ },
594
+ frameTypeBadge: {
595
+ paddingHorizontal: 5,
596
+ paddingVertical: 2,
597
+ borderRadius: 4,
598
+ borderWidth: 1,
599
+ },
600
+ frameTypeBadgeText: {
601
+ fontFamily: AppFonts.interBold,
602
+ fontSize: 8.5,
603
+ },
604
+ frameMethodText: {
605
+ fontFamily: AppFonts.interBold,
606
+ fontSize: 12,
607
+ color: AppColors.primaryBlack,
608
+ flex: 1,
609
+ },
610
+ frameLocationRow: {
611
+ flexDirection: 'row',
612
+ alignItems: 'center',
613
+ },
614
+ frameFileText: {
615
+ fontFamily: AppFonts.interRegular,
616
+ fontSize: 11,
617
+ color: AppColors.grayText,
618
+ flex: 1,
619
+ },
620
+ frameLineText: {
621
+ fontFamily: AppFonts.interBold,
622
+ fontSize: 11,
623
+ color: AppColors.purple,
624
+ },
625
+ frameCopyBtn: {
626
+ flexDirection: 'row',
627
+ alignItems: 'center',
628
+ gap: 3,
629
+ paddingHorizontal: 6,
630
+ paddingVertical: 3,
631
+ borderRadius: 5,
632
+ backgroundColor: AppColors.grayBackground,
633
+ borderWidth: 1,
634
+ borderColor: AppColors.dividerColor,
635
+ },
636
+ frameCopyBtnText: {
637
+ fontFamily: AppFonts.interBold,
638
+ fontSize: 9,
639
+ color: AppColors.grayText,
640
+ },
641
+ frameRawText: {
642
+ fontFamily: AppFonts.interRegular,
643
+ fontSize: 9.5,
644
+ color: AppColors.grayTextWeak,
645
+ backgroundColor: `${AppColors.primaryBlack}06`,
646
+ borderRadius: 5,
647
+ paddingHorizontal: 7,
648
+ paddingVertical: 4,
649
+ overflow: 'hidden',
650
+ },
651
+ componentStackText: {
652
+ fontFamily: AppFonts.interRegular,
653
+ fontSize: 11,
654
+ lineHeight: 16,
655
+ color: AppColors.grayText,
656
+ backgroundColor: `${AppColors.primaryBlack}06`,
657
+ borderRadius: 6,
658
+ padding: 10,
659
+ },
660
+ sectionCard: {
661
+ backgroundColor: AppColors.primaryLight,
662
+ borderRadius: 10,
663
+ padding: 12,
664
+ borderWidth: 1,
665
+ borderColor: AppColors.grayBorderSecondary,
666
+ gap: 10,
667
+ },
668
+ sectionCardHeader: {
669
+ flexDirection: 'row',
670
+ alignItems: 'center',
671
+ gap: 6,
672
+ paddingBottom: 6,
673
+ borderBottomWidth: 1,
674
+ borderBottomColor: AppColors.dividerColor,
675
+ },
676
+ sectionCardTitle: {
677
+ fontFamily: AppFonts.interBold,
678
+ fontSize: 12.5,
679
+ color: AppColors.primaryBlack,
680
+ },
681
+ diagGrid: {
682
+ flexDirection: 'row',
683
+ flexWrap: 'wrap',
684
+ rowGap: 8,
685
+ columnGap: 12,
686
+ },
687
+ diagItem: {
688
+ width: '47%',
689
+ gap: 2,
690
+ },
691
+ diagLabel: {
692
+ fontFamily: AppFonts.interRegular,
693
+ fontSize: 10,
694
+ color: AppColors.grayTextWeak,
695
+ },
696
+ diagValue: {
697
+ fontFamily: AppFonts.interBold,
698
+ fontSize: 11.5,
699
+ color: AppColors.primaryBlack,
700
+ },
701
+ breadcrumbCard: {
702
+ backgroundColor: AppColors.primaryLight,
703
+ borderRadius: 8,
704
+ padding: 10,
705
+ borderWidth: 1,
706
+ borderColor: AppColors.grayBorderSecondary,
707
+ gap: 4,
708
+ },
709
+ breadcrumbHeader: {
710
+ flexDirection: 'row',
711
+ alignItems: 'center',
712
+ justifyContent: 'space-between',
713
+ },
714
+ breadcrumbTag: {
715
+ backgroundColor: `${AppColors.purple}14`,
716
+ paddingHorizontal: 6,
717
+ paddingVertical: 2,
718
+ borderRadius: 4,
719
+ },
720
+ breadcrumbTagText: {
721
+ fontFamily: AppFonts.interBold,
722
+ fontSize: 9,
723
+ color: AppColors.purple,
724
+ },
725
+ breadcrumbTime: {
726
+ fontFamily: AppFonts.interRegular,
727
+ fontSize: 10,
728
+ color: AppColors.grayTextWeak,
729
+ },
730
+ breadcrumbMessage: {
731
+ fontFamily: AppFonts.interRegular,
732
+ fontSize: 11.5,
733
+ color: AppColors.primaryBlack,
734
+ marginTop: 2,
735
+ },
736
+ rawContainer: {
737
+ backgroundColor: AppColors.primaryLight,
738
+ borderRadius: 10,
739
+ padding: 10,
740
+ borderWidth: 1,
741
+ borderColor: AppColors.grayBorderSecondary,
742
+ },
743
+ emptyContainer: {
744
+ padding: 16,
745
+ alignItems: 'center',
746
+ justifyContent: 'center',
747
+ backgroundColor: AppColors.primaryLight,
748
+ borderRadius: 8,
749
+ borderWidth: 1,
750
+ borderColor: AppColors.grayBorderSecondary,
751
+ },
752
+ emptyText: {
753
+ fontFamily: AppFonts.interRegular,
754
+ fontSize: 11,
755
+ color: AppColors.grayText,
756
+ textAlign: 'center',
757
+ },
758
+ });