react-native-inapp-inspector 2.0.1 → 2.0.3

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 (42) hide show
  1. package/README.md +10 -0
  2. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +23 -0
  3. package/dist/commonjs/components/AppHeaderLogo.js +21 -2
  4. package/dist/commonjs/components/Inspector/InspectorHeader.js +84 -11
  5. package/dist/commonjs/components/Inspector/MainScreen.js +8 -0
  6. package/dist/commonjs/components/Inspector/NpmUpdateToast.d.ts +3 -0
  7. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +288 -0
  8. package/dist/commonjs/components/Inspector/SettingsPanel.js +192 -1
  9. package/dist/commonjs/components/Inspector/TelemetryConsentModal.d.ts +3 -0
  10. package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +359 -0
  11. package/dist/commonjs/constants/version.d.ts +1 -1
  12. package/dist/commonjs/constants/version.js +1 -1
  13. package/dist/commonjs/helpers/index.d.ts +1 -0
  14. package/dist/commonjs/helpers/index.js +1 -0
  15. package/dist/commonjs/helpers/telemetry.d.ts +99 -0
  16. package/dist/commonjs/helpers/telemetry.js +398 -0
  17. package/dist/commonjs/index.d.ts +2 -0
  18. package/dist/commonjs/index.js +37 -3
  19. package/dist/commonjs/native/NativeInspector.d.ts +5 -0
  20. package/dist/commonjs/native/NativeInspector.js +3 -2
  21. package/dist/commonjs/types/interfaces.d.ts +5 -0
  22. package/dist/esm/components/AppHeaderLogo.js +21 -2
  23. package/dist/esm/components/Inspector/InspectorHeader.js +85 -12
  24. package/dist/esm/components/Inspector/MainScreen.js +8 -0
  25. package/dist/esm/components/Inspector/NpmUpdateToast.d.ts +3 -0
  26. package/dist/esm/components/Inspector/NpmUpdateToast.js +251 -0
  27. package/dist/esm/components/Inspector/SettingsPanel.js +192 -1
  28. package/dist/esm/components/Inspector/TelemetryConsentModal.d.ts +3 -0
  29. package/dist/esm/components/Inspector/TelemetryConsentModal.js +322 -0
  30. package/dist/esm/constants/version.d.ts +1 -1
  31. package/dist/esm/constants/version.js +1 -1
  32. package/dist/esm/helpers/index.d.ts +1 -0
  33. package/dist/esm/helpers/index.js +1 -0
  34. package/dist/esm/helpers/telemetry.d.ts +99 -0
  35. package/dist/esm/helpers/telemetry.js +380 -0
  36. package/dist/esm/index.d.ts +2 -0
  37. package/dist/esm/index.js +27 -3
  38. package/dist/esm/native/NativeInspector.d.ts +5 -0
  39. package/dist/esm/native/NativeInspector.js +3 -2
  40. package/dist/esm/types/interfaces.d.ts +5 -0
  41. package/ios/{NetworkInspectorModule.m → NetworkInspectorModule.mm} +175 -119
  42. package/package.json +2 -2
