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,727 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const react_1 = __importStar(require("react"));
40
+ const react_native_1 = require("react-native");
41
+ const i18n_1 = require("../../i18n");
42
+ const InspectorContext_1 = require("./InspectorContext");
43
+ const TouchableScale_1 = __importDefault(require("../TouchableScale"));
44
+ const AnimatedEntrance_1 = __importDefault(require("../AnimatedEntrance"));
45
+ const EndOfListFooter_1 = __importDefault(require("../EndOfListFooter"));
46
+ const HighlightText_1 = __importDefault(require("../HighlightText"));
47
+ const AppColors_1 = require("../../styles/AppColors");
48
+ const AppFonts_1 = require("../../styles/AppFonts");
49
+ const crashHandler_1 = require("../../customHooks/crashHandler");
50
+ const CrashFilterModal_1 = __importStar(require("./CrashFilterModal"));
51
+ const NetworkIcons_1 = require("../NetworkIcons");
52
+ const getRelativeTime = (timestamp) => {
53
+ const now = Date.now();
54
+ const diff = Math.max(0, now - timestamp);
55
+ const seconds = Math.floor(diff / 1000);
56
+ const minutes = Math.floor(seconds / 60);
57
+ const hours = Math.floor(minutes / 60);
58
+ const days = Math.floor(hours / 24);
59
+ if (seconds < 60)
60
+ return `${seconds}s ago`;
61
+ if (minutes < 60)
62
+ return `${minutes}m ago`;
63
+ if (hours < 24)
64
+ return `${hours}h ago`;
65
+ return `${days}d ago`;
66
+ };
67
+ const CrashTab = react_1.default.memo(() => {
68
+ const { t } = (0, i18n_1.useTranslation)();
69
+ const { crashRecords, selectedCrash, setSelectedCrash } = (0, InspectorContext_1.useInspector)();
70
+ const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
71
+ const [filterType, setFilterType] = (0, react_1.useState)('all');
72
+ const [crashFilters, setCrashFilters] = (0, react_1.useState)(CrashFilterModal_1.DEFAULT_CRASH_FILTERS);
73
+ const [isFilterModalOpen, setIsFilterModalOpen] = (0, react_1.useState)(false);
74
+ const [detailSubTab, setDetailSubTab] = (0, react_1.useState)('stack');
75
+ // Auto-open the latest crash detail on first visit if crashes exist
76
+ const hasAutoOpened = react_1.default.useRef(false);
77
+ react_1.default.useEffect(() => {
78
+ if (!hasAutoOpened.current && !selectedCrash && crashRecords.length > 0) {
79
+ setSelectedCrash(crashRecords[0]);
80
+ hasAutoOpened.current = true;
81
+ }
82
+ }, [crashRecords, selectedCrash, setSelectedCrash]);
83
+ // Calculate statistics
84
+ const stats = (0, react_1.useMemo)(() => {
85
+ const total = crashRecords.length;
86
+ let fatalCount = 0;
87
+ let jsCount = 0;
88
+ let promiseCount = 0;
89
+ let renderCount = 0;
90
+ let nativeCount = 0;
91
+ crashRecords.forEach(c => {
92
+ if (c.isFatal)
93
+ fatalCount++;
94
+ if (c.type === 'js')
95
+ jsCount++;
96
+ else if (c.type === 'promise')
97
+ promiseCount++;
98
+ else if (c.type === 'render')
99
+ renderCount++;
100
+ else if (c.type === 'native')
101
+ nativeCount++;
102
+ });
103
+ return { total, fatalCount, jsCount, promiseCount, renderCount, nativeCount };
104
+ }, [crashRecords]);
105
+ // Filtered crash list
106
+ const filteredList = (0, react_1.useMemo)(() => {
107
+ let list = crashRecords.filter(item => {
108
+ // Type filter
109
+ if (filterType === 'fatal' && !item.isFatal)
110
+ return false;
111
+ if (filterType === 'js' && item.type !== 'js')
112
+ return false;
113
+ if (filterType === 'promise' && item.type !== 'promise')
114
+ return false;
115
+ if (filterType === 'render' && item.type !== 'render')
116
+ return false;
117
+ if (filterType === 'native' && item.type !== 'native')
118
+ return false;
119
+ // Modal filters: crash types
120
+ if (crashFilters.types.size > 0 && !crashFilters.types.has('all')) {
121
+ const matches = (crashFilters.types.has('fatal') && item.isFatal) ||
122
+ (crashFilters.types.has('js') && item.type === 'js') ||
123
+ (crashFilters.types.has('promise') && item.type === 'promise') ||
124
+ (crashFilters.types.has('render') && item.type === 'render') ||
125
+ (crashFilters.types.has('native') && item.type === 'native');
126
+ if (!matches)
127
+ return false;
128
+ }
129
+ // Modal filters: time window
130
+ if (crashFilters.timeWindow !== 'all') {
131
+ const now = Date.now();
132
+ const cutoff = crashFilters.timeWindow === '15m'
133
+ ? now - 15 * 60 * 1000
134
+ : crashFilters.timeWindow === '1h'
135
+ ? now - 60 * 60 * 1000
136
+ : crashFilters.timeWindow === '24h'
137
+ ? now - 24 * 60 * 60 * 1000
138
+ : now - 7 * 24 * 60 * 60 * 1000;
139
+ if (item.timestamp < cutoff)
140
+ return false;
141
+ }
142
+ // Modal filters: platform
143
+ if (crashFilters.platform !== 'all') {
144
+ const platform = (item.deviceInfo?.platform || '').toLowerCase();
145
+ if (platform !== crashFilters.platform)
146
+ return false;
147
+ }
148
+ // Modal filters: engine
149
+ if (crashFilters.engine !== 'all') {
150
+ const isHermes = item.deviceInfo?.isHermes;
151
+ if (crashFilters.engine === 'hermes' && isHermes !== true)
152
+ return false;
153
+ if (crashFilters.engine === 'jsc' && (isHermes === true))
154
+ return false;
155
+ }
156
+ // Search filter
157
+ if (searchQuery.trim().length > 0) {
158
+ const q = searchQuery.toLowerCase();
159
+ const msg = (item.message || '').toLowerCase();
160
+ const name = (item.name || '').toLowerCase();
161
+ const stack = (item.stack || '').toLowerCase();
162
+ return msg.includes(q) || name.includes(q) || stack.includes(q);
163
+ }
164
+ return true;
165
+ });
166
+ // Modal filters: sort order
167
+ if (crashFilters.sortBy === 'time_asc') {
168
+ list = [...list].sort((a, b) => a.timestamp - b.timestamp);
169
+ }
170
+ return list;
171
+ }, [crashRecords, filterType, searchQuery, crashFilters]);
172
+ const handleClearAll = () => {
173
+ react_native_1.Alert.alert(t('crash.clearTitle'), t('crash.clearMessage'), [
174
+ { text: t('crash.clearCancel'), style: 'cancel' },
175
+ {
176
+ text: t('crash.clearConfirm'),
177
+ style: 'destructive',
178
+ onPress: () => (0, crashHandler_1.clearCrashRecords)(),
179
+ },
180
+ ]);
181
+ };
182
+ const renderCard = (0, react_1.useCallback)(({ item, index }) => {
183
+ const isFatal = item.isFatal;
184
+ const typeLabel = item.type.toUpperCase();
185
+ const topFrame = item.parsedStack && item.parsedStack.length > 0
186
+ ? item.parsedStack.find(f => f.isAppCode) || item.parsedStack[0]
187
+ : null;
188
+ const typeBadgeBg = item.type === 'native'
189
+ ? `${AppColors_1.AppColors.errorColor}22`
190
+ : item.type === 'render'
191
+ ? `${AppColors_1.AppColors.purple}22`
192
+ : item.type === 'promise'
193
+ ? `${AppColors_1.AppColors.darkOrange}22`
194
+ : `${AppColors_1.AppColors.offerPurple}22`;
195
+ const typeBadgeColor = item.type === 'native'
196
+ ? AppColors_1.AppColors.errorColor
197
+ : item.type === 'render'
198
+ ? AppColors_1.AppColors.purple
199
+ : item.type === 'promise'
200
+ ? AppColors_1.AppColors.darkOrange
201
+ : AppColors_1.AppColors.offerPurple;
202
+ return (<AnimatedEntrance_1.default index={index} distance={8}>
203
+ <TouchableScale_1.default onPress={() => setSelectedCrash(item)} style={[
204
+ localStyles.cardContainer,
205
+ isFatal && localStyles.fatalCardBorder,
206
+ ]}>
207
+ {/* Header: #S.No, Badges & Timestamp */}
208
+ <react_native_1.View style={localStyles.cardHeader}>
209
+ <react_native_1.View style={localStyles.badgeRow}>
210
+ <react_native_1.View style={localStyles.sNoBadge}>
211
+ <react_native_1.Text style={localStyles.sNoBadgeText}>#{index + 1}</react_native_1.Text>
212
+ </react_native_1.View>
213
+
214
+ {isFatal ? (<react_native_1.View style={localStyles.fatalBadge}>
215
+ <NetworkIcons_1.FlameIcon size={10} color={AppColors_1.AppColors.white}/>
216
+ <react_native_1.Text style={localStyles.fatalBadgeText}>{t('crash.fatalBadge')}</react_native_1.Text>
217
+ </react_native_1.View>) : (<react_native_1.View style={localStyles.caughtBadge}>
218
+ <react_native_1.Text style={localStyles.caughtBadgeText}>{t('crash.handledBadge')}</react_native_1.Text>
219
+ </react_native_1.View>)}
220
+
221
+ <react_native_1.View style={[
222
+ localStyles.typeBadge,
223
+ { backgroundColor: typeBadgeBg },
224
+ ]}>
225
+ <react_native_1.Text style={[
226
+ localStyles.typeBadgeText,
227
+ { color: typeBadgeColor },
228
+ ]}>
229
+ {typeLabel}
230
+ </react_native_1.Text>
231
+ </react_native_1.View>
232
+ </react_native_1.View>
233
+
234
+ <react_native_1.View style={localStyles.timeRow}>
235
+ <NetworkIcons_1.ClockIcon size={11} color={AppColors_1.AppColors.grayTextWeak}/>
236
+ <react_native_1.Text style={localStyles.timestampText}>
237
+ {item.timeStr || new Date(item.timestamp).toLocaleTimeString()}
238
+ </react_native_1.Text>
239
+ <react_native_1.Text style={localStyles.relativeTimeText}>
240
+ {getRelativeTime(item.timestamp)}
241
+ </react_native_1.Text>
242
+ </react_native_1.View>
243
+ </react_native_1.View>
244
+
245
+ {/* Error Message */}
246
+ <react_native_1.View style={{ marginVertical: 6 }}>
247
+ <HighlightText_1.default text={item.message || t('crash.unknownException')} highlight={searchQuery} numberOfLines={3} style={localStyles.errorMessageText}/>
248
+ </react_native_1.View>
249
+
250
+ {/* Top stack frame location if available */}
251
+ {topFrame && (<react_native_1.View style={localStyles.locationRow}>
252
+ <NetworkIcons_1.TerminalIcon size={12} color={AppColors_1.AppColors.grayTextWeak}/>
253
+ <react_native_1.Text numberOfLines={1} ellipsizeMode="middle" style={localStyles.locationText}>
254
+ {topFrame.method !== '<anonymous>' ? `${topFrame.method} ` : ''}
255
+ <react_native_1.Text style={{ color: AppColors_1.AppColors.purple, fontFamily: AppFonts_1.AppFonts.interBold }}>
256
+ {topFrame.file}:{topFrame.lineNumber}
257
+ </react_native_1.Text>
258
+ </react_native_1.Text>
259
+ </react_native_1.View>)}
260
+
261
+ {/* Footer row: Actions */}
262
+ <react_native_1.View style={localStyles.cardFooter}>
263
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
264
+ {item.deviceInfo && (<react_native_1.View style={localStyles.metaPill}>
265
+ {item.deviceInfo.platform === 'ios' ? (<NetworkIcons_1.AppleIcon size={10} color={AppColors_1.AppColors.grayText}/>) : item.deviceInfo.platform === 'android' ? (<NetworkIcons_1.AndroidIcon size={10} color={AppColors_1.AppColors.grayText}/>) : null}
266
+ <react_native_1.Text style={localStyles.metaPillText}>
267
+ {item.deviceInfo.platform?.toUpperCase()}{' '}
268
+ {item.deviceInfo.osVersion || ''}
269
+ </react_native_1.Text>
270
+ </react_native_1.View>)}
271
+ {item.deviceInfo && (<react_native_1.View style={localStyles.metaPill}>
272
+ <react_native_1.Text style={localStyles.metaPillText}>
273
+ {item.deviceInfo.isHermes ? 'Hermes' : 'JSC'}
274
+ </react_native_1.Text>
275
+ </react_native_1.View>)}
276
+ {item.parsedStack && item.parsedStack.length > 0 && (<react_native_1.View style={localStyles.metaPill}>
277
+ <NetworkIcons_1.CodeBracketsIcon size={10} color={AppColors_1.AppColors.grayText}/>
278
+ <react_native_1.Text style={localStyles.metaPillText}>
279
+ {item.parsedStack.length}
280
+ </react_native_1.Text>
281
+ </react_native_1.View>)}
282
+ {item.breadcrumbs && item.breadcrumbs.length > 0 && (<react_native_1.View style={localStyles.metaPill}>
283
+ <NetworkIcons_1.RepeatIcon size={10} color={AppColors_1.AppColors.grayText}/>
284
+ <react_native_1.Text style={localStyles.metaPillText}>
285
+ {item.breadcrumbs.length} trail
286
+ </react_native_1.Text>
287
+ </react_native_1.View>)}
288
+ {item.memoryInfo && (<react_native_1.View style={localStyles.metaPill}>
289
+ <react_native_1.Text style={localStyles.metaPillText}>
290
+ {item.memoryInfo.usedJSHeapSize} MB
291
+ </react_native_1.Text>
292
+ </react_native_1.View>)}
293
+ </react_native_1.View>
294
+ </react_native_1.View>
295
+ </TouchableScale_1.default>
296
+ </AnimatedEntrance_1.default>);
297
+ }, [searchQuery, setSelectedCrash]);
298
+ return (<react_native_1.View style={localStyles.container}>
299
+ {/* ─── Top Stats Bar ─── */}
300
+ <react_native_1.View style={localStyles.statsBar}>
301
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.statsScrollContent}>
302
+ <react_native_1.View style={[localStyles.statChip, localStyles.statChipActive]}>
303
+ <react_native_1.Text style={localStyles.statChipValue}>{stats.total}</react_native_1.Text>
304
+ <react_native_1.Text style={localStyles.statChipLabel}>{t('crash.statCrashes')}</react_native_1.Text>
305
+ </react_native_1.View>
306
+
307
+ <react_native_1.View style={localStyles.statChip}>
308
+ <react_native_1.Text style={[localStyles.statChipValue, { color: AppColors_1.AppColors.errorColor }]}>
309
+ {stats.fatalCount}
310
+ </react_native_1.Text>
311
+ <react_native_1.Text style={localStyles.statChipLabel}>{t('crash.statFatal')}</react_native_1.Text>
312
+ </react_native_1.View>
313
+
314
+ <react_native_1.View style={localStyles.statChip}>
315
+ <react_native_1.Text style={[localStyles.statChipValue, { color: AppColors_1.AppColors.offerPurple }]}>
316
+ {stats.jsCount}
317
+ </react_native_1.Text>
318
+ <react_native_1.Text style={localStyles.statChipLabel}>{t('crash.statJsErrors')}</react_native_1.Text>
319
+ </react_native_1.View>
320
+
321
+ <react_native_1.View style={localStyles.statChip}>
322
+ <react_native_1.Text style={[localStyles.statChipValue, { color: AppColors_1.AppColors.darkOrange }]}>
323
+ {stats.promiseCount}
324
+ </react_native_1.Text>
325
+ <react_native_1.Text style={localStyles.statChipLabel}>{t('crash.statPromises')}</react_native_1.Text>
326
+ </react_native_1.View>
327
+
328
+ <react_native_1.View style={localStyles.statChip}>
329
+ <react_native_1.Text style={[localStyles.statChipValue, { color: AppColors_1.AppColors.purple }]}>
330
+ {stats.renderCount}
331
+ </react_native_1.Text>
332
+ <react_native_1.Text style={localStyles.statChipLabel}>{t('crash.statRender')}</react_native_1.Text>
333
+ </react_native_1.View>
334
+
335
+ <react_native_1.View style={localStyles.statChip}>
336
+ <react_native_1.Text style={[localStyles.statChipValue, { color: AppColors_1.AppColors.skyBlue }]}>
337
+ {stats.nativeCount}
338
+ </react_native_1.Text>
339
+ <react_native_1.Text style={localStyles.statChipLabel}>{t('crash.statNative')}</react_native_1.Text>
340
+ </react_native_1.View>
341
+ </react_native_1.ScrollView>
342
+ </react_native_1.View>
343
+
344
+ {/* ─── Search & Controls ─── */}
345
+ <react_native_1.View style={localStyles.controlsContainer}>
346
+ <react_native_1.View style={localStyles.searchRow}>
347
+ <react_native_1.View style={localStyles.searchInputWrapper}>
348
+ <NetworkIcons_1.SearchIcon color={AppColors_1.AppColors.grayTextWeak} size={14}/>
349
+ <react_native_1.TextInput value={searchQuery} onChangeText={setSearchQuery} placeholder={t('crash.searchPlaceholder')} placeholderTextColor={AppColors_1.AppColors.grayTextWeak} style={localStyles.searchInput} autoCapitalize="none" autoCorrect={false}/>
350
+ {searchQuery.length > 0 && (<react_native_1.TouchableOpacity onPress={() => setSearchQuery('')} hitSlop={8}>
351
+ <NetworkIcons_1.ClearIcon color={AppColors_1.AppColors.grayTextWeak} size={14}/>
352
+ </react_native_1.TouchableOpacity>)}
353
+ </react_native_1.View>
354
+
355
+ {/* Crash Filters */}
356
+ {crashRecords.length > 0 && (<TouchableScale_1.default onPress={() => setIsFilterModalOpen(true)} style={[
357
+ localStyles.filterButton,
358
+ !(0, CrashFilterModal_1.isCrashFiltersDefault)(crashFilters) && {
359
+ borderColor: `${AppColors_1.AppColors.brandPurple}60`,
360
+ backgroundColor: `${AppColors_1.AppColors.brandPurple}15`,
361
+ },
362
+ ]}>
363
+ <NetworkIcons_1.FilterIcon size={14} color={(0, CrashFilterModal_1.isCrashFiltersDefault)(crashFilters)
364
+ ? AppColors_1.AppColors.grayTextStrong
365
+ : AppColors_1.AppColors.brandPurple}/>
366
+ {!(0, CrashFilterModal_1.isCrashFiltersDefault)(crashFilters) && (<react_native_1.View style={{
367
+ position: 'absolute',
368
+ top: 2,
369
+ right: 2,
370
+ width: 7,
371
+ height: 7,
372
+ borderRadius: 3.5,
373
+ backgroundColor: AppColors_1.AppColors.darkOrange,
374
+ borderWidth: 1,
375
+ borderColor: AppColors_1.AppColors.white,
376
+ }}/>)}
377
+ </TouchableScale_1.default>)}
378
+
379
+ {/* Clear Crashes */}
380
+ {crashRecords.length > 0 && (<TouchableScale_1.default onPress={handleClearAll} style={localStyles.clearButton}>
381
+ <NetworkIcons_1.TrashIcon size={14} color={AppColors_1.AppColors.errorColor}/>
382
+ </TouchableScale_1.default>)}
383
+ </react_native_1.View>
384
+
385
+ {/* ─── Filter Chips ─── */}
386
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.filterScrollContent}>
387
+ {[
388
+ { key: 'all', label: t('crash.filterAll'), Icon: NetworkIcons_1.LayersIcon },
389
+ { key: 'fatal', label: t('crash.filterFatal'), Icon: NetworkIcons_1.SkullIcon },
390
+ { key: 'js', label: t('crash.filterJsError'), Icon: NetworkIcons_1.JsIcon },
391
+ { key: 'promise', label: t('crash.filterPromise'), Icon: NetworkIcons_1.HourglassIcon },
392
+ { key: 'render', label: t('crash.filterRender'), Icon: NetworkIcons_1.LayoutIcon },
393
+ { key: 'native', label: t('crash.filterNative'), Icon: NetworkIcons_1.ChipIcon },
394
+ ].map(chip => {
395
+ const isActive = filterType === chip.key;
396
+ return (<react_native_1.TouchableOpacity key={chip.key} onPress={() => setFilterType(chip.key)} style={[
397
+ localStyles.filterChip,
398
+ isActive && localStyles.filterChipActive,
399
+ ]}>
400
+ <chip.Icon size={12} color={isActive ? AppColors_1.AppColors.white : AppColors_1.AppColors.grayTextStrong}/>
401
+ <react_native_1.Text style={[
402
+ localStyles.filterChipText,
403
+ isActive && localStyles.filterChipTextActive,
404
+ ]}>
405
+ {chip.label}
406
+ </react_native_1.Text>
407
+ </react_native_1.TouchableOpacity>);
408
+ })}
409
+ </react_native_1.ScrollView>
410
+ </react_native_1.View>
411
+
412
+ {/* ─── Crash List or Empty State ─── */}
413
+ {filteredList.length === 0 ? (<react_native_1.ScrollView contentContainerStyle={localStyles.emptyContainer} showsVerticalScrollIndicator={false}>
414
+ <react_native_1.View style={localStyles.emptyIconCircle}>
415
+ <NetworkIcons_1.ShieldAlertIcon size={38} color={AppColors_1.AppColors.greenColor}/>
416
+ </react_native_1.View>
417
+ <react_native_1.Text style={localStyles.emptyTitle}>{t('crash.emptyTitle')}</react_native_1.Text>
418
+ <react_native_1.Text style={localStyles.emptySub}>
419
+ {searchQuery
420
+ ? t('crash.emptySearchSubtitle')
421
+ : t('crash.emptySubtitle')}
422
+ </react_native_1.Text>
423
+ </react_native_1.ScrollView>) : (<react_native_1.FlatList data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter_1.default />}/>)}
424
+
425
+ {/* Filter Modal */}
426
+ <CrashFilterModal_1.default visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)} filters={crashFilters} onApply={setCrashFilters} searchQuery={searchQuery}/>
427
+ </react_native_1.View>);
428
+ });
429
+ exports.default = CrashTab;
430
+ const localStyles = react_native_1.StyleSheet.create({
431
+ container: {
432
+ flex: 1,
433
+ backgroundColor: AppColors_1.AppColors.grayBackground,
434
+ },
435
+ statsBar: {
436
+ backgroundColor: AppColors_1.AppColors.primaryLight,
437
+ borderBottomWidth: 1,
438
+ borderBottomColor: AppColors_1.AppColors.grayBorderSecondary,
439
+ paddingVertical: 10,
440
+ },
441
+ statsScrollContent: {
442
+ paddingHorizontal: 16,
443
+ gap: 8,
444
+ },
445
+ statChip: {
446
+ flexDirection: 'row',
447
+ alignItems: 'center',
448
+ gap: 6,
449
+ paddingHorizontal: 10,
450
+ paddingVertical: 5,
451
+ borderRadius: 8,
452
+ backgroundColor: AppColors_1.AppColors.purpleShade50,
453
+ borderWidth: 1,
454
+ borderColor: `${AppColors_1.AppColors.purple}14`,
455
+ },
456
+ statChipActive: {
457
+ backgroundColor: `${AppColors_1.AppColors.purple}14`,
458
+ borderColor: `${AppColors_1.AppColors.purple}33`,
459
+ },
460
+ statChipValue: {
461
+ fontFamily: AppFonts_1.AppFonts.interBold,
462
+ fontSize: 13,
463
+ color: AppColors_1.AppColors.primaryBlack,
464
+ },
465
+ statChipLabel: {
466
+ fontFamily: AppFonts_1.AppFonts.interMedium,
467
+ fontSize: 11,
468
+ color: AppColors_1.AppColors.grayText,
469
+ },
470
+ controlsContainer: {
471
+ paddingHorizontal: 16,
472
+ paddingTop: 12,
473
+ paddingBottom: 6,
474
+ backgroundColor: AppColors_1.AppColors.grayBackground,
475
+ },
476
+ searchRow: {
477
+ flexDirection: 'row',
478
+ alignItems: 'center',
479
+ gap: 8,
480
+ },
481
+ searchInputWrapper: {
482
+ flex: 1,
483
+ flexDirection: 'row',
484
+ alignItems: 'center',
485
+ backgroundColor: AppColors_1.AppColors.primaryLight,
486
+ borderWidth: 1,
487
+ borderColor: AppColors_1.AppColors.grayBorderSecondary,
488
+ borderRadius: 10,
489
+ paddingHorizontal: 10,
490
+ height: 38,
491
+ gap: 8,
492
+ },
493
+ searchInput: {
494
+ flex: 1,
495
+ fontFamily: AppFonts_1.AppFonts.interRegular,
496
+ fontSize: 13,
497
+ color: AppColors_1.AppColors.primaryBlack,
498
+ paddingVertical: 0,
499
+ },
500
+ clearButton: {
501
+ width: 38,
502
+ height: 38,
503
+ borderRadius: 10,
504
+ backgroundColor: `${AppColors_1.AppColors.errorColor}14`,
505
+ borderWidth: 1,
506
+ borderColor: `${AppColors_1.AppColors.errorColor}22`,
507
+ alignItems: 'center',
508
+ justifyContent: 'center',
509
+ },
510
+ filterButton: {
511
+ width: 38,
512
+ height: 38,
513
+ borderRadius: 10,
514
+ backgroundColor: `${AppColors_1.AppColors.brandPurple}0F`,
515
+ borderWidth: 1,
516
+ borderColor: `${AppColors_1.AppColors.brandPurple}30`,
517
+ alignItems: 'center',
518
+ justifyContent: 'center',
519
+ },
520
+ filterScrollContent: {
521
+ paddingVertical: 10,
522
+ gap: 6,
523
+ },
524
+ filterChip: {
525
+ flexDirection: 'row',
526
+ alignItems: 'center',
527
+ gap: 5,
528
+ paddingHorizontal: 12,
529
+ paddingVertical: 6,
530
+ borderRadius: 8,
531
+ backgroundColor: AppColors_1.AppColors.primaryLight,
532
+ borderWidth: 1,
533
+ borderColor: AppColors_1.AppColors.grayBorderSecondary,
534
+ },
535
+ filterChipActive: {
536
+ backgroundColor: AppColors_1.AppColors.purple,
537
+ borderColor: AppColors_1.AppColors.purple,
538
+ },
539
+ filterChipText: {
540
+ fontFamily: AppFonts_1.AppFonts.interMedium,
541
+ fontSize: 12,
542
+ color: AppColors_1.AppColors.grayText,
543
+ },
544
+ filterChipTextActive: {
545
+ fontFamily: AppFonts_1.AppFonts.interBold,
546
+ color: AppColors_1.AppColors.white,
547
+ },
548
+ listContent: {
549
+ paddingHorizontal: 16,
550
+ paddingTop: 4,
551
+ paddingBottom: 24,
552
+ gap: 10,
553
+ },
554
+ cardContainer: {
555
+ backgroundColor: AppColors_1.AppColors.primaryLight,
556
+ borderRadius: 12,
557
+ borderWidth: 1,
558
+ borderColor: AppColors_1.AppColors.grayBorderSecondary,
559
+ padding: 14,
560
+ shadowColor: AppColors_1.AppColors.shadowColorString,
561
+ shadowOffset: { width: 0, height: 1 },
562
+ shadowOpacity: 0.05,
563
+ shadowRadius: 3,
564
+ elevation: 2,
565
+ },
566
+ fatalCardBorder: {
567
+ borderColor: `${AppColors_1.AppColors.errorColor}44`,
568
+ },
569
+ cardHeader: {
570
+ flexDirection: 'row',
571
+ alignItems: 'center',
572
+ justifyContent: 'space-between',
573
+ },
574
+ badgeRow: {
575
+ flexDirection: 'row',
576
+ alignItems: 'center',
577
+ gap: 6,
578
+ flexShrink: 1,
579
+ },
580
+ sNoBadge: {
581
+ backgroundColor: AppColors_1.AppColors.grayBackground,
582
+ borderWidth: 1,
583
+ borderColor: AppColors_1.AppColors.grayBorderSecondary,
584
+ paddingHorizontal: 6,
585
+ paddingVertical: 3,
586
+ borderRadius: 5,
587
+ },
588
+ sNoBadgeText: {
589
+ fontFamily: AppFonts_1.AppFonts.interBold,
590
+ fontSize: 9.5,
591
+ color: AppColors_1.AppColors.grayText,
592
+ letterSpacing: 0.3,
593
+ },
594
+ fatalBadge: {
595
+ flexDirection: 'row',
596
+ alignItems: 'center',
597
+ gap: 4,
598
+ backgroundColor: AppColors_1.AppColors.errorColor,
599
+ paddingHorizontal: 6,
600
+ paddingVertical: 3,
601
+ borderRadius: 5,
602
+ },
603
+ fatalBadgeText: {
604
+ fontFamily: AppFonts_1.AppFonts.interBold,
605
+ fontSize: 9.5,
606
+ color: AppColors_1.AppColors.white,
607
+ letterSpacing: 0.5,
608
+ },
609
+ caughtBadge: {
610
+ backgroundColor: `${AppColors_1.AppColors.greenColor}1F`,
611
+ paddingHorizontal: 6,
612
+ paddingVertical: 3,
613
+ borderRadius: 5,
614
+ },
615
+ caughtBadgeText: {
616
+ fontFamily: AppFonts_1.AppFonts.interBold,
617
+ fontSize: 9.5,
618
+ color: AppColors_1.AppColors.greenColor,
619
+ letterSpacing: 0.5,
620
+ },
621
+ typeBadge: {
622
+ paddingHorizontal: 6,
623
+ paddingVertical: 3,
624
+ borderRadius: 5,
625
+ },
626
+ typeBadgeText: {
627
+ fontFamily: AppFonts_1.AppFonts.interBold,
628
+ fontSize: 9.5,
629
+ letterSpacing: 0.5,
630
+ },
631
+ timeRow: {
632
+ flexDirection: 'row',
633
+ alignItems: 'center',
634
+ gap: 4,
635
+ flexShrink: 1,
636
+ },
637
+ timestampText: {
638
+ fontFamily: AppFonts_1.AppFonts.interRegular,
639
+ fontSize: 11,
640
+ color: AppColors_1.AppColors.grayTextWeak,
641
+ },
642
+ relativeTimeText: {
643
+ fontFamily: AppFonts_1.AppFonts.interBold,
644
+ fontSize: 10,
645
+ color: AppColors_1.AppColors.grayTextWeak,
646
+ backgroundColor: AppColors_1.AppColors.grayBackground,
647
+ paddingHorizontal: 5,
648
+ paddingVertical: 1,
649
+ borderRadius: 4,
650
+ overflow: 'hidden',
651
+ },
652
+ errorMessageText: {
653
+ fontFamily: AppFonts_1.AppFonts.interBold,
654
+ fontSize: 13.5,
655
+ color: AppColors_1.AppColors.primaryBlack,
656
+ lineHeight: 18,
657
+ },
658
+ locationRow: {
659
+ flexDirection: 'row',
660
+ alignItems: 'center',
661
+ gap: 6,
662
+ marginTop: 2,
663
+ marginBottom: 6,
664
+ backgroundColor: AppColors_1.AppColors.purpleShade50,
665
+ paddingHorizontal: 8,
666
+ paddingVertical: 4,
667
+ borderRadius: 6,
668
+ },
669
+ locationText: {
670
+ fontFamily: AppFonts_1.AppFonts.interRegular,
671
+ fontSize: 11,
672
+ color: AppColors_1.AppColors.grayText,
673
+ flex: 1,
674
+ },
675
+ cardFooter: {
676
+ flexDirection: 'row',
677
+ alignItems: 'center',
678
+ justifyContent: 'space-between',
679
+ marginTop: 6,
680
+ paddingTop: 8,
681
+ borderTopWidth: 1,
682
+ borderTopColor: AppColors_1.AppColors.dividerColor,
683
+ },
684
+ metaPill: {
685
+ flexDirection: 'row',
686
+ alignItems: 'center',
687
+ gap: 4,
688
+ backgroundColor: AppColors_1.AppColors.purpleShade50,
689
+ paddingHorizontal: 6,
690
+ paddingVertical: 2,
691
+ borderRadius: 4,
692
+ },
693
+ metaPillText: {
694
+ fontFamily: AppFonts_1.AppFonts.interMedium,
695
+ fontSize: 10,
696
+ color: AppColors_1.AppColors.grayTextWeak,
697
+ },
698
+ emptyContainer: {
699
+ alignItems: 'center',
700
+ justifyContent: 'center',
701
+ padding: 32,
702
+ marginTop: 40,
703
+ },
704
+ emptyIconCircle: {
705
+ width: 72,
706
+ height: 72,
707
+ borderRadius: 36,
708
+ backgroundColor: `${AppColors_1.AppColors.greenColor}1F`,
709
+ alignItems: 'center',
710
+ justifyContent: 'center',
711
+ marginBottom: 16,
712
+ },
713
+ emptyTitle: {
714
+ fontFamily: AppFonts_1.AppFonts.interBold,
715
+ fontSize: 17,
716
+ color: AppColors_1.AppColors.primaryBlack,
717
+ marginBottom: 6,
718
+ },
719
+ emptySub: {
720
+ fontFamily: AppFonts_1.AppFonts.interRegular,
721
+ fontSize: 13,
722
+ color: AppColors_1.AppColors.grayText,
723
+ textAlign: 'center',
724
+ lineHeight: 18,
725
+ maxWidth: 280,
726
+ },
727
+ });