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