react-native-inapp-inspector 1.1.24 → 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 (114) 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/AnalyticsEventCard.js +94 -88
  6. package/dist/commonjs/components/ConsoleLogCard.js +49 -25
  7. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  8. package/dist/commonjs/components/ErrorBoundary.js +9 -145
  9. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  10. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
  11. package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
  12. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
  13. package/dist/commonjs/components/Inspector/BundleTab.js +826 -406
  14. package/dist/commonjs/components/Inspector/CrashDetail.d.ts +3 -0
  15. package/dist/commonjs/components/Inspector/CrashDetail.js +796 -0
  16. package/dist/commonjs/components/Inspector/CrashFilterModal.d.ts +20 -0
  17. package/dist/commonjs/components/Inspector/CrashFilterModal.js +597 -0
  18. package/dist/commonjs/components/Inspector/CrashTab.d.ts +3 -0
  19. package/dist/commonjs/components/Inspector/CrashTab.js +727 -0
  20. package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
  21. package/dist/commonjs/components/Inspector/InspectorHeader.js +65 -28
  22. package/dist/commonjs/components/Inspector/LogDetail.js +227 -114
  23. package/dist/commonjs/components/Inspector/MainScreen.js +13 -4
  24. package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
  25. package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
  26. package/dist/commonjs/components/Inspector/SettingsPanel.js +90 -1
  27. package/dist/commonjs/components/Inspector/TabBar.js +15 -3
  28. package/dist/commonjs/components/NetworkIcons.d.ts +25 -0
  29. package/dist/commonjs/components/NetworkIcons.js +188 -1
  30. package/dist/commonjs/components/SegmentedTabs.js +33 -23
  31. package/dist/commonjs/components/TreeNode.js +13 -12
  32. package/dist/commonjs/constants/version.d.ts +1 -1
  33. package/dist/commonjs/constants/version.js +1 -1
  34. package/dist/commonjs/customHooks/analyticsLogger.js +70 -44
  35. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +5 -1
  36. package/dist/commonjs/customHooks/bundleAnalyzer.js +497 -126
  37. package/dist/commonjs/customHooks/consoleLogger.d.ts +1 -0
  38. package/dist/commonjs/customHooks/consoleLogger.js +153 -51
  39. package/dist/commonjs/customHooks/crashHandler.d.ts +64 -0
  40. package/dist/commonjs/customHooks/crashHandler.js +681 -0
  41. package/dist/commonjs/customHooks/networkLogger.js +74 -49
  42. package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
  43. package/dist/commonjs/customHooks/performanceTracker.js +44 -3
  44. package/dist/commonjs/decorators/index.d.ts +49 -0
  45. package/dist/commonjs/decorators/index.js +142 -0
  46. package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
  47. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
  48. package/dist/commonjs/helpers/index.d.ts +7 -4
  49. package/dist/commonjs/helpers/index.js +102 -64
  50. package/dist/commonjs/i18n/locales/en.json +182 -4
  51. package/dist/commonjs/index.d.ts +6 -0
  52. package/dist/commonjs/index.js +310 -8
  53. package/dist/commonjs/styles/index.d.ts +4 -1
  54. package/dist/commonjs/styles/index.js +22 -19
  55. package/dist/commonjs/types/enums.d.ts +156 -9
  56. package/dist/commonjs/types/enums.js +139 -2
  57. package/dist/commonjs/types/interfaces.d.ts +70 -1
  58. package/dist/esm/components/AnalyticsEventCard.js +95 -89
  59. package/dist/esm/components/ConsoleLogCard.js +49 -25
  60. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  61. package/dist/esm/components/ErrorBoundary.js +10 -113
  62. package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  63. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
  64. package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
  65. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
  66. package/dist/esm/components/Inspector/BundleTab.js +827 -407
  67. package/dist/esm/components/Inspector/CrashDetail.d.ts +3 -0
  68. package/dist/esm/components/Inspector/CrashDetail.js +758 -0
  69. package/dist/esm/components/Inspector/CrashFilterModal.d.ts +20 -0
  70. package/dist/esm/components/Inspector/CrashFilterModal.js +557 -0
  71. package/dist/esm/components/Inspector/CrashTab.d.ts +3 -0
  72. package/dist/esm/components/Inspector/CrashTab.js +689 -0
  73. package/dist/esm/components/Inspector/InspectorContext.js +1 -0
  74. package/dist/esm/components/Inspector/InspectorHeader.js +66 -29
  75. package/dist/esm/components/Inspector/LogDetail.js +227 -114
  76. package/dist/esm/components/Inspector/MainScreen.js +14 -5
  77. package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
  78. package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
  79. package/dist/esm/components/Inspector/SettingsPanel.js +91 -2
  80. package/dist/esm/components/Inspector/TabBar.js +16 -4
  81. package/dist/esm/components/NetworkIcons.d.ts +25 -0
  82. package/dist/esm/components/NetworkIcons.js +161 -0
  83. package/dist/esm/components/SegmentedTabs.js +33 -23
  84. package/dist/esm/components/TreeNode.js +13 -12
  85. package/dist/esm/constants/version.d.ts +1 -1
  86. package/dist/esm/constants/version.js +1 -1
  87. package/dist/esm/customHooks/analyticsLogger.js +70 -44
  88. package/dist/esm/customHooks/bundleAnalyzer.d.ts +5 -1
  89. package/dist/esm/customHooks/bundleAnalyzer.js +493 -125
  90. package/dist/esm/customHooks/consoleLogger.d.ts +1 -0
  91. package/dist/esm/customHooks/consoleLogger.js +151 -50
  92. package/dist/esm/customHooks/crashHandler.d.ts +64 -0
  93. package/dist/esm/customHooks/crashHandler.js +659 -0
  94. package/dist/esm/customHooks/networkLogger.js +74 -49
  95. package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
  96. package/dist/esm/customHooks/performanceTracker.js +38 -1
  97. package/dist/esm/decorators/index.d.ts +49 -0
  98. package/dist/esm/decorators/index.js +137 -0
  99. package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
  100. package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
  101. package/dist/esm/helpers/index.d.ts +7 -4
  102. package/dist/esm/helpers/index.js +100 -63
  103. package/dist/esm/i18n/locales/en.json +182 -4
  104. package/dist/esm/index.d.ts +6 -0
  105. package/dist/esm/index.js +262 -8
  106. package/dist/esm/styles/index.d.ts +4 -1
  107. package/dist/esm/styles/index.js +22 -19
  108. package/dist/esm/types/enums.d.ts +156 -9
  109. package/dist/esm/types/enums.js +138 -3
  110. package/dist/esm/types/interfaces.d.ts +70 -1
  111. package/ios/NetworkInspectorModule.h +6 -0
  112. package/ios/NetworkInspectorModule.m +125 -0
  113. package/package.json +5 -2
  114. package/react-native-inapp-inspector.podspec +18 -0
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { CrashFilterType } from '../../types/enums';
3
+ export interface CrashFilters {
4
+ types: Set<CrashFilterType>;
5
+ timeWindow: 'all' | '15m' | '1h' | '24h' | '7d';
6
+ platform: 'all' | 'ios' | 'android';
7
+ engine: 'all' | 'hermes' | 'jsc';
8
+ sortBy: 'time_desc' | 'time_asc';
9
+ }
10
+ export declare const DEFAULT_CRASH_FILTERS: CrashFilters;
11
+ export declare const isCrashFiltersDefault: (f: CrashFilters) => boolean;
12
+ interface CrashFilterModalProps {
13
+ visible: boolean;
14
+ onClose: () => void;
15
+ filters: CrashFilters;
16
+ onApply: (filters: CrashFilters) => void;
17
+ searchQuery?: string;
18
+ }
19
+ declare const CrashFilterModal: React.FC<CrashFilterModalProps>;
20
+ export default CrashFilterModal;
@@ -0,0 +1,557 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { Modal, Pressable, ScrollView, StyleSheet, Text, TouchableOpacity, View, } from 'react-native';
3
+ import { useInspector } from './InspectorContext';
4
+ import { AppColors } from '../../styles/AppColors';
5
+ import { AppFonts } from '../../styles/AppFonts';
6
+ import { useTranslation } from '../../i18n';
7
+ import { FilterIcon, CloseWhite, CheckIcon, ClockIcon, HourglassIcon, SkullIcon, JsIcon, LayoutIcon, ChipIcon, AppleIcon, AndroidIcon, BoltIcon, } from '../NetworkIcons';
8
+ import TouchableScale from '../TouchableScale';
9
+ export const DEFAULT_CRASH_FILTERS = {
10
+ types: new Set(['all']),
11
+ timeWindow: 'all',
12
+ platform: 'all',
13
+ engine: 'all',
14
+ sortBy: 'time_desc',
15
+ };
16
+ export const isCrashFiltersDefault = (f) => (f.types.has('all') || f.types.size === 0) &&
17
+ f.timeWindow === 'all' &&
18
+ f.platform === 'all' &&
19
+ f.engine === 'all' &&
20
+ f.sortBy === 'time_desc';
21
+ const CrashFilterModal = ({ visible, onClose, filters, onApply, searchQuery = '', }) => {
22
+ const { t } = useTranslation();
23
+ const { crashRecords } = useInspector();
24
+ const [draft, setDraft] = useState(() => ({
25
+ ...filters,
26
+ types: new Set(filters.types),
27
+ }));
28
+ useEffect(() => {
29
+ if (visible) {
30
+ setDraft({
31
+ ...filters,
32
+ types: new Set(filters.types),
33
+ });
34
+ }
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps
36
+ }, [visible]);
37
+ const toggleType = (key) => {
38
+ setDraft(prev => {
39
+ const next = new Set(prev.types);
40
+ if (key === 'all') {
41
+ return { ...prev, types: new Set(['all']) };
42
+ }
43
+ next.delete('all');
44
+ if (next.has(key)) {
45
+ next.delete(key);
46
+ if (next.size === 0)
47
+ next.add('all');
48
+ }
49
+ else {
50
+ next.add(key);
51
+ }
52
+ return { ...prev, types: next };
53
+ });
54
+ };
55
+ const setTimeWindow = (tw) => {
56
+ setDraft(prev => ({ ...prev, timeWindow: tw }));
57
+ };
58
+ const setPlatform = (p) => {
59
+ setDraft(prev => ({ ...prev, platform: p }));
60
+ };
61
+ const setEngine = (e) => {
62
+ setDraft(prev => ({ ...prev, engine: e }));
63
+ };
64
+ const setSort = (sortBy) => {
65
+ setDraft(prev => ({ ...prev, sortBy }));
66
+ };
67
+ const handleResetDraft = () => {
68
+ setDraft({
69
+ ...DEFAULT_CRASH_FILTERS,
70
+ types: new Set(DEFAULT_CRASH_FILTERS.types),
71
+ });
72
+ };
73
+ const handleApply = () => {
74
+ onApply({
75
+ ...draft,
76
+ types: new Set(draft.types),
77
+ });
78
+ onClose();
79
+ };
80
+ const handleDiscard = () => {
81
+ setDraft({
82
+ ...filters,
83
+ types: new Set(filters.types),
84
+ });
85
+ onClose();
86
+ };
87
+ const draftMatchCount = useMemo(() => {
88
+ let crashes = crashRecords;
89
+ if (searchQuery.trim().length > 0) {
90
+ const q = searchQuery.toLowerCase();
91
+ crashes = crashes.filter(c => (c.message || '').toLowerCase().includes(q) ||
92
+ (c.name || '').toLowerCase().includes(q) ||
93
+ (c.stack || '').toLowerCase().includes(q));
94
+ }
95
+ if (draft.types.size > 0 && !draft.types.has('all')) {
96
+ crashes = crashes.filter(c => {
97
+ if (draft.types.has('fatal') && c.isFatal)
98
+ return true;
99
+ if (draft.types.has('js') && c.type === 'js')
100
+ return true;
101
+ if (draft.types.has('promise') && c.type === 'promise')
102
+ return true;
103
+ if (draft.types.has('render') && c.type === 'render')
104
+ return true;
105
+ if (draft.types.has('native') && c.type === 'native')
106
+ return true;
107
+ return false;
108
+ });
109
+ }
110
+ if (draft.timeWindow !== 'all') {
111
+ const now = Date.now();
112
+ const cutoff = draft.timeWindow === '15m'
113
+ ? now - 15 * 60 * 1000
114
+ : draft.timeWindow === '1h'
115
+ ? now - 60 * 60 * 1000
116
+ : draft.timeWindow === '24h'
117
+ ? now - 24 * 60 * 60 * 1000
118
+ : now - 7 * 24 * 60 * 60 * 1000;
119
+ crashes = crashes.filter(c => c.timestamp >= cutoff);
120
+ }
121
+ if (draft.platform !== 'all') {
122
+ crashes = crashes.filter(c => (c.deviceInfo?.platform || '').toLowerCase() === draft.platform);
123
+ }
124
+ if (draft.engine !== 'all') {
125
+ crashes = crashes.filter(c => {
126
+ const isHermes = c.deviceInfo?.isHermes;
127
+ if (draft.engine === 'hermes')
128
+ return isHermes === true;
129
+ return isHermes === false || isHermes === undefined;
130
+ });
131
+ }
132
+ return crashes.length;
133
+ }, [crashRecords, searchQuery, draft]);
134
+ const isAllTypes = draft.types.has('all') || draft.types.size === 0;
135
+ const typeChips = [
136
+ { key: 'all', label: t('crash.filterTypeAll'), Icon: FilterIcon, color: AppColors.grayTextStrong },
137
+ { key: 'fatal', label: t('crash.filterFatal'), Icon: SkullIcon, color: AppColors.errorColor },
138
+ { key: 'js', label: t('crash.filterJsError'), Icon: JsIcon, color: AppColors.sky600 },
139
+ { key: 'promise', label: t('crash.filterPromise'), Icon: HourglassIcon, color: AppColors.darkOrange },
140
+ { key: 'render', label: t('crash.filterRender'), Icon: LayoutIcon, color: AppColors.purple },
141
+ { key: 'native', label: t('crash.filterNative'), Icon: ChipIcon, color: AppColors.amber700 },
142
+ ];
143
+ return (<Modal visible={visible} transparent animationType="fade" onRequestClose={handleDiscard}>
144
+ <View style={styles.backdrop}>
145
+ <Pressable style={StyleSheet.absoluteFill} onPress={handleDiscard}/>
146
+
147
+ <View style={styles.modalCard}>
148
+ <View style={styles.header}>
149
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
150
+ <View style={styles.headerIconBox}>
151
+ <FilterIcon color={AppColors.brandPurple} size={15}/>
152
+ </View>
153
+ <View>
154
+ <Text style={styles.headerTitle}>{t('crash.filterTitle')}</Text>
155
+ <Text style={styles.headerSubtitle}>
156
+ {t('crash.filterSubtitle')}
157
+ </Text>
158
+ </View>
159
+ </View>
160
+
161
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
162
+ <TouchableOpacity onPress={handleResetDraft} hitSlop={8} style={styles.resetBtn}>
163
+ <Text style={styles.resetBtnText}>{t('crash.filterReset')}</Text>
164
+ </TouchableOpacity>
165
+
166
+ <TouchableOpacity onPress={handleDiscard} hitSlop={10} style={styles.closeBtn}>
167
+ <CloseWhite />
168
+ </TouchableOpacity>
169
+ </View>
170
+ </View>
171
+
172
+ <ScrollView style={{ maxHeight: 460 }} contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={true}>
173
+ <View style={styles.section}>
174
+ <View style={styles.sectionHeaderRow}>
175
+ <SkullIcon color={AppColors.grayTextWeak} size={12}/>
176
+ <Text style={styles.sectionTitle}>
177
+ {t('crash.filterTypeSection')}
178
+ </Text>
179
+ </View>
180
+ <View style={styles.chipsWrap}>
181
+ {typeChips.map(chip => {
182
+ const isSelected = chip.key === 'all' ? isAllTypes : draft.types.has(chip.key);
183
+ return (<TouchableScale key={chip.key} onPress={() => toggleType(chip.key)} style={[
184
+ styles.filterPill,
185
+ isSelected && styles.filterPillActive,
186
+ ]}>
187
+ {chip.key === 'all' && isSelected && (<CheckIcon color={AppColors.white} size={12}/>)}
188
+ <chip.Icon size={12} color={isSelected ? AppColors.white : chip.color}/>
189
+ <Text style={[
190
+ styles.filterPillText,
191
+ isSelected && styles.filterPillTextActive,
192
+ ]}>
193
+ {chip.label}
194
+ </Text>
195
+ </TouchableScale>);
196
+ })}
197
+ </View>
198
+ </View>
199
+
200
+ <View style={styles.section}>
201
+ <View style={styles.sectionHeaderRow}>
202
+ <ClockIcon color={AppColors.grayTextWeak} size={12}/>
203
+ <Text style={styles.sectionTitle}>
204
+ {t('crash.filterTimeSection')}
205
+ </Text>
206
+ </View>
207
+ <View style={styles.chipsWrap}>
208
+ {[
209
+ { key: 'all', label: t('crash.filterTimeAll') },
210
+ { key: '15m', label: t('crash.filterTime15m') },
211
+ { key: '1h', label: t('crash.filterTime1h') },
212
+ { key: '24h', label: t('crash.filterTime24h') },
213
+ { key: '7d', label: t('crash.filterTime7d') },
214
+ ].map(item => (<TouchableScale key={item.key} onPress={() => setTimeWindow(item.key)} style={[
215
+ styles.filterPill,
216
+ draft.timeWindow === item.key && styles.filterPillActive,
217
+ ]}>
218
+ <Text style={[
219
+ styles.filterPillText,
220
+ draft.timeWindow === item.key &&
221
+ styles.filterPillTextActive,
222
+ ]}>
223
+ {item.label}
224
+ </Text>
225
+ </TouchableScale>))}
226
+ </View>
227
+ </View>
228
+
229
+ <View style={styles.section}>
230
+ <View style={styles.sectionHeaderRow}>
231
+ <AppleIcon color={AppColors.grayTextWeak} size={12}/>
232
+ <Text style={styles.sectionTitle}>
233
+ {t('crash.filterPlatformSection')}
234
+ </Text>
235
+ </View>
236
+ <View style={styles.chipsWrap}>
237
+ <TouchableScale onPress={() => setPlatform('all')} style={[
238
+ styles.filterPill,
239
+ draft.platform === 'all' && styles.filterPillActive,
240
+ ]}>
241
+ <Text style={[
242
+ styles.filterPillText,
243
+ draft.platform === 'all' && styles.filterPillTextActive,
244
+ ]}>
245
+ {t('crash.filterPlatformAll')}
246
+ </Text>
247
+ </TouchableScale>
248
+
249
+ <TouchableScale onPress={() => setPlatform('ios')} style={[
250
+ styles.filterPill,
251
+ draft.platform === 'ios' && styles.filterPillActive,
252
+ ]}>
253
+ <AppleIcon size={12} color={draft.platform === 'ios'
254
+ ? AppColors.white
255
+ : AppColors.grayTextStrong}/>
256
+ <Text style={[
257
+ styles.filterPillText,
258
+ draft.platform === 'ios' && styles.filterPillTextActive,
259
+ ]}>
260
+ {t('crash.filterPlatformIos')}
261
+ </Text>
262
+ </TouchableScale>
263
+
264
+ <TouchableScale onPress={() => setPlatform('android')} style={[
265
+ styles.filterPill,
266
+ draft.platform === 'android' && styles.filterPillActive,
267
+ ]}>
268
+ <AndroidIcon size={12} color={draft.platform === 'android'
269
+ ? AppColors.white
270
+ : AppColors.greenColor}/>
271
+ <Text style={[
272
+ styles.filterPillText,
273
+ draft.platform === 'android' && styles.filterPillTextActive,
274
+ ]}>
275
+ {t('crash.filterPlatformAndroid')}
276
+ </Text>
277
+ </TouchableScale>
278
+ </View>
279
+ </View>
280
+
281
+ <View style={styles.section}>
282
+ <View style={styles.sectionHeaderRow}>
283
+ <BoltIcon color={AppColors.grayTextWeak} size={12}/>
284
+ <Text style={styles.sectionTitle}>
285
+ {t('crash.filterEngineSection')}
286
+ </Text>
287
+ </View>
288
+ <View style={styles.chipsWrap}>
289
+ <TouchableScale onPress={() => setEngine('all')} style={[
290
+ styles.filterPill,
291
+ draft.engine === 'all' && styles.filterPillActive,
292
+ ]}>
293
+ <Text style={[
294
+ styles.filterPillText,
295
+ draft.engine === 'all' && styles.filterPillTextActive,
296
+ ]}>
297
+ {t('crash.filterEngineAll')}
298
+ </Text>
299
+ </TouchableScale>
300
+
301
+ <TouchableScale onPress={() => setEngine('hermes')} style={[
302
+ styles.filterPill,
303
+ draft.engine === 'hermes' && styles.filterPillActive,
304
+ ]}>
305
+ <BoltIcon size={12} color={draft.engine === 'hermes'
306
+ ? AppColors.white
307
+ : AppColors.brandPurple}/>
308
+ <Text style={[
309
+ styles.filterPillText,
310
+ draft.engine === 'hermes' && styles.filterPillTextActive,
311
+ ]}>
312
+ {t('crash.filterEngineHermes')}
313
+ </Text>
314
+ </TouchableScale>
315
+
316
+ <TouchableScale onPress={() => setEngine('jsc')} style={[
317
+ styles.filterPill,
318
+ draft.engine === 'jsc' && styles.filterPillActive,
319
+ ]}>
320
+ <Text style={[
321
+ styles.filterPillText,
322
+ draft.engine === 'jsc' && styles.filterPillTextActive,
323
+ ]}>
324
+ {t('crash.jsc')}
325
+ </Text>
326
+ </TouchableScale>
327
+ </View>
328
+ </View>
329
+
330
+ <View style={styles.section}>
331
+ <View style={styles.sectionHeaderRow}>
332
+ <ClockIcon color={AppColors.grayTextWeak} size={12}/>
333
+ <Text style={styles.sectionTitle}>
334
+ {t('crash.filterSortSection')}
335
+ </Text>
336
+ </View>
337
+ <View style={styles.chipsWrap}>
338
+ <TouchableScale onPress={() => setSort('time_desc')} style={[
339
+ styles.filterPill,
340
+ draft.sortBy === 'time_desc' && styles.filterPillActive,
341
+ ]}>
342
+ <ClockIcon size={12} color={draft.sortBy === 'time_desc'
343
+ ? AppColors.white
344
+ : AppColors.grayText}/>
345
+ <Text style={[
346
+ styles.filterPillText,
347
+ draft.sortBy === 'time_desc' &&
348
+ styles.filterPillTextActive,
349
+ ]}>
350
+ {t('crash.filterSortNewest')}
351
+ </Text>
352
+ </TouchableScale>
353
+
354
+ <TouchableScale onPress={() => setSort('time_asc')} style={[
355
+ styles.filterPill,
356
+ draft.sortBy === 'time_asc' && styles.filterPillActive,
357
+ ]}>
358
+ <HourglassIcon size={12} color={draft.sortBy === 'time_asc'
359
+ ? AppColors.white
360
+ : AppColors.grayText}/>
361
+ <Text style={[
362
+ styles.filterPillText,
363
+ draft.sortBy === 'time_asc' &&
364
+ styles.filterPillTextActive,
365
+ ]}>
366
+ {t('crash.filterSortOldest')}
367
+ </Text>
368
+ </TouchableScale>
369
+ </View>
370
+ </View>
371
+ </ScrollView>
372
+
373
+ <View style={styles.footer}>
374
+ <TouchableOpacity onPress={handleDiscard} style={styles.discardBtn} activeOpacity={0.8}>
375
+ <Text style={styles.discardBtnText}>
376
+ {t('crash.filterDiscard')}
377
+ </Text>
378
+ </TouchableOpacity>
379
+
380
+ <TouchableOpacity onPress={handleApply} style={styles.applyBtn} activeOpacity={0.85}>
381
+ <Text style={styles.applyBtnText}>
382
+ {t('crash.filterApply', { count: draftMatchCount })}
383
+ </Text>
384
+ </TouchableOpacity>
385
+ </View>
386
+ </View>
387
+ </View>
388
+ </Modal>);
389
+ };
390
+ const styles = StyleSheet.create({
391
+ backdrop: {
392
+ flex: 1,
393
+ backgroundColor: `${AppColors.primaryBlack}85`,
394
+ justifyContent: 'center',
395
+ alignItems: 'center',
396
+ padding: 16,
397
+ },
398
+ modalCard: {
399
+ width: '100%',
400
+ maxWidth: 440,
401
+ backgroundColor: AppColors.white,
402
+ borderRadius: 14,
403
+ borderWidth: 1,
404
+ borderColor: AppColors.dividerColor,
405
+ shadowColor: AppColors.black,
406
+ shadowOpacity: 0.18,
407
+ shadowRadius: 12,
408
+ shadowOffset: { width: 0, height: 4 },
409
+ elevation: 8,
410
+ overflow: 'hidden',
411
+ },
412
+ header: {
413
+ flexDirection: 'row',
414
+ alignItems: 'center',
415
+ justifyContent: 'space-between',
416
+ paddingHorizontal: 14,
417
+ paddingVertical: 12,
418
+ borderBottomWidth: 1,
419
+ borderBottomColor: AppColors.dividerColor,
420
+ backgroundColor: `${AppColors.brandPurple}08`,
421
+ },
422
+ headerIconBox: {
423
+ width: 28,
424
+ height: 28,
425
+ borderRadius: 7,
426
+ backgroundColor: `${AppColors.brandPurple}18`,
427
+ alignItems: 'center',
428
+ justifyContent: 'center',
429
+ },
430
+ headerTitle: {
431
+ fontFamily: AppFonts.interBold,
432
+ fontSize: 13.5,
433
+ color: AppColors.primaryBlack,
434
+ },
435
+ headerSubtitle: {
436
+ fontFamily: AppFonts.interMedium,
437
+ fontSize: 10,
438
+ color: AppColors.grayTextWeak,
439
+ },
440
+ resetBtn: {
441
+ paddingHorizontal: 8,
442
+ paddingVertical: 4,
443
+ borderRadius: 5,
444
+ backgroundColor: `${AppColors.darkOrange}14`,
445
+ borderWidth: 1,
446
+ borderColor: `${AppColors.darkOrange}30`,
447
+ },
448
+ resetBtnText: {
449
+ fontFamily: AppFonts.interBold,
450
+ fontSize: 10,
451
+ color: AppColors.darkOrange,
452
+ },
453
+ closeBtn: {
454
+ width: 24,
455
+ height: 24,
456
+ borderRadius: 12,
457
+ backgroundColor: `${AppColors.primaryBlack}18`,
458
+ alignItems: 'center',
459
+ justifyContent: 'center',
460
+ },
461
+ scrollContent: {
462
+ padding: 14,
463
+ gap: 14,
464
+ },
465
+ section: {
466
+ gap: 7,
467
+ },
468
+ sectionHeaderRow: {
469
+ flexDirection: 'row',
470
+ alignItems: 'center',
471
+ gap: 5,
472
+ },
473
+ sectionTitle: {
474
+ fontFamily: AppFonts.interBold,
475
+ fontSize: 9.5,
476
+ color: AppColors.grayTextWeak,
477
+ letterSpacing: 0.5,
478
+ },
479
+ chipsWrap: {
480
+ flexDirection: 'row',
481
+ flexWrap: 'wrap',
482
+ gap: 6.5,
483
+ },
484
+ filterPill: {
485
+ flexDirection: 'row',
486
+ alignItems: 'center',
487
+ gap: 5,
488
+ paddingHorizontal: 9,
489
+ paddingVertical: 5.5,
490
+ borderRadius: 7,
491
+ backgroundColor: AppColors.grayBackground,
492
+ borderWidth: 1,
493
+ borderColor: AppColors.dividerColor,
494
+ },
495
+ filterPillActive: {
496
+ backgroundColor: AppColors.brandPurple,
497
+ borderColor: AppColors.brandPurple,
498
+ shadowColor: AppColors.brandPurple,
499
+ shadowOffset: { width: 0, height: 1 },
500
+ shadowOpacity: 0.25,
501
+ shadowRadius: 3,
502
+ elevation: 2,
503
+ },
504
+ filterPillText: {
505
+ fontFamily: AppFonts.interMedium,
506
+ fontSize: 11,
507
+ color: AppColors.primaryBlack,
508
+ },
509
+ filterPillTextActive: {
510
+ color: AppColors.white,
511
+ fontFamily: AppFonts.interBold,
512
+ },
513
+ footer: {
514
+ flexDirection: 'row',
515
+ alignItems: 'center',
516
+ gap: 10,
517
+ paddingHorizontal: 14,
518
+ paddingVertical: 12,
519
+ borderTopWidth: 1,
520
+ borderTopColor: AppColors.dividerColor,
521
+ backgroundColor: AppColors.grayBackground,
522
+ },
523
+ discardBtn: {
524
+ flex: 1,
525
+ paddingVertical: 9.5,
526
+ borderRadius: 8,
527
+ borderWidth: 1,
528
+ borderColor: AppColors.dividerColor,
529
+ backgroundColor: AppColors.white,
530
+ alignItems: 'center',
531
+ justifyContent: 'center',
532
+ },
533
+ discardBtnText: {
534
+ fontFamily: AppFonts.interBold,
535
+ fontSize: 12.5,
536
+ color: AppColors.grayTextStrong,
537
+ },
538
+ applyBtn: {
539
+ flex: 2,
540
+ backgroundColor: AppColors.brandPurple,
541
+ paddingVertical: 9.5,
542
+ borderRadius: 8,
543
+ alignItems: 'center',
544
+ justifyContent: 'center',
545
+ shadowColor: AppColors.brandPurple,
546
+ shadowOffset: { width: 0, height: 1 },
547
+ shadowOpacity: 0.25,
548
+ shadowRadius: 3,
549
+ elevation: 2,
550
+ },
551
+ applyBtnText: {
552
+ fontFamily: AppFonts.interBold,
553
+ fontSize: 12.5,
554
+ color: AppColors.white,
555
+ },
556
+ });
557
+ export default CrashFilterModal;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const CrashTab: React.MemoExoticComponent<() => React.JSX.Element>;
3
+ export default CrashTab;