@@ -5,7 +5,23 @@ export const AppHeaderLogo = ({ size = 46, customIcon, }) => {
5
5
  const [loadError, setLoadError] = useState(false);
6
6
  // 1. If customIcon is explicitly provided, render it directly edge-to-edge
7
7
  if (customIcon) {
8
- return (<View style={[logoStyles.container, { width: size, height: size }]}>
8
+ if (React.isValidElement(customIcon)) {
9
+ const clonedIcon = React.cloneElement(customIcon, {
10
+ size,
11
+ width: size,
12
+ height: size,
13
+ });
14
+ return (<View style={[
15
+ logoStyles.container,
16
+ { width: size, height: size, padding: 0, borderRadius: Math.round(size * 0.28) },
17
+ ]}>
18
+ {clonedIcon}
19
+ </View>);
20
+ }
21
+ return (<View style={[
22
+ logoStyles.container,
23
+ { width: size, height: size, borderRadius: Math.round(size * 0.28) },
24
+ ]}>
9
25
  <Image source={customIcon} style={logoStyles.image} resizeMode="cover"/>
10
26
  </View>);
11
27
  }
@@ -21,7 +37,10 @@ export const AppHeaderLogo = ({ size = 46, customIcon, }) => {
21
37
  </View>);
22
38
  }
23
39
  // 3. Fallback: Inspector's Signature Owl Brand Logo
24
- return (<View style={[logoStyles.container, { width: size, height: size }]}>
40
+ return (<View style={[
41
+ logoStyles.container,
42
+ { width: size, height: size, padding: 0, borderRadius: Math.round(size * 0.28) },
43
+ ]}>
25
44
  <BrandSquareIcon size={size}/>
26
45
  </View>);
27
46
  };
@@ -9,10 +9,36 @@ import { AppColors } from '../../styles/AppColors';
9
9
  import { AppFonts } from '../../styles/AppFonts';
10
10
  import { METHOD_COLORS } from '../../constants';
11
11
  import { LIB_VERSION } from '../../constants';
12
- import { getStatusColor, getAppName, getBundleIdentifier, formatTime, getSize } from '../../helpers';
12
+ import { getStatusColor, getAppName, formatTime, getSize } from '../../helpers';
13
+ import { getTelemetryConsentStatus } from '../../helpers/telemetry';
13
14
  import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ClockIcon, SizeIcon, AppleIcon, AndroidIcon, NpmIcon, ResetIcon, } from '../NetworkIcons';
14
15
  const InspectorHeader = React.memo(() => {
15
- const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, settingsPage, setSettingsPage, resetToDefaults, closeModal, detailTitle, activeTab, environment, selectedCrash, setSelectedCrash, } = useInspector();
16
+ const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, settingsPage, setSettingsPage, resetToDefaults, closeModal, detailTitle, activeTab, environment, visible, selectedCrash, setSelectedCrash, } = useInspector();
17
+ const [isTelemetryActive, setIsTelemetryActive] = React.useState(false);
18
+ const telemetryPulseAnim = React.useRef(new Animated.Value(1)).current;
19
+ React.useEffect(() => {
20
+ getTelemetryConsentStatus().then(status => {
21
+ setIsTelemetryActive(status === 'granted');
22
+ });
23
+ }, [visible]);
24
+ React.useEffect(() => {
25
+ if (!isTelemetryActive)
26
+ return;
27
+ const pulse = Animated.loop(Animated.sequence([
28
+ Animated.timing(telemetryPulseAnim, {
29
+ toValue: 2.2,
30
+ duration: 1500,
31
+ useNativeDriver: true,
32
+ }),
33
+ Animated.timing(telemetryPulseAnim, {
34
+ toValue: 1,
35
+ duration: 0,
36
+ useNativeDriver: true,
37
+ }),
38
+ ]));
39
+ pulse.start();
40
+ return () => pulse.stop();
41
+ }, [isTelemetryActive, telemetryPulseAnim]);
16
42
  const envConfig = useMemo(() => {
17
43
  const rawEnv = (environment || (__DEV__ ? 'DEV' : 'PROD')).trim();
18
44
  const clean = rawEnv.toUpperCase();
@@ -175,7 +201,7 @@ const InspectorHeader = React.memo(() => {
175
201
  minWidth: 0,
176
202
  marginRight: 6,
177
203
  }}>
178
- <AppHeaderLogo size={30} customIcon={appIcon}/>
204
+ <AppHeaderLogo size={46} customIcon={appIcon}/>
179
205
  <View style={{ gap: 2.5, flex: 1, minWidth: 0 }}>
180
206
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, minWidth: 0 }}>
181
207
  <Text style={[styles.headerTitle, { flexShrink: 1 }]} numberOfLines={1} ellipsizeMode="tail">
@@ -222,11 +248,11 @@ const InspectorHeader = React.memo(() => {
222
248
  </Pressable>)}
223
249
  </View>
224
250
 
225
- {/* Clean Bundle Identifier & Version Representation */}
251
+ {/* OS & NPM Version Representation */}
226
252
  <View style={{
227
253
  flexDirection: 'row',
228
254
  alignItems: 'center',
229
- gap: 4.5,
255
+ gap: 5,
230
256
  minWidth: 0,
231
257
  }}>
