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,964 @@
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 { getEventCategory } from '../../helpers';
7
+ import { FilterIcon, CloseWhite, CheckIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, MoneyIcon, ClockIcon, HourglassIcon, FlameIcon, PinIcon, RepeatIcon, TargetGoalIcon, UserIcon, UserCheckIcon, PackageBoxIcon, CodeBracketsIcon, } from '../NetworkIcons';
8
+ import TouchableScale from '../TouchableScale';
9
+ const DEFAULT_FILTERS = {
10
+ categories: new Set(['all']),
11
+ screens: new Set(),
12
+ sources: new Set(['all']),
13
+ userTypes: new Set(['all']),
14
+ timeWindow: 'all',
15
+ payloadComplexity: 'all',
16
+ hasRevenue: false,
17
+ hasItems: false,
18
+ hasUserProps: false,
19
+ hasParams: false,
20
+ onlyDuplicates: false,
21
+ onlyConversions: false,
22
+ sortBy: 'time_desc',
23
+ };
24
+ const AnalyticsFilterModal = ({ visible, onClose, }) => {
25
+ const { analyticsEvents, analyticsSearch, analyticsFilters, setAnalyticsFilters, } = useInspector();
26
+ // Staged Draft State: applied ONLY on tapping "Apply Filters"
27
+ const [draft, setDraft] = useState(() => ({
28
+ ...analyticsFilters,
29
+ categories: new Set(analyticsFilters.categories),
30
+ screens: new Set(analyticsFilters.screens),
31
+ sources: new Set(analyticsFilters.sources || ['all']),
32
+ userTypes: new Set(analyticsFilters.userTypes || ['all']),
33
+ }));
34
+ // Re-sync draft whenever modal opens
35
+ useEffect(() => {
36
+ if (visible) {
37
+ setDraft({
38
+ ...analyticsFilters,
39
+ categories: new Set(analyticsFilters.categories),
40
+ screens: new Set(analyticsFilters.screens),
41
+ sources: new Set(analyticsFilters.sources || ['all']),
42
+ userTypes: new Set(analyticsFilters.userTypes || ['all']),
43
+ });
44
+ }
45
+ }, [visible, analyticsFilters]);
46
+ // Extract unique screen names from logged events
47
+ const uniqueScreens = useMemo(() => {
48
+ const screens = new Set();
49
+ for (const e of analyticsEvents) {
50
+ const scr = e.screenName ||
51
+ e.params?.firebase_screen ||
52
+ e.params?.screen_name ||
53
+ e.params?.firebase_screen_class ||
54
+ e.screenClass;
55
+ if (scr && typeof scr === 'string' && scr.trim() !== '') {
56
+ screens.add(scr.trim());
57
+ }
58
+ }
59
+ return Array.from(screens);
60
+ }, [analyticsEvents]);
61
+ // Toggle Category in Draft
62
+ const toggleCategory = (cat) => {
63
+ setDraft(prev => {
64
+ const next = new Set(prev.categories);
65
+ if (cat === 'all') {
66
+ return { ...prev, categories: new Set(['all']) };
67
+ }
68
+ next.delete('all');
69
+ if (next.has(cat)) {
70
+ next.delete(cat);
71
+ if (next.size === 0)
72
+ next.add('all');
73
+ }
74
+ else {
75
+ next.add(cat);
76
+ }
77
+ return { ...prev, categories: next };
78
+ });
79
+ };
80
+ // Toggle Screen in Draft
81
+ const toggleScreen = (scr) => {
82
+ setDraft(prev => {
83
+ const next = new Set(prev.screens);
84
+ if (next.has(scr)) {
85
+ next.delete(scr);
86
+ }
87
+ else {
88
+ next.add(scr);
89
+ }
90
+ return { ...prev, screens: next };
91
+ });
92
+ };
93
+ // Toggle Source in Draft
94
+ const toggleSource = (src) => {
95
+ setDraft(prev => {
96
+ const next = new Set(prev.sources);
97
+ if (src === 'all') {
98
+ return { ...prev, sources: new Set(['all']) };
99
+ }
100
+ next.delete('all');
101
+ if (next.has(src)) {
102
+ next.delete(src);
103
+ if (next.size === 0)
104
+ next.add('all');
105
+ }
106
+ else {
107
+ next.add(src);
108
+ }
109
+ return { ...prev, sources: next };
110
+ });
111
+ };
112
+ // Toggle User Type in Draft
113
+ const toggleUserType = (ut) => {
114
+ setDraft(prev => {
115
+ const next = new Set(prev.userTypes);
116
+ if (ut === 'all') {
117
+ return { ...prev, userTypes: new Set(['all']) };
118
+ }
119
+ next.delete('all');
120
+ if (next.has(ut)) {
121
+ next.delete(ut);
122
+ if (next.size === 0)
123
+ next.add('all');
124
+ }
125
+ else {
126
+ next.add(ut);
127
+ }
128
+ return { ...prev, userTypes: next };
129
+ });
130
+ };
131
+ // Toggle Boolean Flag in Draft
132
+ const toggleFlag = (key) => {
133
+ setDraft(prev => ({
134
+ ...prev,
135
+ [key]: !prev[key],
136
+ }));
137
+ };
138
+ // Set Time Window in Draft
139
+ const setTimeWindow = (tw) => {
140
+ setDraft(prev => ({ ...prev, timeWindow: tw }));
141
+ };
142
+ // Set Payload Complexity in Draft
143
+ const setPayloadComplexity = (pc) => {
144
+ setDraft(prev => ({ ...prev, payloadComplexity: pc }));
145
+ };
146
+ // Set Sort in Draft
147
+ const setSort = (sortBy) => {
148
+ setDraft(prev => ({ ...prev, sortBy }));
149
+ };
150
+ // Reset Draft to default
151
+ const handleResetDraft = () => {
152
+ setDraft({
153
+ categories: new Set(['all']),
154
+ screens: new Set(),
155
+ sources: new Set(['all']),
156
+ userTypes: new Set(['all']),
157
+ timeWindow: 'all',
158
+ payloadComplexity: 'all',
159
+ hasRevenue: false,
160
+ hasItems: false,
161
+ hasUserProps: false,
162
+ hasParams: false,
163
+ onlyDuplicates: false,
164
+ onlyConversions: false,
165
+ sortBy: 'time_desc',
166
+ });
167
+ };
168
+ // Commit Draft to actual filters
169
+ const handleApply = () => {
170
+ setAnalyticsFilters({
171
+ ...draft,
172
+ categories: new Set(draft.categories),
173
+ screens: new Set(draft.screens),
174
+ sources: new Set(draft.sources),
175
+ userTypes: new Set(draft.userTypes),
176
+ });
177
+ onClose();
178
+ };
179
+ // Discard changes & close
180
+ const handleDiscard = () => {
181
+ setDraft({
182
+ ...analyticsFilters,
183
+ categories: new Set(analyticsFilters.categories),
184
+ screens: new Set(analyticsFilters.screens),
185
+ sources: new Set(analyticsFilters.sources || ['all']),
186
+ userTypes: new Set(analyticsFilters.userTypes || ['all']),
187
+ });
188
+ onClose();
189
+ };
190
+ // Calculate matching count for Draft in real-time
191
+ const draftMatchCount = useMemo(() => {
192
+ let events = analyticsEvents;
193
+ if (analyticsSearch) {
194
+ const s = analyticsSearch.toLowerCase();
195
+ events = events.filter(e => e.name.toLowerCase().includes(s) ||
196
+ JSON.stringify(e.params || {}).toLowerCase().includes(s) ||
197
+ (e.screenName ?? '').toLowerCase().includes(s) ||
198
+ (e.pageTitle ?? '').toLowerCase().includes(s));
199
+ }
200
+ if (draft.categories.size > 0 && !draft.categories.has('all')) {
201
+ events = events.filter(e => {
202
+ const cat = getEventCategory(e.name);
203
+ return draft.categories.has(cat);
204
+ });
205
+ }
206
+ if (draft.timeWindow !== 'all') {
207
+ const now = Date.now();
208
+ const cutoff = draft.timeWindow === '1m'
209
+ ? now - 60 * 1000
210
+ : draft.timeWindow === '5m'
211
+ ? now - 5 * 60 * 1000
212
+ : draft.timeWindow === '15m'
213
+ ? now - 15 * 60 * 1000
214
+ : now - 60 * 60 * 1000;
215
+ events = events.filter(e => e.timestamp >= cutoff);
216
+ }
217
+ if (draft.sources.size > 0 && !draft.sources.has('all')) {
218
+ events = events.filter(e => draft.sources.has(e.source || 'manual'));
219
+ }
220
+ if (draft.userTypes.size > 0 && !draft.userTypes.has('all')) {
221
+ events = events.filter(e => {
222
+ const isIdentified = Boolean(e.userId && e.userId.trim() !== '');
223
+ if (draft.userTypes.has('identified') && isIdentified)
224
+ return true;
225
+ if (draft.userTypes.has('anonymous') && !isIdentified)
226
+ return true;
227
+ return false;
228
+ });
229
+ }
230
+ if (draft.payloadComplexity !== 'all') {
231
+ events = events.filter(e => {
232
+ const count = e.params ? Object.keys(e.params).length : 0;
233
+ if (draft.payloadComplexity === 'none')
234
+ return count === 0;
235
+ if (draft.payloadComplexity === 'simple')
236
+ return count >= 1 && count <= 5;
237
+ if (draft.payloadComplexity === 'heavy')
238
+ return count > 5;
239
+ return true;
240
+ });
241
+ }
242
+ if (draft.onlyConversions) {
243
+ const CONVERSION_PATTERNS = [
244
+ 'purchase',
245
+ 'item_purchase',
246
+ 'ecommerce_purchase',
247
+ 'sign_up',
248
+ 'login',
249
+ 'lead',
250
+ 'generate_lead',
251
+ 'tutorial_complete',
252
+ 'add_payment_info',
253
+ 'begin_checkout',
254
+ ];
255
+ events = events.filter(e => CONVERSION_PATTERNS.some(pat => e.name.toLowerCase().includes(pat)));
256
+ }
257
+ if (draft.screens.size > 0) {
258
+ events = events.filter(e => {
259
+ const scr = e.screenName ||
260
+ e.params?.firebase_screen ||
261
+ e.params?.screen_name ||
262
+ e.params?.firebase_screen_class ||
263
+ e.screenClass ||
264
+ '';
265
+ return draft.screens.has(scr);
266
+ });
267
+ }
268
+ if (draft.hasRevenue) {
269
+ events = events.filter(e => {
270
+ const val = e.params?.value ?? e.params?.price;
271
+ return val !== undefined && val !== null && Number(val) > 0;
272
+ });
273
+ }
274
+ if (draft.hasItems) {
275
+ events = events.filter(e => Array.isArray(e.params?.items) && e.params.items.length > 0);
276
+ }
277
+ if (draft.hasUserProps) {
278
+ events = events.filter(e => e.userProperties && Object.keys(e.userProperties).length > 0);
279
+ }
280
+ if (draft.hasParams) {
281
+ events = events.filter(e => e.params && Object.keys(e.params).length > 0);
282
+ }
283
+ return events.length;
284
+ }, [analyticsEvents, analyticsSearch, draft]);
285
+ const isAllCategories = draft.categories.has('all') || draft.categories.size === 0;
286
+ const isAllSources = draft.sources.has('all') || draft.sources.size === 0;
287
+ const isAllUserTypes = draft.userTypes.has('all') || draft.userTypes.size === 0;
288
+ return (<Modal visible={visible} transparent animationType="fade" onRequestClose={handleDiscard}>
289
+ <View style={styles.backdrop}>
290
+ <Pressable style={StyleSheet.absoluteFill} onPress={handleDiscard}/>
291
+
292
+ <View style={styles.modalCard}>
293
+ {/* ─── Header ──────────────────────────────────────────────────────── */}
294
+ <View style={styles.header}>
295
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
296
+ <View style={styles.headerIconBox}>
297
+ <FilterIcon color={AppColors.brandPurple} size={15}/>
298
+ </View>
299
+ <View>
300
+ <Text style={styles.headerTitle}>Analytics Filters</Text>
301
+ <Text style={styles.headerSubtitle}>
302
+ GA4 Telemetry Dimensions & Metrics
303
+ </Text>
304
+ </View>
305
+ </View>
306
+
307
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
308
+ <TouchableOpacity onPress={handleResetDraft} hitSlop={8} style={styles.resetBtn}>
309
+ <Text style={styles.resetBtnText}>Reset All</Text>
310
+ </TouchableOpacity>
311
+
312
+ <TouchableOpacity onPress={handleDiscard} hitSlop={10} style={styles.closeBtn}>
313
+ <CloseWhite />
314
+ </TouchableOpacity>
315
+ </View>
316
+ </View>
317
+
318
+ {/* ─── Scrollable Filter Options ──────────────────────────────────── */}
319
+ <ScrollView style={{ maxHeight: 460 }} contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={true}>
320
+ {/* 1. Time Horizon Window */}
321
+ <View style={styles.section}>
322
+ <View style={styles.sectionHeaderRow}>
323
+ <ClockIcon color={AppColors.grayTextWeak} size={12}/>
324
+ <Text style={styles.sectionTitle}>TIME HORIZON</Text>
325
+ </View>
326
+ <View style={styles.chipsWrap}>
327
+ {[
328
+ { key: 'all', label: 'All Time' },
329
+ { key: '1m', label: 'Last 1 min' },
330
+ { key: '5m', label: 'Last 5 mins' },
331
+ { key: '15m', label: 'Last 15 mins' },
332
+ { key: '1h', label: 'Last 1 hour' },
333
+ ].map(item => (<TouchableScale key={item.key} onPress={() => setTimeWindow(item.key)} style={[
334
+ styles.filterPill,
335
+ draft.timeWindow === item.key &&
336
+ styles.filterPillActive,
337
+ ]}>
338
+ <Text style={[
339
+ styles.filterPillText,
340
+ draft.timeWindow === item.key &&
341
+ styles.filterPillTextActive,
342
+ ]}>
343
+ {item.label}
344
+ </Text>
345
+ </TouchableScale>))}
346
+ </View>
347
+ </View>
348
+
349
+ {/* 2. Category Dimensions */}
350
+ <View style={styles.section}>
351
+ <View style={styles.sectionHeaderRow}>
352
+ <TargetGoalIcon color={AppColors.grayTextWeak} size={12}/>
353
+ <Text style={styles.sectionTitle}>EVENT CATEGORIES</Text>
354
+ </View>
355
+ <View style={styles.chipsWrap}>
356
+ <TouchableScale onPress={() => toggleCategory('all')} style={[
357
+ styles.filterPill,
358
+ isAllCategories && styles.filterPillActive,
359
+ ]}>
360
+ {isAllCategories && (<CheckIcon color={AppColors.white} size={12}/>)}
361
+ <Text style={[
362
+ styles.filterPillText,
363
+ isAllCategories && styles.filterPillTextActive,
364
+ ]}>
365
+ All Categories
366
+ </Text>
367
+ </TouchableScale>
368
+
369
+ <TouchableScale onPress={() => toggleCategory('ecommerce')} style={[
370
+ styles.filterPill,
371
+ draft.categories.has('ecommerce') &&
372
+ styles.filterPillActive,
373
+ ]}>
374
+ <CartIcon color={draft.categories.has('ecommerce')
375
+ ? AppColors.white
376
+ : AppColors.amber700} size={13}/>
377
+ <Text style={[
378
+ styles.filterPillText,
379
+ draft.categories.has('ecommerce') &&
380
+ styles.filterPillTextActive,
381
+ ]}>
382
+ Ecommerce
383
+ </Text>
384
+ </TouchableScale>
385
+
386
+ <TouchableScale onPress={() => toggleCategory('page_view')} style={[
387
+ styles.filterPill,
388
+ draft.categories.has('page_view') &&
389
+ styles.filterPillActive,
390
+ ]}>
391
+ <GlobeIcon color={draft.categories.has('page_view')
392
+ ? AppColors.white
393
+ : AppColors.sky600} size={13}/>
394
+ <Text style={[
395
+ styles.filterPillText,
396
+ draft.categories.has('page_view') &&
397
+ styles.filterPillTextActive,
398
+ ]}>
399
+ Screens & Pages
400
+ </Text>
401
+ </TouchableScale>
402
+
403
+ <TouchableScale onPress={() => toggleCategory('system')} style={[
404
+ styles.filterPill,
405
+ draft.categories.has('system') &&
406
+ styles.filterPillActive,
407
+ ]}>
408
+ <BoltIcon color={draft.categories.has('system')
409
+ ? AppColors.white
410
+ : AppColors.purple} size={13}/>
411
+ <Text style={[
412
+ styles.filterPillText,
413
+ draft.categories.has('system') &&
414
+ styles.filterPillTextActive,
415
+ ]}>
416
+ System & Lifecycle
417
+ </Text>
418
+ </TouchableScale>
419
+
420
+ <TouchableScale onPress={() => toggleCategory('custom')} style={[
421
+ styles.filterPill,
422
+ draft.categories.has('custom') &&
423
+ styles.filterPillActive,
424
+ ]}>
425
+ <SparkleIcon color={draft.categories.has('custom')
426
+ ? AppColors.white
427
+ : AppColors.brandPurple} size={13}/>
428
+ <Text style={[
429
+ styles.filterPillText,
430
+ draft.categories.has('custom') &&
431
+ styles.filterPillTextActive,
432
+ ]}>
433
+ Custom Events
434
+ </Text>
435
+ </TouchableScale>
436
+ </View>
437
+ </View>
438
+
439
+ {/* 3. Goals, Monetization & Attributes */}
440
+ <View style={styles.section}>
441
+ <View style={styles.sectionHeaderRow}>
442
+ <MoneyIcon color={AppColors.grayTextWeak} size={12}/>
443
+ <Text style={styles.sectionTitle}>
444
+ MONETIZATION & ATTRIBUTES
445
+ </Text>
446
+ </View>
447
+ <View style={styles.chipsWrap}>
448
+ <TouchableScale onPress={() => toggleFlag('onlyConversions')} style={[
449
+ styles.filterPill,
450
+ draft.onlyConversions && styles.filterPillActive,
451
+ ]}>
452
+ <TargetGoalIcon color={draft.onlyConversions
453
+ ? AppColors.white
454
+ : AppColors.brandPurple} size={13}/>
455
+ <Text style={[
456
+ styles.filterPillText,
457
+ draft.onlyConversions && styles.filterPillTextActive,
458
+ ]}>
459
+ Conversion Events Only
460
+ </Text>
461
+ </TouchableScale>
462
+
463
+ <TouchableScale onPress={() => toggleFlag('hasRevenue')} style={[
464
+ styles.filterPill,
465
+ draft.hasRevenue && styles.filterPillActive,
466
+ ]}>
467
+ <MoneyIcon color={draft.hasRevenue
468
+ ? AppColors.white
469
+ : AppColors.emerald600} size={13}/>
470
+ <Text style={[
471
+ styles.filterPillText,
472
+ draft.hasRevenue && styles.filterPillTextActive,
473
+ ]}>
474
+ With Revenue (Value &gt; 0)
475
+ </Text>
476
+ </TouchableScale>
477
+
478
+ <TouchableScale onPress={() => toggleFlag('hasItems')} style={[
479
+ styles.filterPill,
480
+ draft.hasItems && styles.filterPillActive,
481
+ ]}>
482
+ <PackageBoxIcon color={draft.hasItems
483
+ ? AppColors.white
484
+ : AppColors.amber700} size={13}/>
485
+ <Text style={[
486
+ styles.filterPillText,
487
+ draft.hasItems && styles.filterPillTextActive,
488
+ ]}>
489
+ With Items Payload
490
+ </Text>
491
+ </TouchableScale>
492
+
493
+ <TouchableScale onPress={() => toggleFlag('hasUserProps')} style={[
494
+ styles.filterPill,
495
+ draft.hasUserProps && styles.filterPillActive,
496
+ ]}>
497
+ <SparkleIcon color={draft.hasUserProps
498
+ ? AppColors.white
499
+ : AppColors.brandPurple} size={13}/>
500
+ <Text style={[
501
+ styles.filterPillText,
502
+ draft.hasUserProps && styles.filterPillTextActive,
503
+ ]}>
504
+ With User Properties
505
+ </Text>
506
+ </TouchableScale>
507
+
508
+ <TouchableScale onPress={() => toggleFlag('hasParams')} style={[
509
+ styles.filterPill,
510
+ draft.hasParams && styles.filterPillActive,
511
+ ]}>
512
+ <CodeBracketsIcon color={draft.hasParams
513
+ ? AppColors.white
514
+ : AppColors.grayText} size={13}/>
515
+ <Text style={[
516
+ styles.filterPillText,
517
+ draft.hasParams && styles.filterPillTextActive,
518
+ ]}>
519
+ With Custom Params
520
+ </Text>
521
+ </TouchableScale>
522
+
523
+ <TouchableScale onPress={() => toggleFlag('onlyDuplicates')} style={[
524
+ styles.filterPill,
525
+ draft.onlyDuplicates && styles.filterPillActive,
526
+ ]}>
527
+ <RepeatIcon color={draft.onlyDuplicates
528
+ ? AppColors.white
529
+ : AppColors.rose600} size={13}/>
530
+ <Text style={[
531
+ styles.filterPillText,
532
+ draft.onlyDuplicates && styles.filterPillTextActive,
533
+ ]}>
534
+ Frequent Only (&gt; 1×)
535
+ </Text>
536
+ </TouchableScale>
537
+ </View>
538
+ </View>
539
+
540
+ {/* 4. User Identification */}
541
+ <View style={styles.section}>
542
+ <View style={styles.sectionHeaderRow}>
543
+ <UserIcon color={AppColors.grayTextWeak} size={12}/>
544
+ <Text style={styles.sectionTitle}>USER IDENTIFICATION</Text>
545
+ </View>
546
+ <View style={styles.chipsWrap}>
547
+ <TouchableScale onPress={() => toggleUserType('all')} style={[
548
+ styles.filterPill,
549
+ isAllUserTypes && styles.filterPillActive,
550
+ ]}>
551
+ {isAllUserTypes && (<CheckIcon color={AppColors.white} size={12}/>)}
552
+ <Text style={[
553
+ styles.filterPillText,
554
+ isAllUserTypes && styles.filterPillTextActive,
555
+ ]}>
556
+ All Users
557
+ </Text>
558
+ </TouchableScale>
559
+
560
+ <TouchableScale onPress={() => toggleUserType('identified')} style={[
561
+ styles.filterPill,
562
+ draft.userTypes.has('identified') &&
563
+ styles.filterPillActive,
564
+ ]}>
565
+ <UserCheckIcon color={draft.userTypes.has('identified')
566
+ ? AppColors.white
567
+ : AppColors.greenColor} size={13}/>
568
+ <Text style={[
569
+ styles.filterPillText,
570
+ draft.userTypes.has('identified') &&
571
+ styles.filterPillTextActive,
572
+ ]}>
573
+ Identified (User ID)
574
+ </Text>
575
+ </TouchableScale>
576
+
577
+ <TouchableScale onPress={() => toggleUserType('anonymous')} style={[
578
+ styles.filterPill,
579
+ draft.userTypes.has('anonymous') &&
580
+ styles.filterPillActive,
581
+ ]}>
582
+ <UserIcon color={draft.userTypes.has('anonymous')
583
+ ? AppColors.white
584
+ : AppColors.grayTextWeak} size={13}/>
585
+ <Text style={[
586
+ styles.filterPillText,
587
+ draft.userTypes.has('anonymous') &&
588
+ styles.filterPillTextActive,
589
+ ]}>
590
+ Anonymous
591
+ </Text>
592
+ </TouchableScale>
593
+ </View>
594
+ </View>
595
+
596
+ {/* 5. Source Logger SDK */}
597
+ <View style={styles.section}>
598
+ <View style={styles.sectionHeaderRow}>
599
+ <BoltIcon color={AppColors.grayTextWeak} size={12}/>
600
+ <Text style={styles.sectionTitle}>EVENT SOURCE</Text>
601
+ </View>
602
+ <View style={styles.chipsWrap}>
603
+ <TouchableScale onPress={() => toggleSource('all')} style={[
604
+ styles.filterPill,
605
+ isAllSources && styles.filterPillActive,
606
+ ]}>
607
+ {isAllSources && (<CheckIcon color={AppColors.white} size={12}/>)}
608
+ <Text style={[
609
+ styles.filterPillText,
610
+ isAllSources && styles.filterPillTextActive,
611
+ ]}>
612
+ All Sources
613
+ </Text>
614
+ </TouchableScale>
615
+
616
+ <TouchableScale onPress={() => toggleSource('firebase')} style={[
617
+ styles.filterPill,
618
+ draft.sources.has('firebase') &&
619
+ styles.filterPillActive,
620
+ ]}>
621
+ <Text style={[
622
+ styles.filterPillText,
623
+ draft.sources.has('firebase') &&
624
+ styles.filterPillTextActive,
625
+ ]}>
626
+ Firebase / GA SDK
627
+ </Text>
628
+ </TouchableScale>
629
+
630
+ <TouchableScale onPress={() => toggleSource('manual')} style={[
631
+ styles.filterPill,
632
+ draft.sources.has('manual') &&
633
+ styles.filterPillActive,
634
+ ]}>
635
+ <Text style={[
636
+ styles.filterPillText,
637
+ draft.sources.has('manual') &&
638
+ styles.filterPillTextActive,
639
+ ]}>
640
+ In-App Logger
641
+ </Text>
642
+ </TouchableScale>
643
+ </View>
644
+ </View>
645
+
646
+ {/* 6. Payload Complexity */}
647
+ <View style={styles.section}>
648
+ <View style={styles.sectionHeaderRow}>
649
+ <CodeBracketsIcon color={AppColors.grayTextWeak} size={12}/>
650
+ <Text style={styles.sectionTitle}>PAYLOAD COMPLEXITY</Text>
651
+ </View>
652
+ <View style={styles.chipsWrap}>
653
+ {[
654
+ { key: 'all', label: 'All Payloads' },
655
+ { key: 'none', label: '0 Params' },
656
+ { key: 'simple', label: '1 - 5 Params' },
657
+ { key: 'heavy', label: '> 5 Params' },
658
+ ].map(item => (<TouchableScale key={item.key} onPress={() => setPayloadComplexity(item.key)} style={[
659
+ styles.filterPill,
660
+ draft.payloadComplexity === item.key &&
661
+ styles.filterPillActive,
662
+ ]}>
663
+ <Text style={[
664
+ styles.filterPillText,
665
+ draft.payloadComplexity === item.key &&
666
+ styles.filterPillTextActive,
667
+ ]}>
668
+ {item.label}
669
+ </Text>
670
+ </TouchableScale>))}
671
+ </View>
672
+ </View>
673
+
674
+ {/* 7. Discovered Screens (Dimensions) */}
675
+ {uniqueScreens.length > 0 && (<View style={styles.section}>
676
+ <View style={styles.sectionHeaderRow}>
677
+ <PinIcon color={AppColors.grayTextWeak} size={12}/>
678
+ <Text style={styles.sectionTitle}>
679
+ DISCOVERED SCREENS ({uniqueScreens.length})
680
+ </Text>
681
+ </View>
682
+ <View style={styles.chipsWrap}>
683
+ {uniqueScreens.map(scr => {
684
+ const isSelected = draft.screens.has(scr);
685
+ return (<TouchableScale key={scr} onPress={() => toggleScreen(scr)} style={[
686
+ styles.filterPill,
687
+ isSelected && styles.filterPillActive,
688
+ ]}>
689
+ <PinIcon color={isSelected
690
+ ? AppColors.white
691
+ : AppColors.amber700} size={12}/>
692
+ <Text style={[
693
+ styles.filterPillText,
694
+ isSelected && styles.filterPillTextActive,
695
+ ]}>
696
+ {scr}
697
+ </Text>
698
+ </TouchableScale>);
699
+ })}
700
+ </View>
701
+ </View>)}
702
+
703
+ {/* 8. Sort Order */}
704
+ <View style={styles.section}>
705
+ <View style={styles.sectionHeaderRow}>
706
+ <ClockIcon color={AppColors.grayTextWeak} size={12}/>
707
+ <Text style={styles.sectionTitle}>SORT ORDER</Text>
708
+ </View>
709
+ <View style={styles.chipsWrap}>
710
+ <TouchableScale onPress={() => setSort('time_desc')} style={[
711
+ styles.filterPill,
712
+ draft.sortBy === 'time_desc' &&
713
+ styles.filterPillActive,
714
+ ]}>
715
+ <ClockIcon color={draft.sortBy === 'time_desc'
716
+ ? AppColors.white
717
+ : AppColors.grayText} size={12}/>
718
+ <Text style={[
719
+ styles.filterPillText,
720
+ draft.sortBy === 'time_desc' &&
721
+ styles.filterPillTextActive,
722
+ ]}>
723
+ Newest First (Default)
724
+ </Text>
725
+ </TouchableScale>
726
+
727
+ <TouchableScale onPress={() => setSort('time_asc')} style={[
728
+ styles.filterPill,
729
+ draft.sortBy === 'time_asc' &&
730
+ styles.filterPillActive,
731
+ ]}>
732
+ <HourglassIcon color={draft.sortBy === 'time_asc'
733
+ ? AppColors.white
734
+ : AppColors.grayText} size={12}/>
735
+ <Text style={[
736
+ styles.filterPillText,
737
+ draft.sortBy === 'time_asc' &&
738
+ styles.filterPillTextActive,
739
+ ]}>
740
+ Oldest First
741
+ </Text>
742
+ </TouchableScale>
743
+
744
+ <TouchableScale onPress={() => setSort('revenue_desc')} style={[
745
+ styles.filterPill,
746
+ draft.sortBy === 'revenue_desc' &&
747
+ styles.filterPillActive,
748
+ ]}>
749
+ <MoneyIcon color={draft.sortBy === 'revenue_desc'
750
+ ? AppColors.white
751
+ : AppColors.emerald600} size={12}/>
752
+ <Text style={[
753
+ styles.filterPillText,
754
+ draft.sortBy === 'revenue_desc' &&
755
+ styles.filterPillTextActive,
756
+ ]}>
757
+ Highest Revenue
758
+ </Text>
759
+ </TouchableScale>
760
+
761
+ <TouchableScale onPress={() => setSort('count_desc')} style={[
762
+ styles.filterPill,
763
+ draft.sortBy === 'count_desc' &&
764
+ styles.filterPillActive,
765
+ ]}>
766
+ <FlameIcon color={draft.sortBy === 'count_desc'
767
+ ? AppColors.white
768
+ : AppColors.darkOrange} size={12}/>
769
+ <Text style={[
770
+ styles.filterPillText,
771
+ draft.sortBy === 'count_desc' &&
772
+ styles.filterPillTextActive,
773
+ ]}>
774
+ Most Frequent
775
+ </Text>
776
+ </TouchableScale>
777
+ </View>
778
+ </View>
779
+ </ScrollView>
780
+
781
+ {/* ─── Footer Action Bar (Discard + Apply) ────────────────────────── */}
782
+ <View style={styles.footer}>
783
+ <TouchableOpacity onPress={handleDiscard} style={styles.discardBtn} activeOpacity={0.8}>
784
+ <Text style={styles.discardBtnText}>Discard</Text>
785
+ </TouchableOpacity>
786
+
787
+ <TouchableOpacity onPress={handleApply} style={styles.applyBtn} activeOpacity={0.85}>
788
+ <Text style={styles.applyBtnText}>
789
+ Apply ({draftMatchCount} Events)
790
+ </Text>
791
+ </TouchableOpacity>
792
+ </View>
793
+ </View>
794
+ </View>
795
+ </Modal>);
796
+ };
797
+ const styles = StyleSheet.create({
798
+ backdrop: {
799
+ flex: 1,
800
+ backgroundColor: `${AppColors.primaryBlack}85`,
801
+ justifyContent: 'center',
802
+ alignItems: 'center',
803
+ padding: 16,
804
+ },
805
+ modalCard: {
806
+ width: '100%',
807
+ maxWidth: 440,
808
+ backgroundColor: AppColors.white,
809
+ borderRadius: 14,
810
+ borderWidth: 1,
811
+ borderColor: AppColors.dividerColor,
812
+ shadowColor: AppColors.black,
813
+ shadowOpacity: 0.18,
814
+ shadowRadius: 12,
815
+ shadowOffset: { width: 0, height: 4 },
816
+ elevation: 8,
817
+ overflow: 'hidden',
818
+ },
819
+ header: {
820
+ flexDirection: 'row',
821
+ alignItems: 'center',
822
+ justifyContent: 'space-between',
823
+ paddingHorizontal: 14,
824
+ paddingVertical: 12,
825
+ borderBottomWidth: 1,
826
+ borderBottomColor: AppColors.dividerColor,
827
+ backgroundColor: `${AppColors.brandPurple}08`,
828
+ },
829
+ headerIconBox: {
830
+ width: 28,
831
+ height: 28,
832
+ borderRadius: 7,
833
+ backgroundColor: `${AppColors.brandPurple}18`,
834
+ alignItems: 'center',
835
+ justifyContent: 'center',
836
+ },
837
+ headerTitle: {
838
+ fontFamily: AppFonts.interBold,
839
+ fontSize: 13.5,
840
+ color: AppColors.primaryBlack,
841
+ },
842
+ headerSubtitle: {
843
+ fontFamily: AppFonts.interMedium,
844
+ fontSize: 10,
845
+ color: AppColors.grayTextWeak,
846
+ },
847
+ resetBtn: {
848
+ paddingHorizontal: 8,
849
+ paddingVertical: 4,
850
+ borderRadius: 5,
851
+ backgroundColor: `${AppColors.darkOrange}14`,
852
+ borderWidth: 1,
853
+ borderColor: `${AppColors.darkOrange}30`,
854
+ },
855
+ resetBtnText: {
856
+ fontFamily: AppFonts.interBold,
857
+ fontSize: 10,
858
+ color: AppColors.darkOrange,
859
+ },
860
+ closeBtn: {
861
+ width: 24,
862
+ height: 24,
863
+ borderRadius: 12,
864
+ backgroundColor: `${AppColors.primaryBlack}18`,
865
+ alignItems: 'center',
866
+ justifyContent: 'center',
867
+ },
868
+ scrollContent: {
869
+ padding: 14,
870
+ gap: 14,
871
+ },
872
+ section: {
873
+ gap: 7,
874
+ },
875
+ sectionHeaderRow: {
876
+ flexDirection: 'row',
877
+ alignItems: 'center',
878
+ gap: 5,
879
+ },
880
+ sectionTitle: {
881
+ fontFamily: AppFonts.interBold,
882
+ fontSize: 9.5,
883
+ color: AppColors.grayTextWeak,
884
+ letterSpacing: 0.5,
885
+ },
886
+ chipsWrap: {
887
+ flexDirection: 'row',
888
+ flexWrap: 'wrap',
889
+ gap: 6.5,
890
+ },
891
+ filterPill: {
892
+ flexDirection: 'row',
893
+ alignItems: 'center',
894
+ gap: 5,
895
+ paddingHorizontal: 9,
896
+ paddingVertical: 5.5,
897
+ borderRadius: 7,
898
+ backgroundColor: AppColors.grayBackground,
899
+ borderWidth: 1,
900
+ borderColor: AppColors.dividerColor,
901
+ },
902
+ filterPillActive: {
903
+ backgroundColor: AppColors.brandPurple,
904
+ borderColor: AppColors.brandPurple,
905
+ shadowColor: AppColors.brandPurple,
906
+ shadowOffset: { width: 0, height: 1 },
907
+ shadowOpacity: 0.25,
908
+ shadowRadius: 3,
909
+ elevation: 2,
910
+ },
911
+ filterPillText: {
912
+ fontFamily: AppFonts.interMedium,
913
+ fontSize: 11,
914
+ color: AppColors.primaryBlack,
915
+ },
916
+ filterPillTextActive: {
917
+ color: AppColors.white,
918
+ fontFamily: AppFonts.interBold,
919
+ },
920
+ footer: {
921
+ flexDirection: 'row',
922
+ alignItems: 'center',
923
+ gap: 10,
924
+ paddingHorizontal: 14,
925
+ paddingVertical: 12,
926
+ borderTopWidth: 1,
927
+ borderTopColor: AppColors.dividerColor,
928
+ backgroundColor: AppColors.grayBackground,
929
+ },
930
+ discardBtn: {
931
+ flex: 1,
932
+ paddingVertical: 9.5,
933
+ borderRadius: 8,
934
+ borderWidth: 1,
935
+ borderColor: AppColors.dividerColor,
936
+ backgroundColor: AppColors.white,
937
+ alignItems: 'center',
938
+ justifyContent: 'center',
939
+ },
940
+ discardBtnText: {
941
+ fontFamily: AppFonts.interBold,
942
+ fontSize: 12.5,
943
+ color: AppColors.grayTextStrong,
944
+ },
945
+ applyBtn: {
946
+ flex: 2,
947
+ backgroundColor: AppColors.brandPurple,
948
+ paddingVertical: 9.5,
949
+ borderRadius: 8,
950
+ alignItems: 'center',
951
+ justifyContent: 'center',
952
+ shadowColor: AppColors.brandPurple,
953
+ shadowOffset: { width: 0, height: 1 },
954
+ shadowOpacity: 0.25,
955
+ shadowRadius: 3,
956
+ elevation: 2,
957
+ },
958
+ applyBtnText: {
959
+ fontFamily: AppFonts.interBold,
960
+ fontSize: 12.5,
961
+ color: AppColors.white,
962
+ },
963
+ });
964
+ export default AnalyticsFilterModal;