react-native-inapp-inspector 1.1.27 → 1.1.29

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 (100) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -46
  2. package/dist/commonjs/components/AppHeaderLogo.js +12 -15
  3. package/dist/commonjs/components/ConsoleLogCard.js +31 -7
  4. package/dist/commonjs/components/CopyButton.js +15 -1
  5. package/dist/commonjs/components/DomainHeader.js +56 -28
  6. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  7. package/dist/commonjs/components/ErrorBoundary.js +530 -100
  8. package/dist/commonjs/components/HighlightText.js +7 -1
  9. package/dist/commonjs/components/Inspector/AnalyticsTab.js +190 -126
  10. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
  11. package/dist/commonjs/components/Inspector/BundleTab.js +145 -21
  12. package/dist/commonjs/components/Inspector/CrashTab.js +0 -8
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +17 -3
  14. package/dist/commonjs/components/Inspector/LogDetail.js +8 -5
  15. package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
  16. package/dist/commonjs/components/Inspector/NetworkDetail.js +65 -24
  17. package/dist/commonjs/components/Inspector/PerformanceTab.d.ts +5 -0
  18. package/dist/commonjs/components/Inspector/PerformanceTab.js +588 -155
  19. package/dist/commonjs/components/Inspector/SettingsPanel.js +57 -22
  20. package/dist/commonjs/components/Inspector/TabBar.js +13 -1
  21. package/dist/commonjs/components/JsonViewer.js +25 -3
  22. package/dist/commonjs/components/LogCard.js +173 -40
  23. package/dist/commonjs/components/NetworkIcons.d.ts +112 -102
  24. package/dist/commonjs/components/Toast.d.ts +3 -0
  25. package/dist/commonjs/components/Toast.js +152 -0
  26. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  27. package/dist/commonjs/components/TouchableScale.js +3 -3
  28. package/dist/commonjs/components/TreeNode.js +1 -1
  29. package/dist/commonjs/constants/index.js +6 -6
  30. package/dist/commonjs/constants/version.d.ts +1 -1
  31. package/dist/commonjs/constants/version.js +1 -1
  32. package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -11
  33. package/dist/commonjs/customHooks/analyticsLogger.js +8 -1
  34. package/dist/commonjs/customHooks/bundleAnalyzer.js +121 -124
  35. package/dist/commonjs/customHooks/networkLogger.d.ts +1 -0
  36. package/dist/commonjs/customHooks/networkLogger.js +16 -0
  37. package/dist/commonjs/customHooks/performanceTracker.d.ts +74 -0
  38. package/dist/commonjs/customHooks/performanceTracker.js +546 -392
  39. package/dist/commonjs/customHooks/reduxLogger.d.ts +1 -0
  40. package/dist/commonjs/customHooks/reduxLogger.js +3 -1
  41. package/dist/commonjs/helpers/index.d.ts +4 -0
  42. package/dist/commonjs/helpers/index.js +92 -69
  43. package/dist/commonjs/helpers/toast.d.ts +4 -0
  44. package/dist/commonjs/helpers/toast.js +20 -0
  45. package/dist/commonjs/i18n/locales/en.json +229 -5
  46. package/dist/commonjs/index.d.ts +1 -1
  47. package/dist/commonjs/index.js +36 -2
  48. package/dist/commonjs/styles/index.d.ts +63 -15
  49. package/dist/commonjs/styles/index.js +79 -32
  50. package/dist/commonjs/types/interfaces.d.ts +3 -0
  51. package/dist/esm/components/AppHeaderLogo.js +12 -15
  52. package/dist/esm/components/ConsoleLogCard.js +33 -9
  53. package/dist/esm/components/CopyButton.js +15 -1
  54. package/dist/esm/components/DomainHeader.js +56 -28
  55. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  56. package/dist/esm/components/ErrorBoundary.js +498 -101
  57. package/dist/esm/components/HighlightText.js +7 -1
  58. package/dist/esm/components/Inspector/AnalyticsTab.js +191 -127
  59. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
  60. package/dist/esm/components/Inspector/BundleTab.js +145 -23
  61. package/dist/esm/components/Inspector/CrashTab.js +0 -8
  62. package/dist/esm/components/Inspector/InspectorHeader.js +18 -4
  63. package/dist/esm/components/Inspector/LogDetail.js +10 -7
  64. package/dist/esm/components/Inspector/MainScreen.js +4 -0
  65. package/dist/esm/components/Inspector/NetworkDetail.js +66 -25
  66. package/dist/esm/components/Inspector/PerformanceTab.d.ts +5 -0
  67. package/dist/esm/components/Inspector/PerformanceTab.js +591 -158
  68. package/dist/esm/components/Inspector/SettingsPanel.js +57 -22
  69. package/dist/esm/components/Inspector/TabBar.js +13 -1
  70. package/dist/esm/components/JsonViewer.js +25 -3
  71. package/dist/esm/components/LogCard.js +175 -42
  72. package/dist/esm/components/NetworkIcons.d.ts +112 -102
  73. package/dist/esm/components/Toast.d.ts +3 -0
  74. package/dist/esm/components/Toast.js +117 -0
  75. package/dist/esm/components/TouchableScale.d.ts +1 -0
  76. package/dist/esm/components/TouchableScale.js +3 -3
  77. package/dist/esm/components/TreeNode.js +1 -1
  78. package/dist/esm/constants/index.js +6 -6
  79. package/dist/esm/constants/version.d.ts +1 -1
  80. package/dist/esm/constants/version.js +1 -1
  81. package/dist/esm/customHooks/analyticsLogger.d.ts +1 -11
  82. package/dist/esm/customHooks/analyticsLogger.js +6 -0
  83. package/dist/esm/customHooks/bundleAnalyzer.js +121 -124
  84. package/dist/esm/customHooks/networkLogger.d.ts +1 -0
  85. package/dist/esm/customHooks/networkLogger.js +16 -0
  86. package/dist/esm/customHooks/performanceTracker.d.ts +74 -0
  87. package/dist/esm/customHooks/performanceTracker.js +531 -391
  88. package/dist/esm/customHooks/reduxLogger.d.ts +1 -0
  89. package/dist/esm/customHooks/reduxLogger.js +1 -0
  90. package/dist/esm/helpers/index.d.ts +4 -0
  91. package/dist/esm/helpers/index.js +87 -69
  92. package/dist/esm/helpers/toast.d.ts +4 -0
  93. package/dist/esm/helpers/toast.js +15 -0
  94. package/dist/esm/i18n/locales/en.json +229 -5
  95. package/dist/esm/index.d.ts +1 -1
  96. package/dist/esm/index.js +24 -3
  97. package/dist/esm/styles/index.d.ts +63 -15
  98. package/dist/esm/styles/index.js +79 -32
  99. package/dist/esm/types/interfaces.d.ts +3 -0
  100. package/package.json +4 -1