232
258
  <View style={{
@@ -236,21 +262,21 @@ const InspectorHeader = React.memo(() => {
236
262
  borderRadius: 5,
237
263
  paddingHorizontal: 6,
238
264
  paddingVertical: 2,
239
- gap: 3.5,
265
+ gap: 4,
240
266
  borderWidth: 1,
241
267
  borderColor: `${AppColors.white}2E`,
242
- flexShrink: 1,
243
- minWidth: 0,
268
+ flexShrink: 0,
244
269
  }}>
245
- {Platform.OS === 'ios' ? (<AppleIcon color={`${AppColors.white}D9`} size={9}/>) : (<AndroidIcon color={`${AppColors.white}D9`} size={9}/>)}
270
+ {Platform.OS === 'ios' ? (<AppleIcon color={`${AppColors.white}E6`} size={10}/>) : (<AndroidIcon color={`${AppColors.white}E6`} size={10}/>)}
246
271
  <Text style={{
247
272
  fontFamily: AppFonts.interMedium,
248
273
  fontSize: 9.5,
249
274
  color: `${AppColors.white}EB`,
250
275
  letterSpacing: 0.1,
251
- flexShrink: 1,
252
- }} numberOfLines={1} ellipsizeMode="middle">
253
- {getBundleIdentifier()}
276
+ }} numberOfLines={1}>
277
+ {Platform.OS === 'ios'
278
+ ? `iOS ${Platform.Version}`
279
+ : `Android ${Platform.Version}`}
254
280
  </Text>
255
281
  </View>
256
282
 
@@ -279,6 +305,53 @@ const InspectorHeader = React.memo(() => {
279
305
  v{LIB_VERSION}
280
306
  </Text>
281
307
  </Pressable>
308
+
309
+ {isTelemetryActive && (<Pressable onPress={() => Alert.alert('Anonymous Telemetry Active', 'Anonymous diagnostics (such as React Native version, JavaScript engine, and device model) are currently enabled to help improve library tooling.\n\nNo user data or network payloads are captured. You can toggle this anytime in Settings.', [{ text: 'Got it' }])} style={{
310
+ flexDirection: 'row',
311
+ alignItems: 'center',
312
+ backgroundColor: 'rgba(16, 185, 129, 0.22)',
313
+ borderRadius: 5,
314
+ paddingHorizontal: 5.5,
315
+ paddingVertical: 2,
316
+ gap: 4.5,
317
+ borderWidth: 1,
318
+ borderColor: 'rgba(52, 211, 153, 0.45)',
319
+ flexShrink: 0,
320
+ }}>
321
+ <View style={{
322
+ width: 6,
323
+ height: 6,
324
+ alignItems: 'center',
325
+ justifyContent: 'center',
326
+ }}>
327
+ <Animated.View style={{
328
+ position: 'absolute',
329
+ width: 6,
330
+ height: 6,
331
+ borderRadius: 3,
332
+ backgroundColor: '#34D399',
333
+ opacity: telemetryPulseAnim.interpolate({
334
+ inputRange: [1, 2.2],
335
+ outputRange: [0.8, 0],
336
+ }),
337
+ transform: [{ scale: telemetryPulseAnim }],
338
+ }}/>
339
+ <View style={{
340
+ width: 4,
341
+ height: 4,
342
+ borderRadius: 2,
343
+ backgroundColor: '#10B981',
344
+ }}/>
345
+ </View>
346
+ <Text style={{
347
+ fontFamily: AppFonts.interBold,
348
+ fontSize: 9,
349
+ color: '#6EE7B7',
350
+ letterSpacing: 0.2,
351
+ }}>
352
+ LIVE
353
+ </Text>
354
+ </Pressable>)}
282
355
  </View>
283
356
  </View>
284
357
  </View>) : null}
@@ -18,6 +18,8 @@ import PerformanceTab from './PerformanceTab';
18
18
  import CrashTab from './CrashTab';
19
19
  import CrashDetail from './CrashDetail';
20
20
  import SettingsPanel from './SettingsPanel';
21
+ import TelemetryConsentModal from './TelemetryConsentModal';
22
+ import NpmUpdateToast from './NpmUpdateToast';
21
23
  import Toast from '../Toast';
22
24
  import styles from '../../styles';
23
25
  import { AppColors } from '../../styles/AppColors';
@@ -166,11 +168,17 @@ const MainScreen = () => {
166
168
 
167
169
  {/* Bottom floating toast notification */}
168
170
  <Toast />
171
+
172
+ {/* NPM Version Update Toast with timeout progress bar */}
173
+ <NpmUpdateToast />
169
174
  </View>
170
175
  </View>
171
176
  </ErrorBoundary>)}
