react-native-inapp-inspector 1.1.28 → 1.1.31

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 (77) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -80
  2. package/dist/commonjs/components/AnalyticsDetail.js +668 -108
  3. package/dist/commonjs/components/AppHeaderLogo.js +15 -15
  4. package/dist/commonjs/components/ConsoleLogCard.js +112 -66
  5. package/dist/commonjs/components/CopyButton.js +15 -1
  6. package/dist/commonjs/components/DomainHeader.js +64 -32
  7. package/dist/commonjs/components/HighlightText.js +7 -1
  8. package/dist/commonjs/components/Inspector/AnalyticsTab.js +332 -221
  9. package/dist/commonjs/components/Inspector/ConsoleTab.js +162 -56
  10. package/dist/commonjs/components/Inspector/InspectorHeader.js +70 -119
  11. package/dist/commonjs/components/Inspector/LogDetail.js +53 -17
  12. package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
  13. package/dist/commonjs/components/Inspector/NetworkDetail.js +24 -24
  14. package/dist/commonjs/components/Inspector/NetworkTab.js +4 -1
  15. package/dist/commonjs/components/Inspector/ReduxDetail.js +237 -42
  16. package/dist/commonjs/components/Inspector/ReduxTab.js +456 -35
  17. package/dist/commonjs/components/JsonViewer.js +26 -4
  18. package/dist/commonjs/components/LogCard.js +103 -11
  19. package/dist/commonjs/components/NetworkIcons.js +19 -10
  20. package/dist/commonjs/components/Toast.d.ts +3 -0
  21. package/dist/commonjs/components/Toast.js +152 -0
  22. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  23. package/dist/commonjs/components/TouchableScale.js +3 -3
  24. package/dist/commonjs/components/TreeNode.js +12 -2
  25. package/dist/commonjs/constants/index.js +6 -6
  26. package/dist/commonjs/constants/version.d.ts +1 -1
  27. package/dist/commonjs/constants/version.js +1 -1
  28. package/dist/commonjs/customHooks/reduxLogger.js +128 -5
  29. package/dist/commonjs/helpers/index.d.ts +4 -0
  30. package/dist/commonjs/helpers/index.js +107 -92
  31. package/dist/commonjs/helpers/toast.d.ts +4 -0
  32. package/dist/commonjs/helpers/toast.js +20 -0
  33. package/dist/commonjs/i18n/locales/en.json +49 -3
  34. package/dist/commonjs/styles/AppColors.d.ts +6 -0
  35. package/dist/commonjs/styles/AppColors.js +6 -0
  36. package/dist/commonjs/styles/index.d.ts +82 -15
  37. package/dist/commonjs/styles/index.js +92 -26
  38. package/dist/commonjs/types/interfaces.d.ts +7 -0
  39. package/dist/esm/components/AnalyticsDetail.js +670 -110
  40. package/dist/esm/components/AppHeaderLogo.js +15 -15
  41. package/dist/esm/components/ConsoleLogCard.js +114 -68
  42. package/dist/esm/components/CopyButton.js +15 -1
  43. package/dist/esm/components/DomainHeader.js +64 -32
  44. package/dist/esm/components/HighlightText.js +7 -1
  45. package/dist/esm/components/Inspector/AnalyticsTab.js +334 -223
  46. package/dist/esm/components/Inspector/ConsoleTab.js +163 -57
  47. package/dist/esm/components/Inspector/InspectorHeader.js +71 -120
  48. package/dist/esm/components/Inspector/LogDetail.js +55 -19
  49. package/dist/esm/components/Inspector/MainScreen.js +4 -0
  50. package/dist/esm/components/Inspector/NetworkDetail.js +25 -25
  51. package/dist/esm/components/Inspector/NetworkTab.js +4 -1
  52. package/dist/esm/components/Inspector/ReduxDetail.js +239 -44
  53. package/dist/esm/components/Inspector/ReduxTab.js +458 -37
  54. package/dist/esm/components/JsonViewer.js +26 -4
  55. package/dist/esm/components/LogCard.js +105 -13
  56. package/dist/esm/components/NetworkIcons.js +19 -10
  57. package/dist/esm/components/Toast.d.ts +3 -0
  58. package/dist/esm/components/Toast.js +117 -0
  59. package/dist/esm/components/TouchableScale.d.ts +1 -0
  60. package/dist/esm/components/TouchableScale.js +3 -3
  61. package/dist/esm/components/TreeNode.js +12 -2
  62. package/dist/esm/constants/index.js +6 -6
  63. package/dist/esm/constants/version.d.ts +1 -1
  64. package/dist/esm/constants/version.js +1 -1
  65. package/dist/esm/customHooks/reduxLogger.js +128 -5
  66. package/dist/esm/helpers/index.d.ts +4 -0
  67. package/dist/esm/helpers/index.js +102 -92
  68. package/dist/esm/helpers/toast.d.ts +4 -0
  69. package/dist/esm/helpers/toast.js +15 -0
  70. package/dist/esm/i18n/locales/en.json +49 -3
  71. package/dist/esm/styles/AppColors.d.ts +6 -0
  72. package/dist/esm/styles/AppColors.js +6 -0
  73. package/dist/esm/styles/index.d.ts +82 -15
  74. package/dist/esm/styles/index.js +92 -26
  75. package/dist/esm/types/interfaces.d.ts +7 -0
  76. package/ios/NetworkInspectorModule.m +0 -10
  77. package/package.json +13 -10
