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
@@ -1,15 +1,19 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useState, useMemo } from 'react';
2
2
  import { FlatList, Pressable, Text, TextInput, TouchableOpacity, View, } from 'react-native';
3
3
  import { animateNextLayout, useInspector } from './InspectorContext';
4
4
  import AnalyticsEventCard from '../AnalyticsEventCard';
5
5
  import AnalyticsDetail from '../AnalyticsDetail';
6
+ import AnalyticsFilterModal from './AnalyticsFilterModal';
6
7
  import EndOfListFooter from '../EndOfListFooter';
7
8
  import styles from '../../styles';
8
9
  import { AppColors } from '../../styles/AppColors';
10
+ import { AppFonts } from '../../styles/AppFonts';
11
+ import TouchableScale from '../TouchableScale';
9
12
  import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
10
- import { SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, } from '../NetworkIcons';
13
+ import { getEventCategory, } from '../../helpers';
14
+ import { SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, TargetGoalIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
11
15
  const AnalyticsHeader = React.memo(() => {
12
- const { filteredAnalyticsEvents, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
16
+ const { analyticsEvents, filteredAnalyticsEvents, analyticsFilters, setAnalyticsFilters, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
13
17
  const userId = getCurrentUserId();
14
18
  const userProperties = getCurrentUserProperties();
15
19
  const defaultParams = getDefaultEventParameters();
@@ -17,34 +21,628 @@ const AnalyticsHeader = React.memo(() => {
17
21
  const hasUserProps = Object.keys(userProperties).length > 0;
18
22
  const hasDefaultParams = Object.keys(defaultParams).length > 0;
19
23
  const totalEvents = filteredAnalyticsEvents.length;
24
+ // Category counts & breakdown across all session events
25
+ const categoryStats = useMemo(() => {
26
+ let ecommerce = 0;
27
+ let page_view = 0;
28
+ let system = 0;
29
+ let custom = 0;
30
+ let totalRevenue = 0;
31
+ for (const e of analyticsEvents) {
32
+ const cat = getEventCategory(e.name);
33
+ const c = e.count || 1;
34
+ if (cat === 'ecommerce') {
35
+ ecommerce += c;
36
+ const val = e.params?.value ?? e.params?.price;
37
+ if (val && !isNaN(Number(val))) {
38
+ totalRevenue += Number(val);
39
+ }
40
+ }
41
+ else if (cat === 'page_view') {
42
+ page_view += c;
43
+ }
44
+ else if (cat === 'system') {
45
+ system += c;
46
+ }
47
+ else {
48
+ custom += c;
49
+ }
50
+ }
51
+ const total = ecommerce + page_view + system + custom || 1;
52
+ return {
53
+ ecommerce,
54
+ page_view,
55
+ system,
56
+ custom,
57
+ totalRevenue,
58
+ ecommercePct: Math.round((ecommerce / total) * 100),
59
+ page_viewPct: Math.round((page_view / total) * 100),
60
+ systemPct: Math.round((system / total) * 100),
61
+ customPct: Math.round((custom / total) * 100),
62
+ totalCount: total,
63
+ };
64
+ }, [analyticsEvents]);
65
+ // Top Screen Views Leaderboard (GA4 Realtime Card)
66
+ const topScreens = useMemo(() => {
67
+ const screenMap = new Map();
68
+ for (const e of filteredAnalyticsEvents) {
69
+ const scr = e.screenName ||
70
+ e.params?.firebase_screen ||
71
+ e.params?.screen_name ||
72
+ e.params?.firebase_screen_class ||
73
+ e.screenClass;
74
+ if (scr && typeof scr === 'string' && scr.trim() !== '') {
75
+ screenMap.set(scr, (screenMap.get(scr) || 0) + (e.count || 1));
76
+ }
77
+ }
78
+ const arr = Array.from(screenMap.entries()).sort((a, b) => b[1] - a[1]);
79
+ const maxVal = arr.length > 0 ? arr[0][1] : 1;
80
+ return arr.slice(0, 3).map(([name, count]) => ({
81
+ name,
82
+ count,
83
+ pct: Math.round((count / (filteredAnalyticsEvents.length || 1)) * 100),
84
+ barWidthPct: Math.round((count / maxVal) * 100),
85
+ }));
86
+ }, [filteredAnalyticsEvents]);
87
+ // Toggle category filter from top button group
88
+ const toggleCategoryFilter = (cat) => {
89
+ animateNextLayout();
90
+ setAnalyticsFilters(prev => {
91
+ const isSelected = !prev.categories.has('all') && prev.categories.has(cat);
92
+ if (isSelected) {
93
+ return { ...prev, categories: new Set(['all']) };
94
+ }
95
+ else {
96
+ return { ...prev, categories: new Set([cat]) };
97
+ }
98
+ });
99
+ };
100
+ // 12-Bucket GA4 Realtime Stacked Histogram Graph (-30m / 30s to NOW)
101
+ const BUCKET_COUNT = 12;
102
+ const chartHeight = 56;
103
+ const histogramBars = useMemo(() => {
104
+ const buckets = Array.from({ length: BUCKET_COUNT }, () => ({
105
+ ecommerce: 0,
106
+ page_view: 0,
107
+ system: 0,
108
+ custom: 0,
109
+ total: 0,
110
+ }));
111
+ const now = Date.now();
112
+ const windowMs = 60000; // 60s window
113
+ const bucketDuration = windowMs / BUCKET_COUNT;
114
+ for (const e of filteredAnalyticsEvents) {
115
+ const age = now - e.timestamp;
116
+ if (age >= 0 && age < windowMs) {
117
+ const bucketIdx = Math.floor((windowMs - 1 - age) / bucketDuration);
118
+ if (bucketIdx >= 0 && bucketIdx < BUCKET_COUNT) {
119
+ const cat = getEventCategory(e.name);
120
+ const c = e.count || 1;
121
+ buckets[bucketIdx].total += c;
122
+ if (cat === 'ecommerce')
123
+ buckets[bucketIdx].ecommerce += c;
124
+ else if (cat === 'page_view')
125
+ buckets[bucketIdx].page_view += c;
126
+ else if (cat === 'system')
127
+ buckets[bucketIdx].system += c;
128
+ else
129
+ buckets[bucketIdx].custom += c;
130
+ }
131
+ }
132
+ }
133
+ const maxVal = Math.max(...buckets.map(b => b.total), 4);
134
+ return buckets.map((b, idx) => {
135
+ const totalH = Math.max((b.total / maxVal) * (chartHeight - 14), b.total > 0 ? 8 : 2.5);
136
+ const ecomH = b.total > 0 ? (b.ecommerce / b.total) * totalH : 0;
137
+ const screenH = b.total > 0 ? (b.page_view / b.total) * totalH : 0;
138
+ const sysH = b.total > 0 ? (b.system / b.total) * totalH : 0;
139
+ const custH = b.total > 0 ? (b.custom / b.total) * totalH : totalH;
140
+ return {
141
+ index: idx,
142
+ total: b.total,
143
+ totalHeight: totalH,
144
+ ecomHeight: ecomH,
145
+ screenHeight: screenH,
146
+ sysHeight: sysH,
147
+ custHeight: custH,
148
+ isLatest: idx === BUCKET_COUNT - 1,
149
+ };
150
+ });
151
+ }, [filteredAnalyticsEvents, chartHeight]);
20
152
  return (<View style={styles.analyticsHeaderCard}>
21
- <View style={[styles.analyticsHeaderTop, !analyticsHeaderExpanded && { marginBottom: 0 }]}>
153
+ {/* ── Top Header Row (GA4 Style) ─────────────────────────────────────── */}
154
+ <View style={styles.analyticsHeaderTop}>
22
155
  <View style={{ flex: 1 }}>
23
156
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
24
- <Text style={styles.analyticsHeaderTitle}>Session Info</Text>
157
+ <Text style={styles.analyticsHeaderTitle}>Users in last 30 minutes</Text>
25
158
  <View style={[
26
159
  styles.statusDot,
27
- { backgroundColor: isTrackingEnabled ? AppColors.greenColor : AppColors.errorColor }
160
+ {
161
+ backgroundColor: isTrackingEnabled
162
+ ? AppColors.greenColor
163
+ : AppColors.errorColor,
164
+ },
28
165
  ]}/>
29
- <Text style={styles.statusText}>
30
- {isTrackingEnabled ? 'Active' : 'Paused'}
166
+ <Text style={[
167
+ styles.statusText,
168
+ {
169
+ color: isTrackingEnabled
170
+ ? AppColors.greenColor
171
+ : AppColors.errorColor,
172
+ fontFamily: AppFonts.interBold,
173
+ },
174
+ ]}>
175
+ {isTrackingEnabled ? 'Realtime' : 'Paused'}
31
176
  </Text>
32
177
  </View>
33
178
  <Text style={styles.analyticsHeaderSubtitle} numberOfLines={1}>
34
- {userId ? `User ID: ${userId}` : 'Anonymous Session'}
179
+ {userId ? `User: ${userId}` : 'Anonymous Session'}
35
180
  </Text>
36
181
  </View>
37
-
182
+
38
183
  <TouchableOpacity style={styles.analyticsHeaderToggle} activeOpacity={0.7} onPress={() => {
39
184
  animateNextLayout();
40
185
  setAnalyticsHeaderExpanded(!analyticsHeaderExpanded);
41
186
  }}>
42
187
  <Text style={styles.analyticsHeaderToggleText}>
43
- {analyticsHeaderExpanded ? 'Collapse' : 'Expand'}
188
+ {analyticsHeaderExpanded ? 'Hide Info' : 'Session Info'}
44
189
  </Text>
45
190
  </TouchableOpacity>
46
191
  </View>
47
192
 
193
+ {/* ── GA4 Realtime Metric Hero & Activity Chart ─────────────────────── */}
194
+ <View style={{
195
+ marginTop: 10,
196
+ marginBottom: 8,
197
+ backgroundColor: AppColors.grayBackground,
198
+ borderRadius: 10,
199
+ borderWidth: 1,
200
+ borderColor: AppColors.dividerColor,
201
+ padding: 10,
202
+ }}>
203
+ {/* Hero Metric Row */}
204
+ <View style={{
205
+ flexDirection: 'row',
206
+ justifyContent: 'space-between',
207
+ alignItems: 'flex-start',
208
+ marginBottom: 8,
209
+ }}>
210
+ <View>
211
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 6 }}>
212
+ <Text style={{
213
+ fontFamily: AppFonts.interBold,
214
+ fontSize: 22,
215
+ color: AppColors.primaryBlack,
216
+ lineHeight: 26,
217
+ }}>
218
+ {totalEvents}
219
+ </Text>
220
+ <Text style={{
221
+ fontFamily: AppFonts.interMedium,
222
+ fontSize: 10.5,
223
+ color: AppColors.grayTextWeak,
224
+ }}>
225
+ events in window
226
+ </Text>
227
+ </View>
228
+ </View>
229
+
230
+ {/* Realtime Badges */}
231
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
232
+ {categoryStats.totalRevenue > 0 && (<View style={{
233
+ flexDirection: 'row',
234
+ alignItems: 'center',
235
+ gap: 3,
236
+ backgroundColor: `${AppColors.emerald600}15`,
237
+ paddingHorizontal: 6,
238
+ paddingVertical: 2.5,
239
+ borderRadius: 4,
240
+ borderWidth: 1,
241
+ borderColor: `${AppColors.emerald600}30`,
242
+ }}>
243
+ <MoneyIcon color={AppColors.emerald600} size={10}/>
244
+ <Text style={{
245
+ fontFamily: AppFonts.interBold,
246
+ fontSize: 9.5,
247
+ color: AppColors.emerald600,
248
+ }}>
249
+ ${categoryStats.totalRevenue.toFixed(2)}
250
+ </Text>
251
+ </View>)}
252
+ <View style={{
253
+ flexDirection: 'row',
254
+ alignItems: 'center',
255
+ gap: 4,
256
+ backgroundColor: `${AppColors.brandPurple}14`,
257
+ paddingHorizontal: 6,
258
+ paddingVertical: 2.5,
259
+ borderRadius: 4,
260
+ borderWidth: 1,
261
+ borderColor: `${AppColors.brandPurple}2E`,
262
+ }}>
263
+ <TargetGoalIcon color={AppColors.brandPurple} size={10}/>
264
+ <Text style={{
265
+ fontFamily: AppFonts.interBold,
266
+ fontSize: 9.5,
267
+ color: AppColors.brandPurple,
268
+ }}>
269
+ Live Telemetry
270
+ </Text>
271
+ </View>
272
+ </View>
273
+ </View>
274
+
275
+ {/* GA4 Stacked Realtime Histogram Bar Graph */}
276
+ <View style={{
277
+ height: chartHeight,
278
+ flexDirection: 'row',
279
+ alignItems: 'flex-end',
280
+ justifyContent: 'space-between',
281
+ paddingHorizontal: 2,
282
+ paddingBottom: 2,
283
+ borderBottomWidth: 1,
284
+ borderBottomColor: AppColors.dividerColor,
285
+ }}>
286
+ {histogramBars.map(bar => (<View key={bar.index} style={{
287
+ flex: 1,
288
+ alignItems: 'center',
289
+ justifyContent: 'flex-end',
290
+ height: '100%',
291
+ marginHorizontal: 1.5,
292
+ }}>
293
+ {bar.total > 0 && (<Text style={{
294
+ fontFamily: AppFonts.interBold,
295
+ fontSize: 7.5,
296
+ color: bar.isLatest
297
+ ? AppColors.brandPurple
298
+ : AppColors.grayTextWeak,
299
+ marginBottom: 2,
300
+ }}>
301
+ {bar.total}
302
+ </Text>)}
303
+ {/* Stacked Category Segments Column */}
304
+ <View style={{
305
+ width: '100%',
306
+ maxWidth: 15,
307
+ height: bar.totalHeight,
308
+ borderRadius: 3,
309
+ overflow: 'hidden',
310
+ backgroundColor: bar.total > 0
311
+ ? `${AppColors.grayBorderSecondary}40`
312
+ : `${AppColors.grayBorderSecondary}40`,
313
+ }}>
314
+ {bar.total > 0 ? (<>
315
+ {bar.ecomHeight > 0 && (<View style={{
316
+ height: bar.ecomHeight,
317
+ backgroundColor: AppColors.amber500,
318
+ }}/>)}
319
+ {bar.screenHeight > 0 && (<View style={{
320
+ height: bar.screenHeight,
321
+ backgroundColor: AppColors.sky500,
322
+ }}/>)}
323
+ {bar.sysHeight > 0 && (<View style={{
324
+ height: bar.sysHeight,
325
+ backgroundColor: AppColors.purple,
326
+ }}/>)}
327
+ {bar.custHeight > 0 && (<View style={{
328
+ height: bar.custHeight,
329
+ backgroundColor: AppColors.brandPurple,
330
+ }}/>)}
331
+ </>) : (<View style={{
332
+ height: 2.5,
333
+ backgroundColor: `${AppColors.grayBorderSecondary}80`,
334
+ }}/>)}
335
+ </View>
336
+ </View>))}
337
+ </View>
338
+
339
+ {/* Timeline Axis Labels */}
340
+ <View style={{
341
+ flexDirection: 'row',
342
+ justifyContent: 'space-between',
343
+ marginTop: 4,
344
+ paddingHorizontal: 2,
345
+ }}>
346
+ <Text style={{
347
+ fontFamily: AppFonts.interMedium,
348
+ fontSize: 8,
349
+ color: AppColors.grayTextWeak,
350
+ }}>
351
+ -30 min
352
+ </Text>
353
+ <Text style={{
354
+ fontFamily: AppFonts.interMedium,
355
+ fontSize: 8,
356
+ color: AppColors.grayTextWeak,
357
+ }}>
358
+ -20 min
359
+ </Text>
360
+ <Text style={{
361
+ fontFamily: AppFonts.interMedium,
362
+ fontSize: 8,
363
+ color: AppColors.grayTextWeak,
364
+ }}>
365
+ -10 min
366
+ </Text>
367
+ <Text style={{
368
+ fontFamily: AppFonts.interBold,
369
+ fontSize: 8,
370
+ color: AppColors.brandPurple,
371
+ }}>
372
+ NOW
373
+ </Text>
374
+ </View>
375
+ </View>
376
+
377
+ {/* ── Category Breakdown Distribution Bar & Interactive Filter Buttons ─ */}
378
+ <View style={{ marginTop: 2, marginBottom: 4 }}>
379
+ {/* Multi-color Proportional Segmented Bar */}
380
+ <View style={{
381
+ height: 5,
382
+ borderRadius: 2.5,
383
+ backgroundColor: `${AppColors.grayBorderSecondary}40`,
384
+ flexDirection: 'row',
385
+ overflow: 'hidden',
386
+ marginBottom: 8,
387
+ }}>
388
+ {categoryStats.ecommerce > 0 && (<View style={{
389
+ flex: categoryStats.ecommerce,
390
+ backgroundColor: AppColors.amber500,
391
+ }}/>)}
392
+ {categoryStats.page_view > 0 && (<View style={{
393
+ flex: categoryStats.page_view,
394
+ backgroundColor: AppColors.sky500,
395
+ }}/>)}
396
+ {categoryStats.system > 0 && (<View style={{
397
+ flex: categoryStats.system,
398
+ backgroundColor: AppColors.purple,
399
+ }}/>)}
400
+ {categoryStats.custom > 0 && (<View style={{
401
+ flex: categoryStats.custom,
402
+ backgroundColor: AppColors.brandPurple,
403
+ }}/>)}
404
+ </View>
405
+
406
+ {/* 4 Interactive Category Filter Buttons */}
407
+ <View style={{
408
+ flexDirection: 'row',
409
+ flexWrap: 'wrap',
410
+ gap: 6,
411
+ }}>
412
+ {/* Ecommerce Button */}
413
+ {(() => {
414
+ const isSelected = !analyticsFilters.categories.has('all') &&
415
+ analyticsFilters.categories.has('ecommerce');
416
+ return (<TouchableScale onPress={() => toggleCategoryFilter('ecommerce')} style={[
417
+ {
418
+ flexDirection: 'row',
419
+ alignItems: 'center',
420
+ gap: 4.5,
421
+ backgroundColor: isSelected
422
+ ? AppColors.amber500
423
+ : `${AppColors.amber500}14`,
424
+ paddingHorizontal: 7,
425
+ paddingVertical: 4.5,
426
+ borderRadius: 6,
427
+ borderWidth: 1,
428
+ borderColor: isSelected
429
+ ? AppColors.amber500
430
+ : `${AppColors.amber500}35`,
431
+ },
432
+ isSelected && {
433
+ shadowColor: AppColors.amber500,
434
+ shadowOffset: { width: 0, height: 1 },
435
+ shadowOpacity: 0.25,
436
+ shadowRadius: 2,
437
+ elevation: 2,
438
+ },
439
+ ]}>
440
+ <CartIcon color={isSelected ? AppColors.white : AppColors.amber700} size={11}/>
441
+ <Text style={{
442
+ fontFamily: AppFonts.interBold,
443
+ fontSize: 10,
444
+ color: isSelected ? AppColors.white : AppColors.amber700,
445
+ }}>
446
+ Ecommerce: {categoryStats.ecommerce} ({categoryStats.ecommercePct}%)
447
+ </Text>
448
+ </TouchableScale>);
449
+ })()}
450
+
451
+ {/* Screens Button */}
452
+ {(() => {
453
+ const isSelected = !analyticsFilters.categories.has('all') &&
454
+ analyticsFilters.categories.has('page_view');
455
+ return (<TouchableScale onPress={() => toggleCategoryFilter('page_view')} style={[
456
+ {
457
+ flexDirection: 'row',
458
+ alignItems: 'center',
459
+ gap: 4.5,
460
+ backgroundColor: isSelected
461
+ ? AppColors.sky600
462
+ : `${AppColors.sky500}14`,
463
+ paddingHorizontal: 7,
464
+ paddingVertical: 4.5,
465
+ borderRadius: 6,
466
+ borderWidth: 1,
467
+ borderColor: isSelected
468
+ ? AppColors.sky600
469
+ : `${AppColors.sky500}35`,
470
+ },
471
+ isSelected && {
472
+ shadowColor: AppColors.sky600,
473
+ shadowOffset: { width: 0, height: 1 },
474
+ shadowOpacity: 0.25,
475
+ shadowRadius: 2,
476
+ elevation: 2,
477
+ },
478
+ ]}>
479
+ <GlobeIcon color={isSelected ? AppColors.white : AppColors.sky600} size={11}/>
480
+ <Text style={{
481
+ fontFamily: AppFonts.interBold,
482
+ fontSize: 10,
483
+ color: isSelected ? AppColors.white : AppColors.sky600,
484
+ }}>
485
+ Screens: {categoryStats.page_view} ({categoryStats.page_viewPct}%)
486
+ </Text>
487
+ </TouchableScale>);
488
+ })()}
489
+
490
+ {/* System Button */}
491
+ {(() => {
492
+ const isSelected = !analyticsFilters.categories.has('all') &&
493
+ analyticsFilters.categories.has('system');
494
+ return (<TouchableScale onPress={() => toggleCategoryFilter('system')} style={[
495
+ {
496
+ flexDirection: 'row',
497
+ alignItems: 'center',
498
+ gap: 4.5,
499
+ backgroundColor: isSelected
500
+ ? AppColors.purple
501
+ : `${AppColors.purple}14`,
502
+ paddingHorizontal: 7,
503
+ paddingVertical: 4.5,
504
+ borderRadius: 6,
505
+ borderWidth: 1,
506
+ borderColor: isSelected
507
+ ? AppColors.purple
508
+ : `${AppColors.purple}35`,
509
+ },
510
+ isSelected && {
511
+ shadowColor: AppColors.purple,
512
+ shadowOffset: { width: 0, height: 1 },
513
+ shadowOpacity: 0.25,
514
+ shadowRadius: 2,
515
+ elevation: 2,
516
+ },
517
+ ]}>
518
+ <BoltIcon color={isSelected ? AppColors.white : AppColors.purple} size={11}/>
519
+ <Text style={{
520
+ fontFamily: AppFonts.interBold,
521
+ fontSize: 10,
522
+ color: isSelected ? AppColors.white : AppColors.purple,
523
+ }}>
524
+ System: {categoryStats.system} ({categoryStats.systemPct}%)
525
+ </Text>
526
+ </TouchableScale>);
527
+ })()}
528
+
529
+ {/* Custom Button */}
530
+ {(() => {
531
+ const isSelected = !analyticsFilters.categories.has('all') &&
532
+ analyticsFilters.categories.has('custom');
533
+ return (<TouchableScale onPress={() => toggleCategoryFilter('custom')} style={[
534
+ {
535
+ flexDirection: 'row',
536
+ alignItems: 'center',
537
+ gap: 4.5,
538
+ backgroundColor: isSelected
539
+ ? AppColors.brandPurple
540
+ : `${AppColors.brandPurple}14`,
541
+ paddingHorizontal: 7,
542
+ paddingVertical: 4.5,
543
+ borderRadius: 6,
544
+ borderWidth: 1,
545
+ borderColor: isSelected
546
+ ? AppColors.brandPurple
547
+ : `${AppColors.brandPurple}35`,
548
+ },
549
+ isSelected && {
550
+ shadowColor: AppColors.brandPurple,
551
+ shadowOffset: { width: 0, height: 1 },
552
+ shadowOpacity: 0.25,
553
+ shadowRadius: 2,
554
+ elevation: 2,
555
+ },
556
+ ]}>
557
+ <SparkleIcon color={isSelected ? AppColors.white : AppColors.brandPurple} size={11}/>
558
+ <Text style={{
559
+ fontFamily: AppFonts.interBold,
560
+ fontSize: 10,
561
+ color: isSelected ? AppColors.white : AppColors.brandPurple,
562
+ }}>
563
+ Custom: {categoryStats.custom} ({categoryStats.customPct}%)
564
+ </Text>
565
+ </TouchableScale>);
566
+ })()}
567
+ </View>
568
+ </View>
569
+
570
+ {/* ── Top Screens / Views Mini Leaderboard (GA4 Realtime Overview) ──── */}
571
+ {topScreens.length > 0 && (<View style={{
572
+ marginTop: 8,
573
+ backgroundColor: `${AppColors.sky500}0A`,
574
+ borderRadius: 8,
575
+ borderWidth: 1,
576
+ borderColor: `${AppColors.sky500}22`,
577
+ padding: 8,
578
+ }}>
579
+ <View style={{
580
+ flexDirection: 'row',
581
+ justifyContent: 'space-between',
582
+ alignItems: 'center',
583
+ marginBottom: 6,
584
+ }}>
585
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
586
+ <PinIcon color={AppColors.sky600} size={11}/>
587
+ <Text style={{
588
+ fontFamily: AppFonts.interBold,
589
+ fontSize: 9.5,
590
+ color: AppColors.sky600,
591
+ letterSpacing: 0.3,
592
+ }}>
593
+ TOP SCREENS (REALTIME)
594
+ </Text>
595
+ </View>
596
+ <Text style={{
597
+ fontFamily: AppFonts.interMedium,
598
+ fontSize: 9,
599
+ color: AppColors.grayTextWeak,
600
+ }}>
601
+ Views
602
+ </Text>
603
+ </View>
604
+
605
+ <View style={{ gap: 4 }}>
606
+ {topScreens.map(scr => (<View key={scr.name}>
607
+ <View style={{
608
+ flexDirection: 'row',
609
+ justifyContent: 'space-between',
610
+ alignItems: 'center',
611
+ marginBottom: 2,
612
+ }}>
613
+ <Text style={{
614
+ fontFamily: AppFonts.interMedium,
615
+ fontSize: 10,
616
+ color: AppColors.primaryBlack,
617
+ }} numberOfLines={1}>
618
+ {scr.name}
619
+ </Text>
620
+ <Text style={{
621
+ fontFamily: AppFonts.interBold,
622
+ fontSize: 9.5,
623
+ color: AppColors.sky600,
624
+ }}>
625
+ {scr.count} ({scr.pct}%)
626
+ </Text>
627
+ </View>
628
+ {/* Mini Horizontal Progress Meter */}
629
+ <View style={{
630
+ height: 3,
631
+ borderRadius: 1.5,
632
+ backgroundColor: `${AppColors.sky500}22`,
633
+ overflow: 'hidden',
634
+ }}>
635
+ <View style={{
636
+ height: '100%',
637
+ width: `${scr.barWidthPct}%`,
638
+ backgroundColor: AppColors.sky500,
639
+ }}/>
640
+ </View>
641
+ </View>))}
642
+ </View>
643
+ </View>)}
644
+
645
+ {/* ── Expandable User Properties & Default Parameters ───────────────── */}
48
646
  {analyticsHeaderExpanded && (<>
49
647
  {/* Stats Row */}
50
648
  <View style={styles.analyticsStatsRow}>
@@ -88,7 +686,8 @@ const AnalyticsHeader = React.memo(() => {
88
686
  </View>);
89
687
  });
90
688
  const AnalyticsTab = React.memo(() => {
91
- const { filteredAnalyticsEvents, analyticsSearch, setAnalyticsSearch, handleDelete, selectedEvent, setSelectedEvent, newEventIds, logRouteMapRef, isAnalyticsLayoutReady, setIsAnalyticsLayoutReady, isAnalyticsPaused, setIsAnalyticsPaused, } = useInspector();
689
+ const { filteredAnalyticsEvents, analyticsSearch, setAnalyticsSearch, handleDelete, selectedEvent, setSelectedEvent, newEventIds, logRouteMapRef, isAnalyticsLayoutReady, setIsAnalyticsLayoutReady, isAnalyticsPaused, setIsAnalyticsPaused, isAnalyticsFilterApplied, } = useInspector();
690
+ const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
92
691
  const keyExtractor = useCallback((item, index) => item?.id?.toString() ?? index.toString(), []);
93
692
  const renderItem = useCallback(({ item, index }) => {
94
693
  const prev = filteredAnalyticsEvents[index + 1];
@@ -154,6 +753,31 @@ const AnalyticsTab = React.memo(() => {
154
753
  ? AppColors.darkOrange
155
754
  : AppColors.grayTextStrong} size={16}/>
156
755
  </TouchableOpacity>
756
+
757
+ {/* GA Dashboard-style Filter Button */}
758
+ <TouchableOpacity style={[
759
+ styles.toolbarBtn,
760
+ isAnalyticsFilterApplied && {
761
+ borderColor: `${AppColors.brandPurple}60`,
762
+ backgroundColor: `${AppColors.brandPurple}15`,
763
+ },
764
+ ]} onPress={() => setIsFilterModalOpen(true)} hitSlop={6}>
765
+ <FilterIcon color={isAnalyticsFilterApplied
766
+ ? AppColors.brandPurple
767
+ : AppColors.grayTextStrong} size={15}/>
768
+ {isAnalyticsFilterApplied && (<View style={{
769
+ position: 'absolute',
770
+ top: 2,
771
+ right: 2,
772
+ width: 7,
773
+ height: 7,
774
+ borderRadius: 3.5,
775
+ backgroundColor: AppColors.darkOrange,
776
+ borderWidth: 1,
777
+ borderColor: AppColors.white,
778
+ }}/>)}
779
+ </TouchableOpacity>
780
+
157
781
  <TouchableOpacity style={[
158
782
  styles.toolbarBtn,
159
783
  {
@@ -166,6 +790,9 @@ const AnalyticsTab = React.memo(() => {
166
790
  </View>
167
791
  </View>)}
168
792
 
793
+ {/* Filter Modal */}
794
+ <AnalyticsFilterModal visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)}/>
795
+
169
796
  <View style={{ flex: 1 }} onLayout={() => setIsAnalyticsLayoutReady(true)}>
170
797
  {selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : isAnalyticsLayoutReady ? (<FlatList data={filteredAnalyticsEvents} keyExtractor={keyExtractor} ListHeaderComponent={AnalyticsHeader} renderItem={renderItem} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={false} ListEmptyComponent={<View style={styles.emptyContainer}>
171
798
  <View style={styles.emptyIconWrap}>