172
177
  {hasNavigationContext && (<NavigationTracker onStateChange={setNavState}/>)}
173
178
  </Modal>
179
+
180
+ {/* Anonymous Telemetry Consent Dialog at root viewport */}
181
+ <TelemetryConsentModal />
174
182
  </>);
175
183
  };
176
184
  export default MainScreen;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare const NpmUpdateToast: () => React.JSX.Element;
3
+ export default NpmUpdateToast;
@@ -0,0 +1,251 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Animated, Linking, Platform, StyleSheet, Text, TouchableOpacity, View, } from 'react-native';
3
+ import Svg, { Path } from 'react-native-svg';
4
+ import { useInspector } from './InspectorContext';
5
+ import { LIB_VERSION } from '../../constants';
6
+ import { NpmIcon } from '../NetworkIcons';
7
+ const CloseSvg = ({ size = 13, color = '#94A3B8' }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
8
+ <Path d="M18 6L6 18M6 6l12 12" stroke={color} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"/>
9
+ </Svg>);
10
+ const ExternalLinkSvg = ({ size = 12, color = '#FFFFFF' }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
11
+ <Path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
12
+ <Path d="M15 3h6v6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
13
+ <Path d="M10 14L21 3" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
14
+ </Svg>);
15
+ const TOAST_TIMEOUT_MS = 6500;
16
+ export const NpmUpdateToast = () => {
17
+ const { updateAvailable, latestNpmVersion, showUpdateToast } = useInspector();
18
+ const [dismissed, setDismissed] = useState(false);
19
+ const [visible, setVisible] = useState(false);
20
+ const translateYAnim = useRef(new Animated.Value(50)).current;
21
+ const opacityAnim = useRef(new Animated.Value(0)).current;
22
+ const progressAnim = useRef(new Animated.Value(1)).current;
23
+ const timerRef = useRef(null);
24
+ useEffect(() => {
25
+ if (showUpdateToast && updateAvailable && latestNpmVersion && !dismissed) {
26
+ setVisible(true);
27
+ // Slide and fade in
28
+ Animated.parallel([
29
+ Animated.spring(translateYAnim, {
30
+ toValue: 0,
31
+ friction: 8,
32
+ tension: 40,
33
+ useNativeDriver: true,
34
+ }),
35
+ Animated.timing(opacityAnim, {
36
+ toValue: 1,
37
+ duration: 250,
38
+ useNativeDriver: true,
39
+ }),
40
+ ]).start();
41
+ // Progress bar countdown from 1 to 0
42
+ progressAnim.setValue(1);
43
+ Animated.timing(progressAnim, {
44
+ toValue: 0,
45
+ duration: TOAST_TIMEOUT_MS,
46
+ useNativeDriver: false,
47
+ }).start();
48
+ // Auto-hide after timeout
49
+ timerRef.current = setTimeout(() => {
50
+ handleDismiss();
51
+ }, TOAST_TIMEOUT_MS);
52
+ }
53
+ return () => {
54
+ if (timerRef.current) {
55
+ clearTimeout(timerRef.current);
56
+ }
57
+ };
58
+ }, [showUpdateToast, updateAvailable, latestNpmVersion, dismissed]);
59
+ const handleDismiss = () => {
60
+ Animated.parallel([
61
+ Animated.timing(opacityAnim, {
62
+ toValue: 0,
63
+ duration: 200,
64
+ useNativeDriver: true,
65
+ }),
66
+ Animated.timing(translateYAnim, {
67
+ toValue: 50,
68
+ duration: 200,
69
+ useNativeDriver: true,
70
+ }),
71
+ ]).start(() => {
72
+ setVisible(false);
73
+ setDismissed(true);
74
+ });
75
+ };
76
+ const handleOpenNpm = () => {
77
+ Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { });
78
+ handleDismiss();
79
+ };
80
+ if (!showUpdateToast || !visible || !updateAvailable || !latestNpmVersion)
81
+ return null;
82
+ const progressWidth = progressAnim.interpolate({
83
+ inputRange: [0, 1],
84
+ outputRange: ['0%', '100%'],
85
+ });
86
+ return (<Animated.View style={[
87
+ styles.container,
88
+ {
89
+ opacity: opacityAnim,
90
+ transform: [{ translateY: translateYAnim }],
91
+ },
92
+ ]}>
93
+ <View style={styles.contentRow}>
94
+ {/* Left NPM Badge Icon */}
95
+ <View style={styles.npmIconContainer}>
96
+ <NpmIcon size={14} color="#CB3837"/>
97
+ </View>
98
+
99
+ {/* Text Details */}
100
+ <View style={styles.textContainer}>
101
+ <View style={styles.titleRow}>
102
+ <Text style={styles.titleText}>Update Available</Text>
103
+ <View style={styles.versionPill}>
104
+ <Text style={styles.versionPillText}>v{latestNpmVersion}</Text>
105
+ </View>
106
+ </View>
107
+ <Text style={styles.subtitleText} numberOfLines={1}>
108
+ Installed: v{LIB_VERSION} • New version is ready
109
+ </Text>
110
+ </View>
111
+
112
+ {/* Action Button: View */}
113
+ <TouchableOpacity style={styles.actionButton} onPress={handleOpenNpm} activeOpacity={0.8}>
114
+ <Text style={styles.actionButtonText}>View</Text>
115
+ <ExternalLinkSvg size={10} color="#FFFFFF"/>
116
+ </TouchableOpacity>
117
+
118
+ {/* Close Button */}
119
+ <TouchableOpacity style={styles.closeButton} onPress={handleDismiss} hitSlop={8} activeOpacity={0.7}>
120
+ <CloseSvg size={12} color="#94A3B8"/>
121
+ </TouchableOpacity>
122
+ </View>
123
+
124
+ {/* Animated Linear Timeout Progress Bar */}
125
+ <View style={styles.progressBarTrack}>
126
+ <Animated.View style={[styles.progressBarFill, { width: progressWidth }]}/>
127
+ </View>
128
+ </Animated.View>);
129
+ };
130
+ const fontStack = Platform.select({
131
+ ios: {
132
+ fontFamily: 'System',
133
+ },
134
+ android: {
135
+ fontFamily: 'sans-serif',
136
+ },
137
+ default: {},
138
+ });
139
+ const styles = StyleSheet.create({
140
+ container: {
141
+ position: 'absolute',
142
+ bottom: 24,
143
+ left: 16,
144
+ right: 16,
145
+ backgroundColor: '#0F172A',
146
+ borderRadius: 16,
147
+ borderWidth: 1,
148
+ borderColor: '#334155',
149
+ shadowColor: '#000000',
150
+ shadowOffset: { width: 0, height: 10 },
151
+ shadowOpacity: 0.35,
152
+ shadowRadius: 20,
153
+ elevation: 20,
154
+ overflow: 'hidden',
155
+ zIndex: 99999,
156
+ },
157
+ contentRow: {
158
+ flexDirection: 'row',
159
+ alignItems: 'center',
160
+ paddingVertical: 12,
161
+ paddingHorizontal: 14,
162
+ gap: 10,
163
+ },
164
+ npmIconContainer: {
165
+ width: 32,
166
+ height: 32,
167
+ borderRadius: 10,
168
+ backgroundColor: '#FFFFFF',
169
+ justifyContent: 'center',
170
+ alignItems: 'center',
171
+ borderWidth: 1,
172
+ borderColor: '#CBD5E1',
173
+ flexShrink: 0,
174
+ },
175
+ textContainer: {
176
+ flex: 1,
177
+ minWidth: 0,
178
+ },
179
+ titleRow: {
180
+ flexDirection: 'row',
181
+ alignItems: 'center',
182
+ gap: 6,
183
+ },
184
+ titleText: {
185
+ fontSize: 13,
186
+ fontWeight: '700',
187
+ color: '#F8FAFC',
188
+ letterSpacing: -0.2,
189
+ ...fontStack,
190
+ },
191
+ versionPill: {
192
+ backgroundColor: '#7C3AED26',
193
+ paddingHorizontal: 6,
194
+ paddingVertical: 1.5,
195
+ borderRadius: 6,
196
+ borderWidth: 1,
197
+ borderColor: '#7C3AED59',
198
+ },
199
+ versionPillText: {
200
+ fontSize: 10.5,
201
+ fontWeight: '700',
202
+ color: '#A78BFA',
203
+ ...fontStack,
204
+ },
205
+ subtitleText: {
206
+ fontSize: 11,
207
+ color: '#94A3B8',
208
+ marginTop: 1.5,
209
+ ...fontStack,
210
+ },
211
+ actionButton: {
212
+ flexDirection: 'row',
213
+ alignItems: 'center',
214
+ gap: 4,
215
+ backgroundColor: '#7C3AED',
216
+ paddingVertical: 6,
217
+ paddingHorizontal: 10,
218
+ borderRadius: 8,
219
+ flexShrink: 0,
220
+ shadowColor: '#7C3AED',
221
+ shadowOffset: { width: 0, height: 2 },
222
+ shadowOpacity: 0.3,
223
+ shadowRadius: 4,
224
+ elevation: 3,
225
+ },
226
+ actionButtonText: {
227
+ fontSize: 11.5,
228
+ fontWeight: '700',
229
+ color: '#FFFFFF',
230
+ ...fontStack,
231
+ },
232
+ closeButton: {
233
+ width: 24,
234
+ height: 24,
235
+ borderRadius: 12,
236
+ backgroundColor: '#1E293B',
237
+ justifyContent: 'center',
238
+ alignItems: 'center',
239
+ flexShrink: 0,
240
+ },
241
+ progressBarTrack: {
242
+ height: 3,
243
+ width: '100%',
244
+ backgroundColor: '#1E293B',
245
+ },
246
+ progressBarFill: {
247
+ height: '100%',
248
+ backgroundColor: '#7C3AED',
249
+ },
250
+ });
251
+ export default NpmUpdateToast;
@@ -17,11 +17,26 @@ import { clearPerformanceEvents } from '../../customHooks/performanceTracker';
17
17
  import { isReduxConnected } from '../../customHooks/reduxLogger';