@@ -9,11 +9,11 @@ import styles from '../../styles';
9
9
  import { AppColors } from '../../styles/AppColors';
10
10
  import { AppFonts } from '../../styles/AppFonts';
11
11
  import { useTranslation } from '../../i18n';
12
- import Svg, { Path, Line, Circle, G, Defs, LinearGradient as SvgLinearGradient, Stop, } from 'react-native-svg';
12
+ import Svg, { Path, Rect, Line, Circle, G, Defs, LinearGradient as SvgLinearGradient, Stop, } from 'react-native-svg';
13
13
  import TouchableScale from '../TouchableScale';
14
14
  import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
15
15
  import { getEventCategory, } from '../../helpers';
16
- import { SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
16
+ import { LayersIcon, CheckIcon, SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
17
17
  const AnalyticsHeader = React.memo(() => {
18
18
  const { t } = useTranslation();
19
19
  const { analyticsEvents, filteredAnalyticsEvents, analyticsFilters, setAnalyticsFilters, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
@@ -87,17 +87,36 @@ const AnalyticsHeader = React.memo(() => {
87
87
  barWidthPct: Math.round((count / maxVal) * 100),
88
88
  }));
89
89
  }, [filteredAnalyticsEvents]);
90
- // Toggle category filter from top button group
90
+ // Multi-select category toggle from top button group
91
91
  const toggleCategoryFilter = (cat) => {
92
92
  animateNextLayout();
93
93
  setAnalyticsFilters(prev => {
94
- const isSelected = !prev.categories.has('all') && prev.categories.has(cat);
95
- if (isSelected) {
94
+ if (cat === 'all') {
96
95
  return { ...prev, categories: new Set(['all']) };
97
96
  }
97
+ const nextCategories = new Set(prev.categories);
98
+ if (nextCategories.has('all')) {
99
+ nextCategories.delete('all');
100
+ nextCategories.add(cat);
101
+ }
98
102
  else {
99
- return { ...prev, categories: new Set([cat]) };
103
+ if (nextCategories.has(cat)) {
104
+ nextCategories.delete(cat);
105
+ if (nextCategories.size === 0) {
106
+ nextCategories.add('all');
107
+ }
108
+ }
109
+ else {
110
+ nextCategories.add(cat);
111
+ if (nextCategories.has('ecommerce') &&
112
+ nextCategories.has('page_view') &&
113
+ nextCategories.has('system') &&
114
+ nextCategories.has('custom')) {
115
+ return { ...prev, categories: new Set(['all']) };
116
+ }
117
+ }
100
118
  }
119
+ return { ...prev, categories: nextCategories };
101
120
  });
102
121
  };
103
122
  // 12-Bucket GA4 Realtime Stacked Histogram & Spline Wave Graph (-30m / 30s to NOW)
@@ -177,7 +196,7 @@ const AnalyticsHeader = React.memo(() => {
177
196
  }, [filteredAnalyticsEvents, chartHeight, svgWidth, t]);
178
197
  const selectedBucket = selectedBucketIdx != null ? histogramData.buckets[selectedBucketIdx] : null;
179
198
  return (<View style={styles.analyticsHeaderCard}>
180
- {/* ── Top Header Row (GA4 Style) ─────────────────────────────────────── */}
199
+ {/* ── 1. Top Header Row (GA4 Style) ─────────────────────────────────── */}
181
200
  <View style={styles.analyticsHeaderTop}>
182
201
  <View style={{ flex: 1 }}>
183
202
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
@@ -217,9 +236,280 @@ const AnalyticsHeader = React.memo(() => {
217
236
  </TouchableOpacity>
218
237
  </View>
219
238
 
220
- {/* ── GA4 Realtime Metric Hero & Activity Chart ─────────────────────── */}
239
+ {/* ── 2. Multi-Select Category Filter Buttons (AT THE TOP) ──────────── */}
240
+ <View style={{ marginTop: 10, marginBottom: 8 }}>
241
+ {/* Multi-color Proportional Segmented Bar */}
242
+ <View style={{
243
+ height: 4.5,
244
+ borderRadius: 2.5,
245
+ backgroundColor: `${AppColors.grayBorderSecondary}40`,
246
+ flexDirection: 'row',
247
+ overflow: 'hidden',
248
+ marginBottom: 8,
249
+ }}>
250
+ {categoryStats.ecommerce > 0 && (<View style={{
251
+ flex: categoryStats.ecommerce,
252
+ backgroundColor: AppColors.amber500,
253
+ }}/>)}
254
+ {categoryStats.page_view > 0 && (<View style={{
255
+ flex: categoryStats.page_view,
256
+ backgroundColor: AppColors.sky500,
257
+ }}/>)}
258
+ {categoryStats.system > 0 && (<View style={{
259
+ flex: categoryStats.system,
260
+ backgroundColor: AppColors.purple,
261
+ }}/>)}
262
+ {categoryStats.custom > 0 && (<View style={{
263
+ flex: categoryStats.custom,
264
+ backgroundColor: AppColors.brandPurple,
265
+ }}/>)}
266
+ </View>
267
+
268
+ {/* Multi-Select Category Buttons with Checkboxes */}
269
+ <View style={{
270
+ flexDirection: 'row',
271
+ flexWrap: 'wrap',
272
+ gap: 6,
273
+ }}>
274
+ {/* All Button */}
275
+ {(() => {
276
+ const isAllSelected = analyticsFilters.categories.has('all');
277
+ return (<TouchableScale onPress={() => toggleCategoryFilter('all')} style={[
278
+ {
279
+ flexDirection: 'row',
280
+ alignItems: 'center',
281
+ gap: 5,
282
+ backgroundColor: isAllSelected ? AppColors.indigo600Alt : AppColors.indigo50,
283
+ paddingHorizontal: 8,
284
+ paddingVertical: 5,
285
+ borderRadius: 7,
286
+ borderWidth: 1,
287
+ borderColor: isAllSelected ? AppColors.indigo600 : AppColors.indigo400,
288
+ },
289
+ isAllSelected && {
290
+ shadowColor: AppColors.indigo600Alt,
291
+ shadowOffset: { width: 0, height: 1 },
292
+ shadowOpacity: 0.28,
293
+ shadowRadius: 2.5,
294
+ elevation: 2,
295
+ },
296
+ ]}>
297
+ <View style={{
298
+ width: 12,
299
+ height: 12,
300
+ borderRadius: 3,
301
+ borderWidth: 1.2,
302
+ borderColor: isAllSelected ? AppColors.white : AppColors.indigo400,
303
+ backgroundColor: isAllSelected ? AppColors.white : 'transparent',
304
+ alignItems: 'center',
305
+ justifyContent: 'center',
306
+ }}>
307
+ {isAllSelected && <CheckIcon color={AppColors.indigo600Alt} size={8}/>}
308
+ </View>
309
+ <LayersIcon color={isAllSelected ? AppColors.white : AppColors.indigo600Alt} size={11}/>
310
+ <Text style={{
311
+ fontFamily: AppFonts.interBold,
312
+ fontSize: 10,
313
+ color: isAllSelected ? AppColors.white : AppColors.indigo600Alt,
314
+ }}>
315
+ {t('analytics.allCategory')}: {categoryStats.totalCount}
316
+ </Text>
317
+ </TouchableScale>);
318
+ })()}
319
+
320
+ {/* Ecommerce Button */}
321
+ {(() => {
322
+ const isSelected = !analyticsFilters.categories.has('all') &&
323
+ analyticsFilters.categories.has('ecommerce');
324
+ return (<TouchableScale onPress={() => toggleCategoryFilter('ecommerce')} style={[
325
+ {
326
+ flexDirection: 'row',
327
+ alignItems: 'center',
328
+ gap: 5,
329
+ backgroundColor: isSelected ? AppColors.amber600 : AppColors.amber100,
330
+ paddingHorizontal: 8,
331
+ paddingVertical: 5,
332
+ borderRadius: 7,
333
+ borderWidth: 1,
334
+ borderColor: isSelected ? AppColors.amber700 : AppColors.amber200,
335
+ },
336
+ isSelected && {
337
+ shadowColor: AppColors.amber600,
338
+ shadowOffset: { width: 0, height: 1 },
339
+ shadowOpacity: 0.28,
340
+ shadowRadius: 2.5,
341
+ elevation: 2,
342
+ },
343
+ ]}>
344
+ <View style={{
345
+ width: 12,
346
+ height: 12,
347
+ borderRadius: 3,
348
+ borderWidth: 1.2,
349
+ borderColor: isSelected ? AppColors.white : AppColors.amber500,
350
+ backgroundColor: isSelected ? AppColors.white : 'transparent',
351
+ alignItems: 'center',
352
+ justifyContent: 'center',
353
+ }}>
354
+ {isSelected && <CheckIcon color={AppColors.amber600} size={8}/>}
355
+ </View>
356
+ <CartIcon color={isSelected ? AppColors.white : AppColors.amber700} size={11}/>
357
+ <Text style={{
358
+ fontFamily: AppFonts.interBold,
359
+ fontSize: 10,
360
+ color: isSelected ? AppColors.white : AppColors.amber800Warm,
361
+ }}>
362
+ {t('analytics.ecommerceCategory')}: {categoryStats.ecommerce} ({categoryStats.ecommercePct}%)
363
+ </Text>
364
+ </TouchableScale>);
365
+ })()}
366
+
367
+ {/* Screens Button */}
368
+ {(() => {
369
+ const isSelected = !analyticsFilters.categories.has('all') &&
370
+ analyticsFilters.categories.has('page_view');
371
+ return (<TouchableScale onPress={() => toggleCategoryFilter('page_view')} style={[
372
+ {
373
+ flexDirection: 'row',
374
+ alignItems: 'center',
375
+ gap: 5,
376
+ backgroundColor: isSelected ? AppColors.sky600 : AppColors.sky100,
377
+ paddingHorizontal: 8,
378
+ paddingVertical: 5,
379
+ borderRadius: 7,
380
+ borderWidth: 1,
381
+ borderColor: isSelected ? AppColors.blue700 : AppColors.sky400,
382
+ },
383
+ isSelected && {
384
+ shadowColor: AppColors.sky600,
385
+ shadowOffset: { width: 0, height: 1 },
386
+ shadowOpacity: 0.28,
387
+ shadowRadius: 2.5,
388
+ elevation: 2,
389
+ },
390
+ ]}>
391
+ <View style={{
392
+ width: 12,
393
+ height: 12,
394
+ borderRadius: 3,
395
+ borderWidth: 1.2,
396
+ borderColor: isSelected ? AppColors.white : AppColors.sky400,
397
+ backgroundColor: isSelected ? AppColors.white : 'transparent',
398
+ alignItems: 'center',
399
+ justifyContent: 'center',
400
+ }}>
401
+ {isSelected && <CheckIcon color={AppColors.sky600} size={8}/>}
402
+ </View>
403
+ <GlobeIcon color={isSelected ? AppColors.white : AppColors.sky600} size={11}/>
404
+ <Text style={{
405
+ fontFamily: AppFonts.interBold,
406
+ fontSize: 10,
407
+ color: isSelected ? AppColors.white : AppColors.sky600,
408
+ }}>
409
+ {t('analytics.screensCategory')}: {categoryStats.page_view} ({categoryStats.page_viewPct}%)
410
+ </Text>
411
+ </TouchableScale>);
412
+ })()}
413
+
414
+ {/* System Button */}
415
+ {(() => {
416
+ const isSelected = !analyticsFilters.categories.has('all') &&
417
+ analyticsFilters.categories.has('system');
418
+ return (<TouchableScale onPress={() => toggleCategoryFilter('system')} style={[
419
+ {
420
+ flexDirection: 'row',
421
+ alignItems: 'center',
422
+ gap: 5,
423
+ backgroundColor: isSelected ? AppColors.violet600 : AppColors.purple100,
424
+ paddingHorizontal: 8,
425
+ paddingVertical: 5,
426
+ borderRadius: 7,
427
+ borderWidth: 1,
428
+ borderColor: isSelected ? AppColors.purpleShade700 : AppColors.purple200,
429
+ },
430
+ isSelected && {
431
+ shadowColor: AppColors.violet600,
432
+ shadowOffset: { width: 0, height: 1 },
433
+ shadowOpacity: 0.28,
434
+ shadowRadius: 2.5,
435
+ elevation: 2,
436
+ },
437
+ ]}>
438
+ <View style={{
439
+ width: 12,
440
+ height: 12,
441
+ borderRadius: 3,
442
+ borderWidth: 1.2,
443
+ borderColor: isSelected ? AppColors.white : AppColors.purple400,
444
+ backgroundColor: isSelected ? AppColors.white : 'transparent',
445
+ alignItems: 'center',
446
+ justifyContent: 'center',
447
+ }}>
448
+ {isSelected && <CheckIcon color={AppColors.violet600} size={8}/>}
449
+ </View>
450
+ <BoltIcon color={isSelected ? AppColors.white : AppColors.purple} size={11}/>
451
+ <Text style={{
452
+ fontFamily: AppFonts.interBold,
453
+ fontSize: 10,
454
+ color: isSelected ? AppColors.white : AppColors.brandPurple,
455
+ }}>
456
+ {t('analytics.systemCategory')}: {categoryStats.system} ({categoryStats.systemPct}%)
457
+ </Text>
458
+ </TouchableScale>);
459
+ })()}
460
+
461
+ {/* Custom Button */}
462
+ {(() => {
463
+ const isSelected = !analyticsFilters.categories.has('all') &&
464
+ analyticsFilters.categories.has('custom');
465
+ return (<TouchableScale onPress={() => toggleCategoryFilter('custom')} style={[
466
+ {
467
+ flexDirection: 'row',
468
+ alignItems: 'center',
469
+ gap: 5,
470
+ backgroundColor: isSelected ? AppColors.pink600 : AppColors.pink100,
471
+ paddingHorizontal: 8,
472
+ paddingVertical: 5,
473
+ borderRadius: 7,
474
+ borderWidth: 1,
475
+ borderColor: isSelected ? AppColors.rose700 : AppColors.roseBorder,
476
+ },
477
+ isSelected && {
478
+ shadowColor: AppColors.pink600,
479
+ shadowOffset: { width: 0, height: 1 },
480
+ shadowOpacity: 0.28,
481
+ shadowRadius: 2.5,
482
+ elevation: 2,
483
+ },
484
+ ]}>
485
+ <View style={{
486
+ width: 12,
487
+ height: 12,
488
+ borderRadius: 3,
489
+ borderWidth: 1.2,
490
+ borderColor: isSelected ? AppColors.white : AppColors.pink400,
491
+ backgroundColor: isSelected ? AppColors.white : 'transparent',
492
+ alignItems: 'center',
493
+ justifyContent: 'center',
494
+ }}>
495
+ {isSelected && <CheckIcon color={AppColors.pink600} size={8}/>}
496
+ </View>
497
+ <SparkleIcon color={isSelected ? AppColors.white : AppColors.brandPurple} size={11}/>
498
+ <Text style={{
499
+ fontFamily: AppFonts.interBold,
500
+ fontSize: 10,
501
+ color: isSelected ? AppColors.white : AppColors.pink600,
502
+ }}>
503
+ {t('analytics.customCategory')}: {categoryStats.custom} ({categoryStats.customPct}%)
504
+ </Text>
505
+ </TouchableScale>);
506
+ })()}
507
+ </View>
508
+ </View>
509
+
510
+ {/* ── 3. GA4 Realtime Metric Hero & Activity Chart (UNDER CATEGORIES) ─ */}
221
511
  <View style={{
222
- marginTop: 10,
512
+ marginTop: 2,
223
513
  marginBottom: 8,
224
514
  backgroundColor: AppColors.grayBackground,
225
515
  borderRadius: 10,
@@ -299,41 +589,55 @@ const AnalyticsHeader = React.memo(() => {
299
589
  </View>
300
590
  </View>
301
591
 
302
- {/* ── High Precision SVG Spline Area Graph ── */}
592
+ {/* ── Crashlytics-Style Multi-Bar Histogram with Trendline Overlay ── */}
303
593
  <View style={{ height: chartHeight, position: 'relative' }}>
304
594
  <Svg width="100%" height={chartHeight} viewBox={`0 0 ${svgWidth} ${chartHeight}`}>
305
595
  <Defs>
306
- <SvgLinearGradient id="telemetryAreaGrad" x1="0" y1="0" x2="0" y2="1">
307
- <Stop offset="0%" stopColor={AppColors.brandPurple} stopOpacity="0.4"/>
308
- <Stop offset="80%" stopColor={AppColors.brandPurple} stopOpacity="0.05"/>
309
- <Stop offset="100%" stopColor={AppColors.brandPurple} stopOpacity="0.0"/>
596
+ <SvgLinearGradient id="crashlyticsBarGrad" x1="0" y1="0" x2="0" y2="1">
597
+ <Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.9"/>
598
+ <Stop offset="100%" stopColor="#6366F1" stopOpacity="0.6"/>
599
+ </SvgLinearGradient>
600
+ <SvgLinearGradient id="crashlyticsAreaGrad" x1="0" y1="0" x2="0" y2="1">
601
+ <Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.25"/>
602
+ <Stop offset="80%" stopColor="#8B5CF6" stopOpacity="0.04"/>
603
+ <Stop offset="100%" stopColor="#8B5CF6" stopOpacity="0.0"/>
310
604
  </SvgLinearGradient>
311
605
  </Defs>
312
606
 
313
- {/* Baseline & Grid lines */}
314
- <Line x1="8" y1={chartHeight - 6} x2={svgWidth - 8} y2={chartHeight - 6} stroke={AppColors.dividerColor} strokeWidth="1"/>
315
- <Line x1="8" y1={chartHeight / 2} x2={svgWidth - 8} y2={chartHeight / 2} stroke={AppColors.dividerColor} strokeWidth="0.8" strokeDasharray="3,3"/>
607
+ {/* Subtle Grid Guidelines */}
608
+ <Line x1="6" y1={chartHeight - 6} x2={svgWidth - 6} y2={chartHeight - 6} stroke="#E2E8F0" strokeWidth="1"/>
609
+ <Line x1="6" y1={chartHeight / 2} x2={svgWidth - 6} y2={chartHeight / 2} stroke="#F1F5F9" strokeWidth="1" strokeDasharray="4,4"/>
316
610
 
317
- {/* Glowing Area Fill */}
318
- <Path d={histogramData.areaPath} fill="url(#telemetryAreaGrad)"/>
611
+ {/* Glowing Area Underlay */}
612
+ <Path d={histogramData.areaPath} fill="url(#crashlyticsAreaGrad)"/>
319
613
 
320
- {/* Smooth Spline Wave Stroke */}
321
- <Path d={histogramData.linePath} fill="none" stroke={AppColors.brandPurple} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
322
-
323
- {/* Data Points & Active Bucket Indicators */}
614
+ {/* Vertical Crashlytics Histogram Bars */}
324
615
  {histogramData.buckets.map((b, idx) => {
325
616
  const isSelected = selectedBucketIdx === idx;
326
- return (<G key={b.index}>
327
- {isSelected && (<Line x1={b.x} y1={4} x2={b.x} y2={chartHeight - 6} stroke={AppColors.brandPurple} strokeWidth="1.2" strokeDasharray="2,2"/>)}
617
+ const barWidth = 10;
618
+ const barX = b.x - barWidth / 2;
619
+ const barHeight = Math.max(3, chartHeight - 8 - b.y);
620
+ const barY = chartHeight - 8 - barHeight;
621
+ return (<G key={`bar-${b.index}`}>
622
+ {/* Background slot */}
623
+ <Rect x={barX} y={6} width={barWidth} height={chartHeight - 14} rx={3} fill={isSelected ? '#EDE9FE' : '#F8FAFC'}/>
328
624
 
329
- {b.total > 0 && (<Circle cx={b.x} cy={b.y} r={isSelected ? 4.5 : 2.8} fill={isSelected ? AppColors.brandPurple : AppColors.white} stroke={AppColors.brandPurple} strokeWidth={isSelected ? 2 : 1.5}/>)}
625
+ {/* Active Event Bar */}
626
+ {b.total > 0 && (<Rect x={barX} y={barY} width={barWidth} height={barHeight} rx={3} fill="url(#crashlyticsBarGrad)"/>)}
330
627
 
331
- {b.isLatest && (<G>
332
- <Circle cx={b.x} cy={b.y} r="6.5" fill={AppColors.brandPurple} opacity="0.22"/>
333
- <Circle cx={b.x} cy={b.y} r="3" fill={AppColors.brandPurple}/>
334
- </G>)}
628
+ {/* Selected Cursor Line */}
629
+ {isSelected && (<Line x1={b.x} y1={4} x2={b.x} y2={chartHeight - 6} stroke="#8B5CF6" strokeWidth="1.5" strokeDasharray="2,2"/>)}
335
630
  </G>);
336
631
  })}
632
+
633
+ {/* Crashlytics Apex Trendline */}
634
+ <Path d={histogramData.linePath} fill="none" stroke="#8B5CF6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
635
+
636
+ {/* Pulsing Dot on Current Active Time Bucket */}
637
+ {histogramData.buckets.length > 0 && (<G>
638
+ <Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="5.5" fill="#8B5CF6" opacity="0.25"/>
639
+ <Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="3" fill="#7C3AED"/>
640
+ </G>)}
337
641
  </Svg>
338
642
 
339
643
  {/* Invisible Touch Columns for Interactive Bucket Selection */}
@@ -423,199 +727,6 @@ const AnalyticsHeader = React.memo(() => {
423
727
  </View>
424
728
  </View>
425
729
 
426
- {/* ── Category Breakdown Distribution Bar & Interactive Filter Buttons ─ */}
427
- <View style={{ marginTop: 2, marginBottom: 4 }}>
428
- {/* Multi-color Proportional Segmented Bar */}
429
- <View style={{
430
- height: 5,
431
- borderRadius: 2.5,
432
- backgroundColor: `${AppColors.grayBorderSecondary}40`,
433
- flexDirection: 'row',
434
- overflow: 'hidden',
435
- marginBottom: 8,
436
- }}>
437
- {categoryStats.ecommerce > 0 && (<View style={{
438
- flex: categoryStats.ecommerce,
439
- backgroundColor: AppColors.amber500,
440
- }}/>)}
441
- {categoryStats.page_view > 0 && (<View style={{
442
- flex: categoryStats.page_view,
443
- backgroundColor: AppColors.sky500,
444
- }}/>)}
445
- {categoryStats.system > 0 && (<View style={{
446
- flex: categoryStats.system,
447
- backgroundColor: AppColors.purple,
448
- }}/>)}
449
- {categoryStats.custom > 0 && (<View style={{
450
- flex: categoryStats.custom,
451
- backgroundColor: AppColors.brandPurple,
452
- }}/>)}
453
- </View>
454
-
455
- {/* 4 Interactive Category Filter Buttons */}
456
- <View style={{
457
- flexDirection: 'row',
458
- flexWrap: 'wrap',
459
- gap: 6,
460
- }}>
461
- {/* Ecommerce Button */}
462
- {(() => {
463
- const isSelected = !analyticsFilters.categories.has('all') &&
464
- analyticsFilters.categories.has('ecommerce');
465
- return (<TouchableScale onPress={() => toggleCategoryFilter('ecommerce')} style={[
466
- {
467
- flexDirection: 'row',
468
- alignItems: 'center',
469
- gap: 4.5,
470
- backgroundColor: isSelected
471
- ? AppColors.amber500
472
- : `${AppColors.amber500}14`,
473
- paddingHorizontal: 7,
474
- paddingVertical: 4.5,
475
- borderRadius: 6,
476
- borderWidth: 1,
477
- borderColor: isSelected
478
- ? AppColors.amber500
479
- : `${AppColors.amber500}35`,
480
- },
481
- isSelected && {
482
- shadowColor: AppColors.amber500,
483
- shadowOffset: { width: 0, height: 1 },
484
- shadowOpacity: 0.25,
485
- shadowRadius: 2,
486
- elevation: 2,
487
- },
488
- ]}>
489
- <CartIcon color={isSelected ? AppColors.white : AppColors.amber700} size={11}/>
490
- <Text style={{
491
- fontFamily: AppFonts.interBold,
492
- fontSize: 10,
493
- color: isSelected ? AppColors.white : AppColors.amber700,
494
- }}>
495
- Ecommerce: {categoryStats.ecommerce} ({categoryStats.ecommercePct}%)
496
- </Text>
497
- </TouchableScale>);
498
- })()}
499
-
500
- {/* Screens Button */}
501
- {(() => {
502
- const isSelected = !analyticsFilters.categories.has('all') &&
503
- analyticsFilters.categories.has('page_view');
504
- return (<TouchableScale onPress={() => toggleCategoryFilter('page_view')} style={[
505
- {
506
- flexDirection: 'row',
507
- alignItems: 'center',
508
- gap: 4.5,
509
- backgroundColor: isSelected
510
- ? AppColors.sky600
511
- : `${AppColors.sky500}14`,
512
- paddingHorizontal: 7,
513
- paddingVertical: 4.5,
514
- borderRadius: 6,
515
- borderWidth: 1,
516
- borderColor: isSelected
517
- ? AppColors.sky600
518
- : `${AppColors.sky500}35`,
519
- },
520
- isSelected && {
521
- shadowColor: AppColors.sky600,
522
- shadowOffset: { width: 0, height: 1 },
523
- shadowOpacity: 0.25,
524
- shadowRadius: 2,
525
- elevation: 2,
526
- },
527
- ]}>
528
- <GlobeIcon color={isSelected ? AppColors.white : AppColors.sky600} size={11}/>
529
- <Text style={{
530
- fontFamily: AppFonts.interBold,
531
- fontSize: 10,
532
- color: isSelected ? AppColors.white : AppColors.sky600,
533
- }}>
534
- Screens: {categoryStats.page_view} ({categoryStats.page_viewPct}%)
535
- </Text>
536
- </TouchableScale>);
537
- })()}
538
-
539
- {/* System Button */}
540
- {(() => {
541
- const isSelected = !analyticsFilters.categories.has('all') &&
542
- analyticsFilters.categories.has('system');
543
- return (<TouchableScale onPress={() => toggleCategoryFilter('system')} style={[
544
- {
545
- flexDirection: 'row',
546
- alignItems: 'center',
547
- gap: 4.5,
548
- backgroundColor: isSelected
549
- ? AppColors.purple
550
- : `${AppColors.purple}14`,
551
- paddingHorizontal: 7,
552
- paddingVertical: 4.5,
553
- borderRadius: 6,
554
- borderWidth: 1,
555
- borderColor: isSelected
556
- ? AppColors.purple
557
- : `${AppColors.purple}35`,
558
- },
559
- isSelected && {
560
- shadowColor: AppColors.purple,
561
- shadowOffset: { width: 0, height: 1 },
562
- shadowOpacity: 0.25,
563
- shadowRadius: 2,
564
- elevation: 2,
565
- },
566
- ]}>
567
- <BoltIcon color={isSelected ? AppColors.white : AppColors.purple} size={11}/>
568
- <Text style={{
569
- fontFamily: AppFonts.interBold,
570
- fontSize: 10,
571
- color: isSelected ? AppColors.white : AppColors.purple,
572
- }}>
573
- System: {categoryStats.system} ({categoryStats.systemPct}%)
574
- </Text>
575
- </TouchableScale>);
576
- })()}
577
-
578
- {/* Custom Button */}
579
- {(() => {
580
- const isSelected = !analyticsFilters.categories.has('all') &&
581
- analyticsFilters.categories.has('custom');
582
- return (<TouchableScale onPress={() => toggleCategoryFilter('custom')} style={[
583
- {
584
- flexDirection: 'row',
585
- alignItems: 'center',
586
- gap: 4.5,
587
- backgroundColor: isSelected
588
- ? AppColors.brandPurple
589
- : `${AppColors.brandPurple}14`,
590
- paddingHorizontal: 7,
591
- paddingVertical: 4.5,
592
- borderRadius: 6,
593
- borderWidth: 1,
594
- borderColor: isSelected
595
- ? AppColors.brandPurple
596
- : `${AppColors.brandPurple}35`,
597
- },
598
- isSelected && {
599
- shadowColor: AppColors.brandPurple,
600
- shadowOffset: { width: 0, height: 1 },
601
- shadowOpacity: 0.25,
602
- shadowRadius: 2,
603
- elevation: 2,
604
- },
605
- ]}>
606
- <SparkleIcon color={isSelected ? AppColors.white : AppColors.brandPurple} size={11}/>
607
- <Text style={{
608
- fontFamily: AppFonts.interBold,
609
- fontSize: 10,
610
- color: isSelected ? AppColors.white : AppColors.brandPurple,
611
- }}>
612
- Custom: {categoryStats.custom} ({categoryStats.customPct}%)
613
- </Text>
614
- </TouchableScale>);
615
- })()}
616
- </View>
617
- </View>
618
-
619
730
  {/* ── Top Screens / Views Mini Leaderboard (GA4 Realtime Overview) ──── */}
620
731
  {topScreens.length > 0 && (<View style={{
621
732
  marginTop: 8,