@@ -3,6 +3,12 @@ import { Text } from 'react-native';
3
3
  // Helpers
4
4
  import { escapeRegex, handleOpenExternalLink } from '../helpers';
5
5
  const HighlightText = React.memo(function HighlightText({ text, search, style, highlightStyle, detectLinks, numberOfLines, ...rest }) {
6
+ // Optimization: For huge strings (> 20KB) with no active search, bypass all splitting/regex overhead
7
+ if (!text || (text.length > 20000 && !search && !detectLinks)) {
8
+ return (<Text style={style} numberOfLines={numberOfLines} {...rest}>
9
+ {text}
10
+ </Text>);
11
+ }
6
12
  // Regex to detect absolute URLs
7
13
  const URL_REGEX = /(https?:\/\/[^\s]+)/g;
8
14
  const renderNormalOrHighlighted = (subText, keyPrefix) => {
@@ -17,7 +23,7 @@ const HighlightText = React.memo(function HighlightText({ text, search, style, h
17
23
  </Text>) : (<Text key={i}>{part}</Text>))}
18
24
  </Text>);
19
25
  };
20
- if (detectLinks && text) {
26
+ if (detectLinks && text && text.length < 20000) {
21
27
  const parts = text.split(URL_REGEX);
22
28
  return (<Text style={style} numberOfLines={numberOfLines} {...rest}>
23
29
  {parts.map((part, i) => {
@@ -8,11 +8,14 @@ import EndOfListFooter from '../EndOfListFooter';
8
8
  import styles from '../../styles';
9
9
  import { AppColors } from '../../styles/AppColors';
10
10
  import { AppFonts } from '../../styles/AppFonts';
11
+ import { useTranslation } from '../../i18n';
12
+ import Svg, { Path, Rect, Line, Circle, G, Defs, LinearGradient as SvgLinearGradient, Stop, } from 'react-native-svg';
11
13
  import TouchableScale from '../TouchableScale';
12
14
  import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
13
15
  import { getEventCategory, } from '../../helpers';
14
- import { SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, TargetGoalIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
16
+ import { SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
15
17
  const AnalyticsHeader = React.memo(() => {
18
+ const { t } = useTranslation();
16
19
  const { analyticsEvents, filteredAnalyticsEvents, analyticsFilters, setAnalyticsFilters, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
17
20
  const userId = getCurrentUserId();
18
21
  const userProperties = getCurrentUserProperties();
@@ -97,20 +100,31 @@ const AnalyticsHeader = React.memo(() => {
97
100
  }
98
101
  });
99
102
  };
100
- // 12-Bucket GA4 Realtime Stacked Histogram Graph (-30m / 30s to NOW)
103
+ // 12-Bucket GA4 Realtime Stacked Histogram & Spline Wave Graph (-30m / 30s to NOW)
101
104
  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
- }));
105
+ const chartHeight = 64;
106
+ const svgWidth = 320;
107
+ const [selectedBucketIdx, setSelectedBucketIdx] = useState(null);
108
+ const histogramData = useMemo(() => {
109
+ const buckets = [];
111
110
  const now = Date.now();
112
111
  const windowMs = 60000; // 60s window
113
112
  const bucketDuration = windowMs / BUCKET_COUNT;
113
+ for (let i = 0; i < BUCKET_COUNT; i++) {
114
+ const bucketAgeSec = Math.round(((BUCKET_COUNT - 1 - i) * bucketDuration) / 1000);
115
+ buckets.push({
116
+ index: i,
117
+ ecommerce: 0,
118
+ page_view: 0,
119
+ system: 0,
120
+ custom: 0,
121
+ total: 0,
122
+ x: (i / (BUCKET_COUNT - 1)) * (svgWidth - 24) + 12,
123
+ y: chartHeight - 12,
124
+ isLatest: i === BUCKET_COUNT - 1,
125
+ ageLabel: i === BUCKET_COUNT - 1 ? t('analytics.timeNow') : `-${bucketAgeSec}s`,
126
+ });
127
+ }
114
128
  for (const e of filteredAnalyticsEvents) {
115
129
  const age = now - e.timestamp;
116
130
  if (age >= 0 && age < windowMs) {
@@ -131,30 +145,43 @@ const AnalyticsHeader = React.memo(() => {
131
145
  }
132
146
  }
133
147
  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
- };
148
+ buckets.forEach(b => {
149
+ const normalizedH = (b.total / maxVal) * (chartHeight - 22);
150
+ b.y = chartHeight - 10 - normalizedH;
150
151
  });
151
- }, [filteredAnalyticsEvents, chartHeight]);
152
+ // Generate Cubic Bézier Spline paths
153
+ let linePath = `M ${buckets[0].x.toFixed(1)} ${buckets[0].y.toFixed(1)}`;
154
+ for (let i = 0; i < buckets.length - 1; i++) {
155
+ const p0 = buckets[Math.max(0, i - 1)];
156
+ const p1 = buckets[i];
157
+ const p2 = buckets[i + 1];
158
+ const p3 = buckets[Math.min(buckets.length - 1, i + 2)];
159
+ const cp1x = p1.x + (p2.x - p0.x) / 6;
160
+ const cp1y = p1.y + (p2.y - p0.y) / 6;
161
+ const cp2x = p2.x - (p3.x - p1.x) / 6;
162
+ const cp2y = p2.y - (p3.y - p1.y) / 6;
163
+ linePath += ` C ${cp1x.toFixed(1)} ${cp1y.toFixed(1)}, ${cp2x.toFixed(1)} ${cp2y.toFixed(1)}, ${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`;
164
+ }
165
+ const lastB = buckets[buckets.length - 1];
166
+ const firstB = buckets[0];
167
+ const areaPath = `${linePath} L ${lastB.x.toFixed(1)} ${chartHeight - 4} L ${firstB.x.toFixed(1)} ${chartHeight - 4} Z`;
168
+ const peakVal = Math.max(...buckets.map(b => b.total), 0);
169
+ const eventRate = Math.round(buckets.reduce((a, b) => a + b.total, 0));
170
+ return {
171
+ buckets,
172
+ linePath,
173
+ areaPath,
174
+ peakVal,
175
+ eventRate,
176
+ };
177
+ }, [filteredAnalyticsEvents, chartHeight, svgWidth, t]);
178
+ const selectedBucket = selectedBucketIdx != null ? histogramData.buckets[selectedBucketIdx] : null;
152
179
  return (<View style={styles.analyticsHeaderCard}>
153
180
  {/* ── Top Header Row (GA4 Style) ─────────────────────────────────────── */}
154
181
  <View style={styles.analyticsHeaderTop}>
155
182
  <View style={{ flex: 1 }}>
156
183
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
157
- <Text style={styles.analyticsHeaderTitle}>Users in last 30 minutes</Text>
184
+ <Text style={styles.analyticsHeaderTitle}>{t('analytics.realtimeStream')}</Text>
158
185
  <View style={[
159
186
  styles.statusDot,
160
187
  {
@@ -172,11 +199,11 @@ const AnalyticsHeader = React.memo(() => {
172
199
  fontFamily: AppFonts.interBold,
173
200
  },
174
201
  ]}>
175
- {isTrackingEnabled ? 'Realtime' : 'Paused'}
202
+ {isTrackingEnabled ? t('performance.live') : t('common.pause')}
176
203
  </Text>
177
204
  </View>
178
205
  <Text style={styles.analyticsHeaderSubtitle} numberOfLines={1}>
179
- {userId ? `User: ${userId}` : 'Anonymous Session'}
206
+ {userId ? `${t('analytics.userId')}: ${userId}` : t('analytics.emptySubtitle')}
180
207
  </Text>
181
208
  </View>
182
209
 
@@ -185,7 +212,7 @@ const AnalyticsHeader = React.memo(() => {
185
212
  setAnalyticsHeaderExpanded(!analyticsHeaderExpanded);
186
213
  }}>
187
214
  <Text style={styles.analyticsHeaderToggleText}>
188
- {analyticsHeaderExpanded ? 'Hide Info' : 'Session Info'}
215
+ {analyticsHeaderExpanded ? t('common.close') : t('analytics.eventDetails')}
189
216
  </Text>
190
217
  </TouchableOpacity>
191
218
  </View>
@@ -205,7 +232,7 @@ const AnalyticsHeader = React.memo(() => {
205
232
  flexDirection: 'row',
206
233
  justifyContent: 'space-between',
207
234
  alignItems: 'flex-start',
208
- marginBottom: 8,
235
+ marginBottom: 6,
209
236
  }}>
210
237
  <View>
211
238
  <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 6 }}>
@@ -222,13 +249,33 @@ const AnalyticsHeader = React.memo(() => {
222
249
  fontSize: 10.5,
223
250
  color: AppColors.grayTextWeak,
224
251
  }}>
225
- events in window
252
+ {t('analytics.eventsInWindow')}
226
253
  </Text>
227
254
  </View>
228
255
  </View>
229
256
 
230
- {/* Realtime Badges */}
257
+ {/* Realtime Rate Badges */}
231
258
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
259
+ <View style={{
260
+ flexDirection: 'row',
261
+ alignItems: 'center',
262
+ gap: 3,
263
+ backgroundColor: `${AppColors.purple}14`,
264
+ paddingHorizontal: 6,
265
+ paddingVertical: 2.5,
266
+ borderRadius: 4,
267
+ borderWidth: 1,
268
+ borderColor: `${AppColors.purple}2E`,
269
+ }}>
270
+ <Text style={{
271
+ fontFamily: AppFonts.interBold,
272
+ fontSize: 9.5,
273
+ color: AppColors.purple,
274
+ }}>
275
+ {t('analytics.eventVelocity', { rate: histogramData.eventRate })}
276
+ </Text>
277
+ </View>
278
+
232
279
  {categoryStats.totalRevenue > 0 && (<View style={{
233
280
  flexDirection: 'row',
234
281
  alignItems: 'center',
@@ -249,127 +296,143 @@ const AnalyticsHeader = React.memo(() => {
249
296
  ${categoryStats.totalRevenue.toFixed(2)}
250
297
  </Text>
251
298
  </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
299
  </View>
273
300
  </View>
274
301
 
275
- {/* GA4 Stacked Realtime Histogram Bar Graph */}
276
- <View style={{
277
- height: chartHeight,
302
+ {/* ── Crashlytics-Style Multi-Bar Histogram with Trendline Overlay ── */}
303
+ <View style={{ height: chartHeight, position: 'relative' }}>
304
+ <Svg width="100%" height={chartHeight} viewBox={`0 0 ${svgWidth} ${chartHeight}`}>
305
+ <Defs>
306
+ <SvgLinearGradient id="crashlyticsBarGrad" x1="0" y1="0" x2="0" y2="1">
307
+ <Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.9"/>
308
+ <Stop offset="100%" stopColor="#6366F1" stopOpacity="0.6"/>
309
+ </SvgLinearGradient>
310
+ <SvgLinearGradient id="crashlyticsAreaGrad" x1="0" y1="0" x2="0" y2="1">
311
+ <Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.25"/>
312
+ <Stop offset="80%" stopColor="#8B5CF6" stopOpacity="0.04"/>
313
+ <Stop offset="100%" stopColor="#8B5CF6" stopOpacity="0.0"/>
314
+ </SvgLinearGradient>
315
+ </Defs>
316
+
317
+ {/* Subtle Grid Guidelines */}
318
+ <Line x1="6" y1={chartHeight - 6} x2={svgWidth - 6} y2={chartHeight - 6} stroke="#E2E8F0" strokeWidth="1"/>
319
+ <Line x1="6" y1={chartHeight / 2} x2={svgWidth - 6} y2={chartHeight / 2} stroke="#F1F5F9" strokeWidth="1" strokeDasharray="4,4"/>
320
+
321
+ {/* Glowing Area Underlay */}
322
+ <Path d={histogramData.areaPath} fill="url(#crashlyticsAreaGrad)"/>
323
+
324
+ {/* Vertical Crashlytics Histogram Bars */}
325
+ {histogramData.buckets.map((b, idx) => {
326
+ const isSelected = selectedBucketIdx === idx;
327
+ const barWidth = 10;
328
+ const barX = b.x - barWidth / 2;
329
+ const barHeight = Math.max(3, chartHeight - 8 - b.y);
330
+ const barY = chartHeight - 8 - barHeight;
331
+ return (<G key={`bar-${b.index}`}>
332
+ {/* Background slot */}
333
+ <Rect x={barX} y={6} width={barWidth} height={chartHeight - 14} rx={3} fill={isSelected ? '#EDE9FE' : '#F8FAFC'}/>
334
+
335
+ {/* Active Event Bar */}
336
+ {b.total > 0 && (<Rect x={barX} y={barY} width={barWidth} height={barHeight} rx={3} fill="url(#crashlyticsBarGrad)"/>)}
337
+
338
+ {/* Selected Cursor Line */}
339
+ {isSelected && (<Line x1={b.x} y1={4} x2={b.x} y2={chartHeight - 6} stroke="#8B5CF6" strokeWidth="1.5" strokeDasharray="2,2"/>)}
340
+ </G>);
341
+ })}
342
+
343
+ {/* Crashlytics Apex Trendline */}
344
+ <Path d={histogramData.linePath} fill="none" stroke="#8B5CF6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
345
+
346
+ {/* Pulsing Dot on Current Active Time Bucket */}
347
+ {histogramData.buckets.length > 0 && (<G>
348
+ <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"/>
349
+ <Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="3" fill="#7C3AED"/>
350
+ </G>)}
351
+ </Svg>
352
+
353
+ {/* Invisible Touch Columns for Interactive Bucket Selection */}
354
+ <View style={{
355
+ position: 'absolute',
356
+ top: 0,
357
+ left: 0,
358
+ right: 0,
359
+ bottom: 0,
278
360
  flexDirection: 'row',
279
- alignItems: 'flex-end',
280
- justifyContent: 'space-between',
281
- paddingHorizontal: 2,
282
- paddingBottom: 2,
283
- borderBottomWidth: 1,
284
- borderBottomColor: AppColors.dividerColor,
285
361
  }}>
286
- {histogramBars.map(bar => (<View key={bar.index} style={{
287
- flex: 1,
362
+ {histogramData.buckets.map((b, idx) => (<TouchableOpacity key={b.index} style={{ flex: 1, height: '100%' }} activeOpacity={0.8} onPress={() => setSelectedBucketIdx(selectedBucketIdx === idx ? null : idx)}/>))}
363
+ </View>
364
+ </View>
365
+
366
+ {/* Selected Bucket Quick Breakdown Overlay */}
367
+ {selectedBucket && (<View style={{
368
+ flexDirection: 'row',
288
369
  alignItems: 'center',
289
- justifyContent: 'flex-end',
290
- height: '100%',
291
- marginHorizontal: 1.5,
370
+ justifyContent: 'space-between',
371
+ backgroundColor: AppColors.white,
372
+ borderRadius: 6,
373
+ paddingHorizontal: 8,
374
+ paddingVertical: 4,
375
+ marginTop: 4,
376
+ borderWidth: 1,
377
+ borderColor: AppColors.brandPurple,
292
378
  }}>
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`,
379
+ <Text style={{
380
+ fontFamily: AppFonts.interBold,
381
+ fontSize: 10,
382
+ color: AppColors.primaryBlack,
313
383
  }}>
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>
384
+ {t('analytics.selectedBucket', {
385
+ time: selectedBucket.ageLabel,
386
+ count: selectedBucket.total,
387
+ })}
388
+ </Text>
389
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
390
+ {selectedBucket.ecommerce > 0 && (<Text style={{ fontSize: 9, fontFamily: AppFonts.interBold, color: AppColors.amber700 }}>
391
+ Ecom: {selectedBucket.ecommerce}
392
+ </Text>)}
393
+ {selectedBucket.page_view > 0 && (<Text style={{ fontSize: 9, fontFamily: AppFonts.interBold, color: AppColors.sky500 }}>
394
+ Screen: {selectedBucket.page_view}
395
+ </Text>)}
396
+ {selectedBucket.custom > 0 && (<Text style={{ fontSize: 9, fontFamily: AppFonts.interBold, color: AppColors.brandPurple }}>
397
+ Custom: {selectedBucket.custom}
398
+ </Text>)}
399
+ </View>
400
+ </View>)}
338
401
 
339
402
  {/* Timeline Axis Labels */}
340
403
  <View style={{
341
404
  flexDirection: 'row',
342
405
  justifyContent: 'space-between',
343
406
  marginTop: 4,
344
- paddingHorizontal: 2,
407
+ paddingHorizontal: 4,
345
408
  }}>
346
409
  <Text style={{
347
410
  fontFamily: AppFonts.interMedium,
348
- fontSize: 8,
411
+ fontSize: 8.5,
349
412
  color: AppColors.grayTextWeak,
350
413
  }}>
351
- -30 min
414
+ {t('analytics.time30mAgo')}
352
415
  </Text>
353
416
  <Text style={{
354
417
  fontFamily: AppFonts.interMedium,
355
- fontSize: 8,
418
+ fontSize: 8.5,
356
419
  color: AppColors.grayTextWeak,
357
420
  }}>
358
- -20 min
421
+ {t('analytics.time20mAgo')}
359
422
  </Text>
360
423
  <Text style={{
361
424
  fontFamily: AppFonts.interMedium,
362
- fontSize: 8,
425
+ fontSize: 8.5,
363
426
  color: AppColors.grayTextWeak,
364
427
  }}>
365
- -10 min
428
+ {t('analytics.time10mAgo')}
366
429
  </Text>
367
430
  <Text style={{
368
431
  fontFamily: AppFonts.interBold,
369
- fontSize: 8,
432
+ fontSize: 8.5,
370
433
  color: AppColors.brandPurple,
371
434
  }}>
372
- NOW
435
+ {t('analytics.timeNow')}
373
436
  </Text>
374
437
  </View>
375
438
  </View>
@@ -648,26 +711,26 @@ const AnalyticsHeader = React.memo(() => {
648
711
  <View style={styles.analyticsStatsRow}>
649
712
  <View style={styles.analyticsStatBox}>
650
713
  <Text style={styles.analyticsStatValue}>{totalEvents}</Text>
651
- <Text style={styles.analyticsStatLabel}>Events</Text>
714
+ <Text style={styles.analyticsStatLabel}>{t('analytics.eventCount', { count: totalEvents })}</Text>
652
715
  </View>
653
716
  <View style={styles.analyticsStatBox}>
654
717
  <Text style={styles.analyticsStatValue}>
655
718
  {Object.keys(userProperties).length}
656
719
  </Text>
657
- <Text style={styles.analyticsStatLabel}>User Props</Text>
720
+ <Text style={styles.analyticsStatLabel}>{t('analytics.props')}</Text>
658
721
  </View>
659
722
  <View style={styles.analyticsStatBox}>
660
723
  <Text style={styles.analyticsStatValue}>
661
724
  {Object.keys(defaultParams).length}
662
725
  </Text>
663
- <Text style={styles.analyticsStatLabel}>Defaults</Text>
726
+ <Text style={styles.analyticsStatLabel}>{t('analytics.defaultParameters')}</Text>
664
727
  </View>
665
728
  </View>
666
729
 
667
730
  {/* Details Section */}
668
731
  {(hasUserProps || hasDefaultParams) && (<View style={styles.analyticsHeaderDetails}>
669
732
  {hasUserProps && (<View style={{ marginBottom: 10 }}>
670
- <Text style={styles.detailsGroupTitle}>Active User Properties</Text>
733
+ <Text style={styles.detailsGroupTitle}>{t('analytics.userProperties')}</Text>
671
734
  {Object.entries(userProperties).map(([k, v]) => (<View key={k} style={styles.detailsRow}>
672
735
  <Text style={styles.detailsKey} selectable={true}>{k}</Text>
673
736
  <Text style={styles.detailsValue} selectable={true}>{String(v)}</Text>
@@ -675,7 +738,7 @@ const AnalyticsHeader = React.memo(() => {
675
738
  </View>)}
676
739
 
677
740
  {hasDefaultParams && (<View>
678
- <Text style={styles.detailsGroupTitle}>Default Event Parameters</Text>
741
+ <Text style={styles.detailsGroupTitle}>{t('analytics.defaultParameters')}</Text>
679
742
  {Object.entries(defaultParams).map(([k, v]) => (<View key={k} style={styles.detailsRow}>
680
743
  <Text style={styles.detailsKey} selectable={true}>{k}</Text>
681
744
  <Text style={styles.detailsValue} selectable={true}>{String(v)}</Text>
@@ -686,6 +749,7 @@ const AnalyticsHeader = React.memo(() => {
686
749
  </View>);
687
750
  });
688
751
  const AnalyticsTab = React.memo(() => {
752
+ const { t } = useTranslation();
689
753
  const { filteredAnalyticsEvents, analyticsSearch, setAnalyticsSearch, handleDelete, selectedEvent, setSelectedEvent, newEventIds, logRouteMapRef, isAnalyticsLayoutReady, setIsAnalyticsLayoutReady, isAnalyticsPaused, setIsAnalyticsPaused, isAnalyticsFilterApplied, } = useInspector();
690
754
  const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
691
755
  const keyExtractor = useCallback((item, index) => item?.id?.toString() ?? index.toString(), []);
@@ -43,5 +43,6 @@ export interface TreeNode {
43
43
  fileCount: number;
44
44
  children: TreeNode[];
45
45
  }
46
+ export declare const downloadBundleFile: (file: BundleFileItem, scriptURL?: string) => Promise<void>;
46
47
  declare const BundleTab: React.MemoExoticComponent<() => React.JSX.Element>;
47
48
  export default BundleTab;