18
18
  import { isAnalyticsConnected } from '../../customHooks/analyticsLogger';
19
19
  import { useTranslation } from '../../i18n';
20
+ import { getTelemetryConsentStatus, setTelemetryConsent, sendSessionTelemetryPing, } from '../../helpers/telemetry';
20
21
  import { SignalIcon, TerminalIcon, AnalyticsIcon, SettingsIcon, SunIcon, MoonIcon, ScreenIcon, MotionIcon, LayersIcon, EyeIcon, CheckIcon, TrashIcon, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, ShieldAlertIcon, ForwardChevronIcon, ChevronDownIcon, } from '../NetworkIcons';
21
22
  const SettingsPanel = () => {
22
23
  const { t } = useTranslation();
23
- const { settingsPage, setSettingsPage, settingsActiveSubTab, setSettingsActiveSubTab, tabVisibility, toggleTabVisibility, defaultTab, setDefaultTab, isDark, setIsDark, modalHeightPercent, setModalHeightPercent, modalAnimationType, setModalAnimationType, showDuplicateLogs, setShowDuplicateLogs, showConsoleLevels, setShowConsoleLevels, resetToDefaults, storage, logs, consoleLogs, analyticsEvents, reduxState, maxNetworkLogs, setMaxNetworkLogs, maxConsoleLogs, setMaxConsoleLogs, maxAnalyticsEventsLimit, setMaxAnalyticsEventsLimit, isAutoRamLimitEnabled, setIsAutoRamLimitEnabled, deviceFreeRamMb, reduxAutoRefresh, setReduxAutoRefreshState, reduxExpandDepth, setReduxExpandDepth, switchActiveTab, setSelected, setSelectedEvent, setReduxState, crashRecords, maxCrashLogs, setMaxCrashLogs, } = useInspector();
24
+ const { settingsPage, setSettingsPage, settingsActiveSubTab, setSettingsActiveSubTab, tabVisibility, toggleTabVisibility, defaultTab, setDefaultTab, isDark, setIsDark, modalHeightPercent, setModalHeightPercent, modalAnimationType, setModalAnimationType, showDuplicateLogs, setShowDuplicateLogs, showUpdateToast, setShowUpdateToast, showConsoleLevels, setShowConsoleLevels, resetToDefaults, storage, logs, consoleLogs, analyticsEvents, reduxState, maxNetworkLogs, setMaxNetworkLogs, maxConsoleLogs, setMaxConsoleLogs, maxAnalyticsEventsLimit, setMaxAnalyticsEventsLimit, isAutoRamLimitEnabled, setIsAutoRamLimitEnabled, deviceFreeRamMb, reduxAutoRefresh, setReduxAutoRefreshState, reduxExpandDepth, setReduxExpandDepth, switchActiveTab, setSelected, setSelectedEvent, setReduxState, crashRecords, maxCrashLogs, setMaxCrashLogs, } = useInspector();
24
25
  const [stagedHeight, setStagedHeight] = useState(modalHeightPercent);
26
+ const [telemetryConsent, setTelemetryConsentState] = useState(true);
27
+ useEffect(() => {
28
+ getTelemetryConsentStatus().then(status => {
29
+ setTelemetryConsentState(status === 'granted');
30
+ });
31
+ }, []);
32
+ const handleToggleTelemetry = async () => {
33
+ const nextVal = !telemetryConsent;
34
+ setTelemetryConsentState(nextVal);
35
+ await setTelemetryConsent(nextVal);
36
+ if (nextVal) {
37
+ sendSessionTelemetryPing({ force: true });
38
+ }
39
+ };
25
40
  useEffect(() => {
26
41
  setStagedHeight(modalHeightPercent);
27
42
  }, [modalHeightPercent]);
@@ -1315,6 +1330,182 @@ const SettingsPanel = () => {
1315
1330
  </View>)}
1316
1331
  </View>
1317
1332
  </View>
1333
+
1334
+ {/* Section 4: Privacy & Diagnostics */}
1335
+ <View style={{
1336
+ backgroundColor: AppColors.primaryLight,
1337
+ borderRadius: 14,
1338
+ borderWidth: 1,
1339
+ borderColor: AppColors.grayBorderSecondary,
1340
+ overflow: 'hidden',
1341
+ padding: 14,
1342
+ gap: 12,
1343
+ }}>
1344
+ <Text style={{
1345
+ fontFamily: AppFonts.interBold,
1346
+ fontSize: 11,
1347
+ lineHeight: 14,
1348
+ color: AppColors.grayTextWeak,
1349
+ letterSpacing: 0.8,
1350
+ }}>
1351
+ PRIVACY & ANONYMOUS DIAGNOSTICS
1352
+ </Text>
1353
+
1354
+ <View style={{
1355
+ flexDirection: 'row',
1356
+ alignItems: 'center',
1357
+ justifyContent: 'space-between',
1358
+ }}>
1359
+ <View style={{
1360
+ flexDirection: 'row',
1361
+ alignItems: 'center',
1362
+ gap: 10,
1363
+ flex: 1,
1364
+ marginRight: 10,
1365
+ }}>
1366
+ <View style={{
1367
+ width: 32,
1368
+ height: 32,
1369
+ borderRadius: 8,
1370
+ backgroundColor: AppColors.purpleShade50,
1371
+ alignItems: 'center',
1372
+ justifyContent: 'center',
1373
+ }}>
1374
+ <ShieldAlertIcon color={AppColors.purple} size={15}/>
1375
+ </View>
1376
+ <View style={{ flex: 1 }}>
1377
+ <Text style={{
1378
+ fontFamily: AppFonts.interBold,
1379
+ fontSize: 13.5,
1380
+ lineHeight: 18,
1381
+ color: AppColors.primaryBlack,
1382
+ }}>
1383
+ Help Improve In-App Inspector
1384
+ </Text>
1385
+ <Text style={{
1386
+ fontFamily: AppFonts.interRegular,
1387
+ fontSize: 11,
1388
+ lineHeight: 15,
1389
+ color: AppColors.grayText,
1390
+ marginTop: 1,
1391
+ }}>
1392
+ Share non-identifiable technical metrics (RN version, JS engine, device model). No user data or network payloads are captured.
1393
+ </Text>
1394
+ </View>
1395
+ </View>
1396
+
1397
+ <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle Anonymous Telemetry" accessibilityState={{ checked: telemetryConsent }} onPress={handleToggleTelemetry} style={{
1398
+ width: 42,
1399
+ height: 24,
1400
+ borderRadius: 12,
1401
+ backgroundColor: telemetryConsent
1402
+ ? AppColors.purple
1403
+ : AppColors.grayBorderSecondary,
1404
+ padding: 2,
1405
+ justifyContent: 'center',
1406
+ alignItems: telemetryConsent ? 'flex-end' : 'flex-start',
1407
+ }}>
1408
+ <View style={{
1409
+ width: 20,
1410
+ height: 20,
1411
+ borderRadius: 10,
1412
+ backgroundColor: AppColors.white,
1413
+ shadowColor: AppColors.black,
1414
+ shadowOpacity: 0.18,
1415
+ shadowRadius: 2,
1416
+ shadowOffset: { width: 0, height: 1 },
1417
+ }}/>
1418
+ </TouchableScale>
1419
+ </View>
1420
+ </View>
1421
+
1422
+ {/* Section 5: Notifications & Toasts */}
1423
+ <View style={{
1424
+ backgroundColor: AppColors.primaryLight,
1425
+ borderRadius: 14,
1426
+ borderWidth: 1,
1427
+ borderColor: AppColors.grayBorderSecondary,
1428
+ overflow: 'hidden',
1429
+ padding: 14,
1430
+ gap: 12,
1431
+ }}>
1432
+ <Text style={{
1433
+ fontFamily: AppFonts.interBold,
1434
+ fontSize: 11,
1435
+ lineHeight: 14,
1436
+ color: AppColors.grayTextWeak,
1437
+ letterSpacing: 0.8,
1438
+ }}>
1439
+ NOTIFICATIONS & TOASTS
1440
+ </Text>
1441
+
1442
+ <View style={{
1443
+ flexDirection: 'row',
1444
+ alignItems: 'center',
1445
+ justifyContent: 'space-between',
1446
+ }}>
1447
+ <View style={{
1448
+ flexDirection: 'row',
1449
+ alignItems: 'center',
1450
+ gap: 10,
1451
+ flex: 1,
1452
+ marginRight: 10,
1453
+ }}>
1454
+ <View style={{
1455
+ width: 32,
1456
+ height: 32,
1457
+ borderRadius: 8,
1458
+ backgroundColor: AppColors.purpleShade50,
1459
+ alignItems: 'center',
1460
+ justifyContent: 'center',
1461
+ }}>
1462
+ <PackageIcon color={AppColors.purple} size={15}/>
1463
+ </View>
1464
+ <View style={{ flex: 1 }}>
1465
+ <Text style={{
1466
+ fontFamily: AppFonts.interBold,
1467
+ fontSize: 13.5,
1468
+ lineHeight: 18,
1469
+ color: AppColors.primaryBlack,
1470
+ }}>
1471
+ NPM Update Toast
1472
+ </Text>
1473
+ <Text style={{
1474
+ fontFamily: AppFonts.interRegular,
1475
+ fontSize: 11,
1476
+ lineHeight: 15,
1477
+ color: AppColors.grayText,
1478
+ marginTop: 1,
1479
+ }}>
1480
+ Show a floating toast banner with countdown progress when a newer release is published on npm.
1481
+ </Text>
1482
+ </View>
1483
+ </View>
1484
+
1485
+ <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle NPM Update Toast" accessibilityState={{ checked: showUpdateToast }} onPress={() => setShowUpdateToast(prev => !prev)} style={{
1486
+ width: 42,
1487
+ height: 24,
1488
+ borderRadius: 12,
1489
+ backgroundColor: showUpdateToast
1490
+ ? AppColors.purple
1491
+ : AppColors.grayBorderSecondary,
1492
+ padding: 2,
1493
+ justifyContent: 'center',
1494
+ alignItems: showUpdateToast ? 'flex-end' : 'flex-start',
1495
+ }}>
1496
+ <View style={{
1497
+ width: 20,
1498
+ height: 20,
1499
+ borderRadius: 10,
1500
+ backgroundColor: AppColors.white,
1501
+ shadowColor: AppColors.black,
1502
+ shadowOpacity: 0.18,
1503
+ shadowRadius: 2,
1504
+ shadowOffset: { width: 0, height: 1 },
1505
+ }}/>
1506
+ </TouchableScale>
1507
+ </View>
1508
+ </View>
1318
1509
  </View>)}
1319
1510
  <View style={{ height: 48 }}/>
1320
1511
  </ScrollView>
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare const TelemetryConsentModal: () => React.JSX.Element;
3
+ export default TelemetryConsentModal;