react-native-inapp-inspector 2.2.2 → 2.2.4

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 (159) hide show
  1. package/dist/commonjs/components/AnalyticsDetail.js +5 -1
  2. package/dist/commonjs/components/AnalyticsEventCard.js +2 -1
  3. package/dist/commonjs/components/DiffViewer.js +1 -1
  4. package/dist/commonjs/components/Inspector/BundleTab.js +22 -0
  5. package/dist/commonjs/components/Inspector/DeviceInfoTab.d.ts +3 -0
  6. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +835 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +12 -14
  8. package/dist/commonjs/components/Inspector/LogDetail.js +6 -3
  9. package/dist/commonjs/components/Inspector/MainScreen.js +7 -3
  10. package/dist/commonjs/components/Inspector/NetworkTab.js +92 -15
  11. package/dist/commonjs/components/Inspector/PerformanceTab.js +38 -12
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +440 -229
  13. package/dist/commonjs/components/Inspector/StorageTab.d.ts +3 -0
  14. package/dist/commonjs/components/Inspector/StorageTab.js +892 -0
  15. package/dist/commonjs/components/Inspector/TabBar.js +14 -0
  16. package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +11 -2
  17. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +4 -1
  18. package/dist/commonjs/components/NetworkIcons.d.ts +6 -0
  19. package/dist/commonjs/components/NetworkIcons.js +30 -1
  20. package/dist/commonjs/constants/version.d.ts +1 -1
  21. package/dist/commonjs/constants/version.js +1 -1
  22. package/dist/commonjs/customHooks/bundleAnalyzer.js +44 -22
  23. package/dist/commonjs/customHooks/storageInspector.d.ts +67 -0
  24. package/dist/commonjs/customHooks/storageInspector.js +486 -0
  25. package/dist/commonjs/i18n/locales/en.json +8 -2
  26. package/dist/commonjs/index.d.ts +1 -0
  27. package/dist/commonjs/index.js +16 -1
  28. package/dist/commonjs/storage.d.ts +5 -0
  29. package/dist/commonjs/storage.js +18 -0
  30. package/dist/commonjs/types/enums.d.ts +5 -0
  31. package/dist/commonjs/types/enums.js +5 -0
  32. package/dist/esm/components/AnalyticsDetail.js +6 -2
  33. package/dist/esm/components/AnalyticsEventCard.js +2 -1
  34. package/dist/esm/components/DiffViewer.js +1 -1
  35. package/dist/esm/components/Inspector/BundleTab.js +22 -0
  36. package/dist/esm/components/Inspector/DeviceInfoTab.d.ts +3 -0
  37. package/dist/esm/components/Inspector/DeviceInfoTab.js +796 -0
  38. package/dist/esm/components/Inspector/InspectorHeader.js +13 -15
  39. package/dist/esm/components/Inspector/LogDetail.js +7 -4
  40. package/dist/esm/components/Inspector/MainScreen.js +7 -3
  41. package/dist/esm/components/Inspector/NetworkTab.js +93 -16
  42. package/dist/esm/components/Inspector/PerformanceTab.js +39 -13
  43. package/dist/esm/components/Inspector/SettingsPanel.js +441 -230
  44. package/dist/esm/components/Inspector/StorageTab.d.ts +3 -0
  45. package/dist/esm/components/Inspector/StorageTab.js +853 -0
  46. package/dist/esm/components/Inspector/TabBar.js +15 -1
  47. package/dist/esm/components/Inspector/TelemetryConsentModal.js +11 -2
  48. package/dist/esm/components/Inspector/UpdateAvailableModal.js +4 -1
  49. package/dist/esm/components/NetworkIcons.d.ts +6 -0
  50. package/dist/esm/components/NetworkIcons.js +23 -0
  51. package/dist/esm/constants/version.d.ts +1 -1
  52. package/dist/esm/constants/version.js +1 -1
  53. package/dist/esm/customHooks/bundleAnalyzer.js +44 -22
  54. package/dist/esm/customHooks/storageInspector.d.ts +67 -0
  55. package/dist/esm/customHooks/storageInspector.js +469 -0
  56. package/dist/esm/i18n/locales/en.json +8 -2
  57. package/dist/esm/index.d.ts +1 -0
  58. package/dist/esm/index.js +5 -0
  59. package/dist/esm/storage.d.ts +5 -0
  60. package/dist/esm/storage.js +5 -0
  61. package/dist/esm/types/enums.d.ts +5 -0
  62. package/dist/esm/types/enums.js +5 -0
  63. package/package.json +9 -1
  64. package/src/analytics.ts +35 -0
  65. package/src/bundle.ts +25 -0
  66. package/src/components/AnalyticsDetail.tsx +870 -0
  67. package/src/components/AnalyticsEventCard.tsx +442 -0
  68. package/src/components/AnalyticsGraph.tsx +583 -0
  69. package/src/components/AnimatedEntrance.tsx +64 -0
  70. package/src/components/AppHeaderLogo.tsx +109 -0
  71. package/src/components/BrandCircleIcon.tsx +144 -0
  72. package/src/components/BrandSquareIcon.tsx +144 -0
  73. package/src/components/CodeSnippet.tsx +725 -0
  74. package/src/components/ConsoleLogCard.tsx +628 -0
  75. package/src/components/CopyButton.tsx +87 -0
  76. package/src/components/DiffViewer.tsx +82 -0
  77. package/src/components/DomainHeader.tsx +237 -0
  78. package/src/components/EmptyState.tsx +73 -0
  79. package/src/components/EndOfListFooter.tsx +100 -0
  80. package/src/components/ErrorBoundary.tsx +688 -0
  81. package/src/components/HeadersSection.tsx +192 -0
  82. package/src/components/HighlightText.tsx +100 -0
  83. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  84. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  85. package/src/components/Inspector/BundleTab.tsx +4760 -0
  86. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  87. package/src/components/Inspector/CrashDetail.tsx +941 -0
  88. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  89. package/src/components/Inspector/CrashTab.tsx +871 -0
  90. package/src/components/Inspector/DeviceInfoTab.tsx +1204 -0
  91. package/src/components/Inspector/FabLauncher.tsx +80 -0
  92. package/src/components/Inspector/InspectorContext.tsx +28 -0
  93. package/src/components/Inspector/InspectorHeader.tsx +972 -0
  94. package/src/components/Inspector/LogDetail.tsx +1431 -0
  95. package/src/components/Inspector/MainScreen.tsx +262 -0
  96. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  97. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  98. package/src/components/Inspector/NetworkTab.tsx +1183 -0
  99. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  100. package/src/components/Inspector/PerformanceTab.tsx +1928 -0
  101. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  102. package/src/components/Inspector/ReduxTab.tsx +954 -0
  103. package/src/components/Inspector/SettingsPanel.tsx +3447 -0
  104. package/src/components/Inspector/StorageTab.tsx +1048 -0
  105. package/src/components/Inspector/TabBar.tsx +207 -0
  106. package/src/components/Inspector/TelemetryConsentModal.tsx +416 -0
  107. package/src/components/Inspector/UpdateAvailableModal.tsx +557 -0
  108. package/src/components/JsonViewer.tsx +486 -0
  109. package/src/components/LogCard.tsx +491 -0
  110. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  111. package/src/components/MetaAccordion.tsx +340 -0
  112. package/src/components/MiniBarChart.tsx +42 -0
  113. package/src/components/MiniLineChart.tsx +33 -0
  114. package/src/components/NetworkIcons.tsx +2221 -0
  115. package/src/components/SectionHeader.tsx +113 -0
  116. package/src/components/SegmentedTabs.tsx +83 -0
  117. package/src/components/Slider.tsx +300 -0
  118. package/src/components/SourcePageCard.tsx +148 -0
  119. package/src/components/Toast.tsx +131 -0
  120. package/src/components/TouchableScale.tsx +91 -0
  121. package/src/components/TreeNode.tsx +186 -0
  122. package/src/console.ts +24 -0
  123. package/src/constants/index.ts +38 -0
  124. package/src/constants/version.ts +3 -0
  125. package/src/crash.ts +32 -0
  126. package/src/customHooks/analyticsLogger.ts +336 -0
  127. package/src/customHooks/bundleAnalyzer.ts +1256 -0
  128. package/src/customHooks/consoleLogger.ts +497 -0
  129. package/src/customHooks/crashHandler.ts +944 -0
  130. package/src/customHooks/logFilters.ts +32 -0
  131. package/src/customHooks/networkLogger.ts +419 -0
  132. package/src/customHooks/performanceTracker.ts +1014 -0
  133. package/src/customHooks/reduxLogger.ts +406 -0
  134. package/src/customHooks/storageInspector.ts +509 -0
  135. package/src/customHooks/useAccordion.tsx +60 -0
  136. package/src/decorators/index.ts +184 -0
  137. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  138. package/src/helpers/index.ts +860 -0
  139. package/src/helpers/memoryManager.ts +138 -0
  140. package/src/helpers/searchQueryParser.ts +283 -0
  141. package/src/helpers/settingsStore.ts +171 -0
  142. package/src/helpers/telemetry.ts +505 -0
  143. package/src/helpers/toast.ts +17 -0
  144. package/src/i18n/index.ts +69 -0
  145. package/src/i18n/locales/en.json +1058 -0
  146. package/src/index.tsx +2356 -0
  147. package/src/native/NativeInspector.ts +397 -0
  148. package/src/native/NativeNetworkInspector.ts +24 -0
  149. package/src/network.ts +22 -0
  150. package/src/performance.ts +38 -0
  151. package/src/redux.ts +23 -0
  152. package/src/storage.ts +18 -0
  153. package/src/styles/AppColors.ts +408 -0
  154. package/src/styles/AppFonts.ts +8 -0
  155. package/src/styles/common.ts +209 -0
  156. package/src/styles/index.ts +1570 -0
  157. package/src/types/enums.ts +199 -0
  158. package/src/types/index.ts +34 -0
  159. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,3447 @@
1
+ import React, {useMemo, useState, useEffect, useRef} from 'react';
2
+ import {
3
+ Alert,
4
+ Animated,
5
+ Platform,
6
+ ScrollView,
7
+ StatusBar,
8
+ StyleSheet,
9
+ Text,
10
+ TextInput,
11
+ View,
12
+ } from 'react-native';
13
+ import Svg, {Path} from 'react-native-svg';
14
+ import {animateNextLayout, useInspector} from './InspectorContext';
15
+ import TouchableScale from '../TouchableScale';
16
+ import Slider from '../Slider';
17
+ import {toggleGlobalTheme} from '../../styles';
18
+ import {AppColors} from '../../styles/AppColors';
19
+ import {AppFonts} from '../../styles/AppFonts';
20
+ import {
21
+ isPersistentStorageAvailable,
22
+ calculateRamBasedLimits,
23
+ } from '../../helpers/settingsStore';
24
+ import {clearNetworkLogs} from '../../customHooks/networkLogger';
25
+ import {clearConsoleLogs} from '../../customHooks/consoleLogger';
26
+ import {clearAnalyticsEvents} from '../../customHooks/analyticsLogger';
27
+ import {clearCrashRecords} from '../../customHooks/crashHandler';
28
+ import {clearCachedBundleAnalysis} from '../../customHooks/bundleAnalyzer';
29
+ import {clearPerformanceEvents} from '../../customHooks/performanceTracker';
30
+ import {isReduxConnected} from '../../customHooks/reduxLogger';
31
+ import {isAnalyticsConnected} from '../../customHooks/analyticsLogger';
32
+ import {useTranslation} from '../../i18n';
33
+ import {ActiveTab} from '../../types';
34
+ import {
35
+ getTelemetryConsentStatus,
36
+ setTelemetryConsent,
37
+ sendSessionTelemetryPing,
38
+ } from '../../helpers/telemetry';
39
+ import {
40
+ SignalIcon,
41
+ TerminalIcon,
42
+ AnalyticsIcon,
43
+ SettingsIcon,
44
+ SunIcon,
45
+ MoonIcon,
46
+ ScreenIcon,
47
+ MotionIcon,
48
+ LayersIcon,
49
+ EyeIcon,
50
+ CheckIcon,
51
+ TrashIcon,
52
+ PackageIcon,
53
+ ReduxIcon,
54
+ PerformanceIcon,
55
+ CrashIcon,
56
+ ShieldAlertIcon,
57
+ ForwardChevronIcon,
58
+ ChevronDownIcon,
59
+ NpmIcon,
60
+ BoltIcon,
61
+ BrainIcon,
62
+ SmartphoneIcon,
63
+ DatabaseIcon,
64
+ } from '../NetworkIcons';
65
+ import {LIB_VERSION} from '../../constants';
66
+ import {copyToClipboard} from '../../helpers';
67
+ import {showToast} from '../../helpers/toast';
68
+ import {pruneAllLogs} from '../../helpers/memoryManager';
69
+
70
+ const SettingsPanel = () => {
71
+ const {t} = useTranslation();
72
+ const {
73
+ settingsPage,
74
+ setSettingsPage,
75
+ settingsActiveSubTab,
76
+ setSettingsActiveSubTab,
77
+ tabVisibility,
78
+ toggleTabVisibility,
79
+ defaultTab,
80
+ setDefaultTab,
81
+ isDark,
82
+ setIsDark,
83
+ modalHeightPercent,
84
+ setModalHeightPercent,
85
+ modalAnimationType,
86
+ setModalAnimationType,
87
+ showDuplicateLogs,
88
+ setShowDuplicateLogs,
89
+ showUpdateToast,
90
+ setShowUpdateToast,
91
+ showConsoleLevels,
92
+ setShowConsoleLevels,
93
+ resetToDefaults,
94
+ storage,
95
+ logs,
96
+ consoleLogs,
97
+ analyticsEvents,
98
+ reduxState,
99
+ maxNetworkLogs,
100
+ setMaxNetworkLogs,
101
+ maxConsoleLogs,
102
+ setMaxConsoleLogs,
103
+ maxAnalyticsEventsLimit,
104
+ setMaxAnalyticsEventsLimit,
105
+ isAutoRamLimitEnabled,
106
+ setIsAutoRamLimitEnabled,
107
+ deviceFreeRamMb,
108
+ reduxAutoRefresh,
109
+ setReduxAutoRefreshState,
110
+ reduxExpandDepth,
111
+ setReduxExpandDepth,
112
+ switchActiveTab,
113
+ setSelected,
114
+ setSelectedEvent,
115
+ setReduxState,
116
+ crashRecords,
117
+ maxCrashLogs,
118
+ setMaxCrashLogs,
119
+ updateAvailable,
120
+ latestNpmVersion,
121
+ } = useInspector();
122
+
123
+ const [stagedHeight, setStagedHeight] = useState(modalHeightPercent);
124
+ const [telemetryConsent, setTelemetryConsentState] = useState<boolean>(true);
125
+
126
+ useEffect(() => {
127
+ getTelemetryConsentStatus().then(status => {
128
+ setTelemetryConsentState(status === 'granted');
129
+ });
130
+ }, []);
131
+
132
+ const handleToggleTelemetry = async () => {
133
+ const nextVal = !telemetryConsent;
134
+ setTelemetryConsentState(nextVal);
135
+ await setTelemetryConsent(nextVal);
136
+ if (nextVal) {
137
+ sendSessionTelemetryPing({force: true});
138
+ }
139
+ };
140
+
141
+ useEffect(() => {
142
+ setStagedHeight(modalHeightPercent);
143
+ }, [modalHeightPercent]);
144
+
145
+ const autoRamProfile = calculateRamBasedLimits(deviceFreeRamMb);
146
+
147
+ const isPersistent = isPersistentStorageAvailable();
148
+
149
+ // Safe Area & Status Bar Padding for All Device Form Factors
150
+ const statusBarHeight =
151
+ Platform.OS === 'android' ? StatusBar.currentHeight || 24 : 44;
152
+ const headerTopPadding = modalHeightPercent >= 95 ? statusBarHeight : 0;
153
+
154
+ const allModules = [
155
+ {
156
+ key: 'apis',
157
+ label: 'APIs (Network)',
158
+ category: 'core',
159
+ icon: 'apis',
160
+ desc: 'HTTP/HTTPS requests, GraphQL, Axios & WebSocket inspector',
161
+ },
162
+ {
163
+ key: 'logs',
164
+ label: 'Console Logs',
165
+ category: 'core',
166
+ icon: 'logs',
167
+ desc: 'Terminal console logs, warnings, errors & stack traces',
168
+ },
169
+ {
170
+ key: 'performance',
171
+ label: 'Performance Tracker',
172
+ category: 'diagnostic',
173
+ icon: 'performance',
174
+ desc: '60 FPS monitor, Hermes memory telemetry & re-render profiler',
175
+ },
176
+ {
177
+ key: 'bundle',
178
+ label: 'Bundle Analyzer',
179
+ category: 'diagnostic',
180
+ icon: 'bundle',
181
+ desc: 'Metro packager dependencies, source maps & asset breakdown',
182
+ },
183
+ {
184
+ key: 'crash',
185
+ label: 'Crash Protection',
186
+ category: 'diagnostic',
187
+ icon: 'crash',
188
+ desc: 'Runtime exception guard, breadcrumbs & memory snapshot',
189
+ },
190
+ {
191
+ key: 'analytics',
192
+ label: 'Analytics Logger',
193
+ category: 'telemetry',
194
+ icon: 'analytics',
195
+ desc: 'Firebase & custom analytics events, user properties & params',
196
+ },
197
+ {
198
+ key: 'redux',
199
+ label: 'Redux Inspector',
200
+ category: 'telemetry',
201
+ icon: 'redux',
202
+ desc: 'Store state diffing, action history & reducer timeline',
203
+ },
204
+ {
205
+ key: 'device',
206
+ label: 'Device Info',
207
+ category: 'diagnostic',
208
+ icon: 'device',
209
+ desc: 'Hardware specs, IP address, screen metrics, UDID & runtime stats',
210
+ },
211
+ {
212
+ key: 'storage',
213
+ label: 'Storage Inspector',
214
+ category: 'telemetry',
215
+ icon: 'storage',
216
+ desc: 'AsyncStorage & MMKV key-value store viewer with full CRUD support',
217
+ },
218
+ ] as const;
219
+
220
+ // Staged selection state for checkboxes before clicking "Save Changes"
221
+ const [stagedTabVisibility, setStagedTabVisibility] = useState<
222
+ Record<ActiveTab, boolean>
223
+ >(() => ({
224
+ apis: true,
225
+ logs: Boolean(tabVisibility?.logs),
226
+ analytics: Boolean(tabVisibility?.analytics),
227
+ redux: Boolean(tabVisibility?.redux),
228
+ bundle: Boolean(tabVisibility?.bundle),
229
+ performance: Boolean(tabVisibility?.performance),
230
+ crash: Boolean(tabVisibility?.crash),
231
+ device: Boolean(tabVisibility?.device ?? true),
232
+ storage: Boolean(tabVisibility?.storage ?? true),
233
+ }));
234
+
235
+ // Synchronize staged state with tabVisibility when tabVisibility updates
236
+ useEffect(() => {
237
+ setStagedTabVisibility({
238
+ apis: true,
239
+ logs: Boolean(tabVisibility?.logs),
240
+ analytics: Boolean(tabVisibility?.analytics),
241
+ redux: Boolean(tabVisibility?.redux),
242
+ bundle: Boolean(tabVisibility?.bundle),
243
+ performance: Boolean(tabVisibility?.performance),
244
+ crash: Boolean(tabVisibility?.crash),
245
+ device: Boolean(tabVisibility?.device ?? true),
246
+ storage: Boolean(tabVisibility?.storage ?? true),
247
+ });
248
+ }, [tabVisibility]);
249
+
250
+ const hasUnsavedChanges = useMemo(() => {
251
+ return allModules.some(
252
+ m =>
253
+ Boolean(stagedTabVisibility[m.key as ActiveTab]) !==
254
+ Boolean(tabVisibility?.[m.key as ActiveTab]),
255
+ );
256
+ }, [stagedTabVisibility, tabVisibility, allModules]);
257
+
258
+ const stagedActiveCount = allModules.filter(
259
+ m => m.key === 'apis' || Boolean(stagedTabVisibility[m.key as ActiveTab]),
260
+ ).length;
261
+
262
+ const handleSaveChanges = () => {
263
+ animateNextLayout();
264
+ allModules.forEach(m => {
265
+ if (m.key !== 'apis') {
266
+ const isStagedOn = Boolean(stagedTabVisibility[m.key as ActiveTab]);
267
+ const isCurrentOn = Boolean(tabVisibility?.[m.key as ActiveTab]);
268
+ if (isStagedOn !== isCurrentOn) {
269
+ toggleTabVisibility(m.key as ActiveTab);
270
+ }
271
+ }
272
+ });
273
+ Alert.alert(t('settings.settingsSaved'), t('settings.settingsSavedDesc'));
274
+ };
275
+
276
+ const [isDefaultTabDropdownOpen, setIsDefaultTabDropdownOpen] =
277
+ useState(false);
278
+
279
+ const goBackToMain = () => {
280
+ animateNextLayout();
281
+ setSettingsPage('main');
282
+ };
283
+
284
+ // Helper: settings row with icon + label + optional description
285
+ const renderSettingRow = (opts: {
286
+ icon: React.ReactNode;
287
+ label: string;
288
+ description?: string;
289
+ right?: React.ReactNode;
290
+ picker?: {
291
+ options: readonly any[];
292
+ selectedValue: any;
293
+ onSelect: (val: any) => void;
294
+ formatLabel?: (val: any) => string;
295
+ };
296
+ numericInput?: {
297
+ value: number;
298
+ onChange: (val: number) => void;
299
+ min?: number;
300
+ max?: number;
301
+ placeholder?: string;
302
+ keyboardType?: 'numeric' | 'number-pad' | 'decimal-pad';
303
+ };
304
+ onPress?: () => void;
305
+ isLast?: boolean;
306
+ }) => {
307
+ return (
308
+ <View
309
+ style={{
310
+ paddingVertical: 12,
311
+ borderBottomWidth: opts.isLast ? 0 : 1,
312
+ borderBottomColor: AppColors.dividerColor,
313
+ }}>
314
+ <TouchableScale
315
+ disabled={!opts.onPress}
316
+ onPress={opts.onPress}
317
+ style={{
318
+ flexDirection: 'row',
319
+ alignItems: 'center',
320
+ gap: 12,
321
+ }}>
322
+ <View
323
+ style={{
324
+ width: 36,
325
+ height: 36,
326
+ borderRadius: 10,
327
+ backgroundColor: AppColors.purpleShade50,
328
+ borderWidth: 1,
329
+ borderColor: `${AppColors.purple}2E`,
330
+ alignItems: 'center',
331
+ justifyContent: 'center',
332
+ }}>
333
+ {opts.icon}
334
+ </View>
335
+ <View style={{flex: 1}}>
336
+ <Text
337
+ style={{
338
+ fontFamily: AppFonts.interBold,
339
+ fontSize: 14,
340
+ lineHeight: 18,
341
+ color: AppColors.primaryBlack,
342
+ }}>
343
+ {opts.label}
344
+ </Text>
345
+ {opts.description ? (
346
+ <Text
347
+ style={{
348
+ fontFamily: AppFonts.interRegular,
349
+ fontSize: 11,
350
+ lineHeight: 15,
351
+ color: AppColors.grayText,
352
+ marginTop: 1,
353
+ }}>
354
+ {opts.description}
355
+ </Text>
356
+ ) : null}
357
+ </View>
358
+ {opts.right || null}
359
+ </TouchableScale>
360
+ {opts.numericInput && (
361
+ <View style={{marginTop: 10, gap: 6}}>
362
+ <View
363
+ style={{
364
+ flexDirection: 'row',
365
+ alignItems: 'center',
366
+ backgroundColor: AppColors.grayBackground,
367
+ borderRadius: 8,
368
+ borderWidth: 1,
369
+ borderColor: AppColors.dividerColor,
370
+ paddingHorizontal: 12,
371
+ paddingVertical: 4,
372
+ }}>
373
+ <TextInput
374
+ style={{
375
+ flex: 1,
376
+ fontFamily: AppFonts.interMedium,
377
+ fontSize: 14,
378
+ lineHeight: 18,
379
+ color: AppColors.primaryBlack,
380
+ paddingVertical: 6,
381
+ }}
382
+ value={String(opts.numericInput.value || '')}
383
+ onChangeText={text => {
384
+ const num = parseInt(text.replace(/[^0-9]/g, ''), 10);
385
+ if (!isNaN(num)) {
386
+ const clamped = Math.max(
387
+ opts.numericInput?.min ?? 1,
388
+ Math.min(opts.numericInput?.max ?? 10000, num),
389
+ );
390
+ opts.numericInput?.onChange(clamped);
391
+ }
392
+ }}
393
+ keyboardType={opts.numericInput.keyboardType ?? 'number-pad'}
394
+ placeholder={opts.numericInput.placeholder}
395
+ placeholderTextColor={AppColors.grayTextWeak}
396
+ maxLength={6}
397
+ selectTextOnFocus
398
+ />
399
+ <Text
400
+ style={{
401
+ fontFamily: AppFonts.interBold,
402
+ fontSize: 11,
403
+ color: AppColors.purple,
404
+ marginLeft: 6,
405
+ }}>
406
+ MAX
407
+ </Text>
408
+ </View>
409
+ {opts.numericInput.min !== undefined ||
410
+ opts.numericInput.max !== undefined ? (
411
+ <Text
412
+ style={{
413
+ fontFamily: AppFonts.interRegular,
414
+ fontSize: 10,
415
+ lineHeight: 13,
416
+ color: AppColors.grayTextWeak,
417
+ }}>
418
+ Range: {opts.numericInput.min ?? 1} -{' '}
419
+ {opts.numericInput.max ?? '∞'}
420
+ </Text>
421
+ ) : null}
422
+ </View>
423
+ )}
424
+ {opts.picker && (
425
+ <View
426
+ style={{
427
+ flexDirection: 'row',
428
+ backgroundColor: AppColors.grayBackground,
429
+ borderRadius: 8,
430
+ padding: 2.5,
431
+ marginTop: 10,
432
+ borderWidth: 1,
433
+ borderColor: AppColors.dividerColor,
434
+ }}>
435
+ {opts.picker.options.map(opt => {
436
+ const isActive = opts.picker!.selectedValue === opt;
437
+ return (
438
+ <TouchableScale
439
+ key={String(opt)}
440
+ onPress={() => opts.picker!.onSelect(opt)}
441
+ style={{
442
+ flex: 1,
443
+ paddingVertical: 6,
444
+ alignItems: 'center',
445
+ borderRadius: 6,
446
+ backgroundColor: isActive
447
+ ? AppColors.purple
448
+ : 'transparent',
449
+ }}>
450
+ <Text
451
+ style={{
452
+ fontFamily: AppFonts.interBold,
453
+ fontSize: 11,
454
+ lineHeight: 14,
455
+ color: isActive ? AppColors.white : AppColors.grayText,
456
+ }}>
457
+ {opts.picker!.formatLabel
458
+ ? opts.picker!.formatLabel(opt)
459
+ : opt}
460
+ </Text>
461
+ </TouchableScale>
462
+ );
463
+ })}
464
+ </View>
465
+ )}
466
+ </View>
467
+ );
468
+ };
469
+
470
+ const renderMainSettingsContent = () => (
471
+ <View
472
+ style={{
473
+ flex: 1,
474
+ backgroundColor: AppColors.grayBackground,
475
+ }}>
476
+ <ScrollView
477
+ style={{flex: 1}}
478
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 14}}
479
+ showsVerticalScrollIndicator={false}>
480
+ {/* Segmented Top Navigation Sub-Tabs */}
481
+ <View
482
+ style={{
483
+ flexDirection: 'row',
484
+ backgroundColor: AppColors.primaryLight,
485
+ borderRadius: 12,
486
+ padding: 4,
487
+ borderWidth: 1,
488
+ borderColor: AppColors.grayBorderSecondary,
489
+ shadowColor: AppColors.black,
490
+ shadowOpacity: 0.04,
491
+ shadowRadius: 4,
492
+ shadowOffset: {width: 0, height: 2},
493
+ }}>
494
+ <TouchableScale
495
+ accessible={true}
496
+ accessibilityRole="tab"
497
+ accessibilityLabel={t('settings.modulesAndTools')}
498
+ accessibilityState={{selected: settingsActiveSubTab === 'module'}}
499
+ onPress={() => {
500
+ animateNextLayout();
501
+ setSettingsActiveSubTab('module');
502
+ }}
503
+ style={{
504
+ flex: 1,
505
+ paddingVertical: 9,
506
+ flexDirection: 'row',
507
+ alignItems: 'center',
508
+ justifyContent: 'center',
509
+ gap: 6,
510
+ borderRadius: 9,
511
+ backgroundColor:
512
+ settingsActiveSubTab === 'module'
513
+ ? AppColors.purple
514
+ : 'transparent',
515
+ }}>
516
+ <LayersIcon
517
+ color={
518
+ settingsActiveSubTab === 'module'
519
+ ? AppColors.white
520
+ : AppColors.grayText
521
+ }
522
+ size={13}
523
+ />
524
+ <Text
525
+ style={{
526
+ fontFamily: AppFonts.interBold,
527
+ fontSize: 12.5,
528
+ lineHeight: 16,
529
+ color:
530
+ settingsActiveSubTab === 'module'
531
+ ? AppColors.white
532
+ : AppColors.grayText,
533
+ }}>
534
+ {t('settings.modulesAndTools')}
535
+ </Text>
536
+ </TouchableScale>
537
+
538
+ <TouchableScale
539
+ accessible={true}
540
+ accessibilityRole="tab"
541
+ accessibilityLabel={t('settings.uiPreferences')}
542
+ accessibilityState={{selected: settingsActiveSubTab === 'ui'}}
543
+ onPress={() => {
544
+ animateNextLayout();
545
+ setSettingsActiveSubTab('ui');
546
+ }}
547
+ style={{
548
+ flex: 1,
549
+ paddingVertical: 9,
550
+ flexDirection: 'row',
551
+ alignItems: 'center',
552
+ justifyContent: 'center',
553
+ gap: 5,
554
+ borderRadius: 9,
555
+ backgroundColor:
556
+ settingsActiveSubTab === 'ui'
557
+ ? AppColors.purple
558
+ : 'transparent',
559
+ }}>
560
+ <ScreenIcon
561
+ color={
562
+ settingsActiveSubTab === 'ui'
563
+ ? AppColors.white
564
+ : AppColors.grayText
565
+ }
566
+ size={13}
567
+ />
568
+ <Text
569
+ numberOfLines={1}
570
+ style={{
571
+ fontFamily: AppFonts.interBold,
572
+ fontSize: 12,
573
+ lineHeight: 16,
574
+ color:
575
+ settingsActiveSubTab === 'ui'
576
+ ? AppColors.white
577
+ : AppColors.grayText,
578
+ }}>
579
+ {t('settings.uiPreferences')}
580
+ </Text>
581
+ </TouchableScale>
582
+
583
+ <TouchableScale
584
+ accessible={true}
585
+ accessibilityRole="tab"
586
+ accessibilityLabel={t('settings.ramLimits')}
587
+ accessibilityState={{selected: settingsActiveSubTab === 'limits'}}
588
+ onPress={() => {
589
+ animateNextLayout();
590
+ setSettingsActiveSubTab('limits');
591
+ }}
592
+ style={{
593
+ flex: 1,
594
+ paddingVertical: 9,
595
+ flexDirection: 'row',
596
+ alignItems: 'center',
597
+ justifyContent: 'center',
598
+ gap: 5,
599
+ borderRadius: 9,
600
+ backgroundColor:
601
+ settingsActiveSubTab === 'limits'
602
+ ? AppColors.purple
603
+ : 'transparent',
604
+ }}>
605
+ <BrainIcon
606
+ color={
607
+ settingsActiveSubTab === 'limits'
608
+ ? AppColors.white
609
+ : AppColors.grayText
610
+ }
611
+ size={13}
612
+ />
613
+ <Text
614
+ numberOfLines={1}
615
+ style={{
616
+ fontFamily: AppFonts.interBold,
617
+ fontSize: 12,
618
+ lineHeight: 16,
619
+ color:
620
+ settingsActiveSubTab === 'limits'
621
+ ? AppColors.white
622
+ : AppColors.grayText,
623
+ }}>
624
+ {t('settings.ramLimits')}
625
+ </Text>
626
+ </TouchableScale>
627
+ </View>
628
+
629
+ {settingsActiveSubTab === 'module' && (
630
+ <View style={{gap: 12}}>
631
+ {/* Individual Module Cards with Left Checkboxes */}
632
+ <View style={{gap: 10}}>
633
+ {allModules.map(moduleItem => {
634
+ const isReduxAvail = isReduxConnected();
635
+ const isAnalyticsAvail = isAnalyticsConnected();
636
+ const isUnavailable =
637
+ (moduleItem.key === 'redux' && !isReduxAvail) ||
638
+ (moduleItem.key === 'analytics' && !isAnalyticsAvail);
639
+ const isLocked = moduleItem.key === 'apis' || isUnavailable;
640
+ const isChecked =
641
+ moduleItem.key === 'apis' ||
642
+ (Boolean(
643
+ stagedTabVisibility?.[moduleItem.key as ActiveTab],
644
+ ) &&
645
+ !isUnavailable);
646
+
647
+ const liveStats =
648
+ moduleItem.key === 'apis'
649
+ ? `${logs.length} requests • Limit: ${maxNetworkLogs}`
650
+ : moduleItem.key === 'logs'
651
+ ? `${consoleLogs.length} logs • Limit: ${maxConsoleLogs}`
652
+ : moduleItem.key === 'performance'
653
+ ? '60 FPS Target • Memory Telemetry'
654
+ : moduleItem.key === 'bundle'
655
+ ? 'Metro Packager • Dependency Map'
656
+ : moduleItem.key === 'crash'
657
+ ? `${
658
+ crashRecords?.length || 0
659
+ } crashes recorded • Crash Guard`
660
+ : moduleItem.key === 'analytics'
661
+ ? `${analyticsEvents.length} events logged`
662
+ : moduleItem.key === 'redux'
663
+ ? `${
664
+ Object.keys(reduxState || {}).length
665
+ } slices • Depth: ${reduxExpandDepth}`
666
+ : '';
667
+
668
+ return (
669
+ <View
670
+ key={moduleItem.key}
671
+ style={{
672
+ backgroundColor: AppColors.primaryLight,
673
+ borderRadius: 14,
674
+ borderWidth: 1.5,
675
+ borderColor: isChecked
676
+ ? `${AppColors.purple}38`
677
+ : AppColors.grayBorderSecondary,
678
+ padding: 14,
679
+ gap: 10,
680
+ shadowColor: AppColors.black,
681
+ shadowOpacity: isChecked ? 0.04 : 0.02,
682
+ shadowRadius: 5,
683
+ shadowOffset: {width: 0, height: 2},
684
+ }}>
685
+ {/* Top Row: [Checkbox] -> [Icon Tile] -> [Title & Subtitle] */}
686
+ <View
687
+ style={{
688
+ flexDirection: 'row',
689
+ alignItems: 'flex-start',
690
+ gap: 10,
691
+ }}>
692
+ {/* 1. Checkbox on the Left */}
693
+ <TouchableScale
694
+ accessible={true}
695
+ accessibilityRole="checkbox"
696
+ accessibilityLabel={`Select ${moduleItem.label}`}
697
+ accessibilityState={{
698
+ checked: isChecked,
699
+ disabled: isLocked,
700
+ }}
701
+ disabled={isLocked}
702
+ onPress={() => {
703
+ if (isLocked) return;
704
+ setStagedTabVisibility(prev => ({
705
+ ...prev,
706
+ [moduleItem.key]:
707
+ !prev[moduleItem.key as ActiveTab],
708
+ }));
709
+ }}
710
+ hitSlop={8}
711
+ style={{
712
+ width: 22,
713
+ height: 22,
714
+ borderRadius: 6,
715
+ borderWidth: isChecked ? 0 : 1.8,
716
+ borderColor: isLocked
717
+ ? AppColors.dividerColor
718
+ : AppColors.grayBorderSecondary,
719
+ backgroundColor:
720
+ moduleItem.key === 'apis'
721
+ ? `${AppColors.blue500}22`
722
+ : isChecked
723
+ ? AppColors.purple
724
+ : AppColors.grayBackground,
725
+ alignItems: 'center',
726
+ justifyContent: 'center',
727
+ marginTop: 7,
728
+ shadowColor: isChecked
729
+ ? AppColors.purple
730
+ : 'transparent',
731
+ shadowOpacity: isChecked ? 0.25 : 0,
732
+ shadowRadius: 2,
733
+ elevation: isChecked ? 2 : 0,
734
+ }}>
735
+ {moduleItem.key === 'apis' ? (
736
+ <CheckIcon size={12} color={AppColors.blue500} />
737
+ ) : isChecked ? (
738
+ <CheckIcon size={12} color={AppColors.white} />
739
+ ) : isLocked ? (
740
+ <Svg
741
+ width={10}
742
+ height={10}
743
+ viewBox="0 0 24 24"
744
+ fill="none">
745
+ <Path
746
+ d="M7 10V7a5 5 0 0 1 10 0v3"
747
+ stroke={AppColors.grayText}
748
+ strokeWidth="2.2"
749
+ strokeLinecap="round"
750
+ />
751
+ <Path
752
+ d="M5 10h14v9a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z"
753
+ fill={AppColors.grayText}
754
+ />
755
+ </Svg>
756
+ ) : null}
757
+ </TouchableScale>
758
+
759
+ {/* 2. Middle Tap Zone: Icon + Title & Desc */}
760
+ <TouchableScale
761
+ accessible={true}
762
+ accessibilityRole="button"
763
+ accessibilityLabel={`${moduleItem.label} settings`}
764
+ disabled={isUnavailable}
765
+ onPress={() => {
766
+ animateNextLayout();
767
+ setSettingsPage(moduleItem.key);
768
+ }}
769
+ style={{
770
+ flex: 1,
771
+ flexDirection: 'row',
772
+ alignItems: 'flex-start',
773
+ gap: 10,
774
+ }}>
775
+ {/* Icon Tile */}
776
+ <View
777
+ style={{
778
+ width: 36,
779
+ height: 36,
780
+ borderRadius: 10,
781
+ backgroundColor: isLocked
782
+ ? `${AppColors.grayBorderSecondary}4D`
783
+ : isChecked
784
+ ? `${AppColors.purple}14`
785
+ : AppColors.grayBackground,
786
+ borderWidth: 1,
787
+ borderColor: isLocked
788
+ ? AppColors.dividerColor
789
+ : isChecked
790
+ ? `${AppColors.purple}33`
791
+ : AppColors.grayBorderSecondary,
792
+ alignItems: 'center',
793
+ justifyContent: 'center',
794
+ marginTop: 1,
795
+ }}>
796
+ {moduleItem.icon === 'apis' && (
797
+ <SignalIcon
798
+ color={
799
+ isChecked
800
+ ? AppColors.purple
801
+ : AppColors.grayTextWeak
802
+ }
803
+ size={16}
804
+ />
805
+ )}
806
+ {moduleItem.icon === 'logs' && (
807
+ <TerminalIcon
808
+ color={
809
+ isChecked
810
+ ? AppColors.purple
811
+ : AppColors.grayTextWeak
812
+ }
813
+ size={16}
814
+ />
815
+ )}
816
+ {moduleItem.icon === 'performance' && (
817
+ <PerformanceIcon
818
+ color={
819
+ isChecked
820
+ ? AppColors.purple
821
+ : AppColors.grayTextWeak
822
+ }
823
+ size={16}
824
+ />
825
+ )}
826
+ {moduleItem.icon === 'bundle' && (
827
+ <PackageIcon
828
+ color={
829
+ isChecked
830
+ ? AppColors.purple
831
+ : AppColors.grayTextWeak
832
+ }
833
+ size={16}
834
+ />
835
+ )}
836
+ {moduleItem.icon === 'crash' && (
837
+ <CrashIcon
838
+ color={
839
+ isChecked
840
+ ? AppColors.purple
841
+ : AppColors.grayTextWeak
842
+ }
843
+ size={16}
844
+ />
845
+ )}
846
+ {moduleItem.icon === 'analytics' && (
847
+ <AnalyticsIcon
848
+ color={
849
+ isChecked
850
+ ? AppColors.purple
851
+ : AppColors.grayTextWeak
852
+ }
853
+ size={16}
854
+ />
855
+ )}
856
+ {moduleItem.icon === 'redux' && (
857
+ <ReduxIcon
858
+ color={
859
+ isChecked
860
+ ? AppColors.purple
861
+ : AppColors.grayTextWeak
862
+ }
863
+ size={16}
864
+ />
865
+ )}
866
+ {moduleItem.icon === 'device' && (
867
+ <SmartphoneIcon
868
+ color={
869
+ isChecked
870
+ ? AppColors.purple
871
+ : AppColors.grayTextWeak
872
+ }
873
+ size={16}
874
+ />
875
+ )}
876
+ {moduleItem.icon === 'storage' && (
877
+ <DatabaseIcon
878
+ color={
879
+ isChecked
880
+ ? AppColors.purple
881
+ : AppColors.grayTextWeak
882
+ }
883
+ size={16}
884
+ />
885
+ )}
886
+ </View>
887
+
888
+ {/* Titles & Status Pill */}
889
+ <View style={{flex: 1, gap: 2}}>
890
+ <View
891
+ style={{
892
+ flexDirection: 'row',
893
+ alignItems: 'center',
894
+ gap: 6,
895
+ flexWrap: 'wrap',
896
+ }}>
897
+ <Text
898
+ style={{
899
+ fontFamily: AppFonts.interBold,
900
+ fontSize: 14,
901
+ lineHeight: 18,
902
+ color: isLocked
903
+ ? AppColors.grayText
904
+ : AppColors.primaryBlack,
905
+ }}>
906
+ {moduleItem.label}
907
+ </Text>
908
+
909
+ {/* Default Startup Tab Badge */}
910
+ {moduleItem.key === defaultTab &&
911
+ !isUnavailable && (
912
+ <View
913
+ style={{
914
+ flexDirection: 'row',
915
+ alignItems: 'center',
916
+ backgroundColor: `${AppColors.purple}14`,
917
+ borderRadius: 12,
918
+ paddingHorizontal: 5,
919
+ paddingVertical: 1.5,
920
+ borderWidth: 1,
921
+ borderColor: `${AppColors.purple}2E`,
922
+ gap: 3,
923
+ }}>
924
+ <View
925
+ style={{
926
+ width: 4,
927
+ height: 4,
928
+ borderRadius: 2,
929
+ backgroundColor: AppColors.purple,
930
+ }}
931
+ />
932
+ <Text
933
+ style={{
934
+ fontFamily: AppFonts.interBold,
935
+ fontSize: 8,
936
+ lineHeight: 11,
937
+ color: AppColors.purple,
938
+ letterSpacing: 0.4,
939
+ }}>
940
+ {t('settings.defaultBadge')}
941
+ </Text>
942
+ </View>
943
+ )}
944
+
945
+ {/* Status Pill */}
946
+ {moduleItem.key === 'apis' ? (
947
+ <View
948
+ style={{
949
+ backgroundColor: `${AppColors.blue500}14`,
950
+ borderRadius: 4,
951
+ paddingHorizontal: 5,
952
+ paddingVertical: 1.5,
953
+ borderWidth: 1,
954
+ borderColor: `${AppColors.blue500}33`,
955
+ }}>
956
+ <Text
957
+ style={{
958
+ fontFamily: AppFonts.interBold,
959
+ fontSize: 8,
960
+ lineHeight: 11,
961
+ color: AppColors.blue500,
962
+ letterSpacing: 0.3,
963
+ }}>
964
+ {t('settings.coreBadge')}
965
+ </Text>
966
+ </View>
967
+ ) : isUnavailable ? (
968
+ <View
969
+ style={{
970
+ backgroundColor: `${AppColors.amber500}18`,
971
+ borderRadius: 4,
972
+ paddingHorizontal: 5,
973
+ paddingVertical: 1.5,
974
+ borderWidth: 1,
975
+ borderColor: `${AppColors.amber500}35`,
976
+ }}>
977
+ <Text
978
+ style={{
979
+ fontFamily: AppFonts.interBold,
980
+ fontSize: 8,
981
+ lineHeight: 11,
982
+ color: AppColors.amber700,
983
+ letterSpacing: 0.3,
984
+ }}>
985
+ {moduleItem.key === 'redux'
986
+ ? t('settings.notConnectedBadge')
987
+ : t('settings.notDetectedBadge')}
988
+ </Text>
989
+ </View>
990
+ ) : isChecked ? (
991
+ <View
992
+ style={{
993
+ backgroundColor: `${AppColors.liveGreen}18`,
994
+ borderRadius: 4,
995
+ paddingHorizontal: 5,
996
+ paddingVertical: 1.5,
997
+ borderWidth: 1,
998
+ borderColor: `${AppColors.liveGreen}38`,
999
+ }}>
1000
+ <Text
1001
+ style={{
1002
+ fontFamily: AppFonts.interBold,
1003
+ fontSize: 8,
1004
+ lineHeight: 11,
1005
+ color: AppColors.green700,
1006
+ letterSpacing: 0.3,
1007
+ }}>
1008
+ {t('settings.activeBadge')}
1009
+ </Text>
1010
+ </View>
1011
+ ) : (
1012
+ <View
1013
+ style={{
1014
+ backgroundColor: `${AppColors.grayTextWeak}18`,
1015
+ borderRadius: 4,
1016
+ paddingHorizontal: 5,
1017
+ paddingVertical: 1.5,
1018
+ borderWidth: 1,
1019
+ borderColor: `${AppColors.grayTextWeak}2E`,
1020
+ }}>
1021
+ <Text
1022
+ style={{
1023
+ fontFamily: AppFonts.interBold,
1024
+ fontSize: 8,
1025
+ lineHeight: 11,
1026
+ color: AppColors.grayTextWeak,
1027
+ letterSpacing: 0.3,
1028
+ }}>
1029
+ {t('settings.dormantBadge')}
1030
+ </Text>
1031
+ </View>
1032
+ )}
1033
+ </View>
1034
+
1035
+ <Text
1036
+ style={{
1037
+ fontFamily: AppFonts.interRegular,
1038
+ fontSize: 11,
1039
+ color: AppColors.grayText,
1040
+ lineHeight: 15,
1041
+ marginTop: 1,
1042
+ }}>
1043
+ {moduleItem.desc}
1044
+ </Text>
1045
+ </View>
1046
+ </TouchableScale>
1047
+ </View>
1048
+
1049
+ {/* Card Footer: Live Stats + Configure Button */}
1050
+ {!isUnavailable && (
1051
+ <View
1052
+ style={{
1053
+ flexDirection: 'row',
1054
+ alignItems: 'center',
1055
+ justifyContent: 'space-between',
1056
+ paddingTop: 8,
1057
+ borderTopWidth: 1,
1058
+ borderTopColor: AppColors.dividerColor,
1059
+ }}>
1060
+ {/* Live Info Pill */}
1061
+ <View
1062
+ style={{
1063
+ flexDirection: 'row',
1064
+ alignItems: 'center',
1065
+ gap: 6,
1066
+ flex: 1,
1067
+ }}>
1068
+ <View
1069
+ style={{
1070
+ width: 6,
1071
+ height: 6,
1072
+ borderRadius: 3,
1073
+ backgroundColor: isChecked
1074
+ ? AppColors.liveGreen
1075
+ : AppColors.grayTextWeak,
1076
+ }}
1077
+ />
1078
+ <Text
1079
+ style={{
1080
+ fontFamily: AppFonts.interMedium,
1081
+ fontSize: 11,
1082
+ color: AppColors.grayText,
1083
+ lineHeight: 14,
1084
+ }}>
1085
+ {liveStats}
1086
+ </Text>
1087
+ </View>
1088
+
1089
+ {/* Sleek Configure Button */}
1090
+ <TouchableScale
1091
+ accessible={true}
1092
+ accessibilityRole="button"
1093
+ accessibilityLabel={`Configure ${moduleItem.label} settings`}
1094
+ onPress={() => {
1095
+ animateNextLayout();
1096
+ setSettingsPage(moduleItem.key);
1097
+ }}
1098
+ style={{
1099
+ flexDirection: 'row',
1100
+ alignItems: 'center',
1101
+ gap: 5,
1102
+ backgroundColor: `${AppColors.purple}12`,
1103
+ paddingHorizontal: 10,
1104
+ paddingVertical: 5,
1105
+ borderRadius: 8,
1106
+ borderWidth: 1,
1107
+ borderColor: `${AppColors.purple}26`,
1108
+ }}>
1109
+ <SettingsIcon color={AppColors.purple} size={11} />
1110
+ <Text
1111
+ style={{
1112
+ fontFamily: AppFonts.interBold,
1113
+ fontSize: 11,
1114
+ lineHeight: 14,
1115
+ color: AppColors.purple,
1116
+ }}>
1117
+ {t('settings.configure')}
1118
+ </Text>
1119
+ <ForwardChevronIcon
1120
+ color={AppColors.purple}
1121
+ size={9}
1122
+ />
1123
+ </TouchableScale>
1124
+ </View>
1125
+ )}
1126
+ </View>
1127
+ );
1128
+ })}
1129
+ </View>
1130
+ </View>
1131
+ )}
1132
+
1133
+ {settingsActiveSubTab === 'ui' && (
1134
+ <View style={{gap: 14}}>
1135
+ {/* Section 1: Appearance */}
1136
+ <View
1137
+ style={{
1138
+ backgroundColor: AppColors.primaryLight,
1139
+ borderRadius: 14,
1140
+ borderWidth: 1,
1141
+ borderColor: AppColors.grayBorderSecondary,
1142
+ overflow: 'hidden',
1143
+ padding: 14,
1144
+ gap: 12,
1145
+ }}>
1146
+ <Text
1147
+ style={{
1148
+ fontFamily: AppFonts.interBold,
1149
+ fontSize: 11,
1150
+ lineHeight: 14,
1151
+ color: AppColors.grayTextWeak,
1152
+ letterSpacing: 0.8,
1153
+ }}>
1154
+ {t('settings.appearanceTheme')}
1155
+ </Text>
1156
+
1157
+ <View
1158
+ style={{
1159
+ flexDirection: 'row',
1160
+ alignItems: 'center',
1161
+ justifyContent: 'space-between',
1162
+ }}>
1163
+ <View
1164
+ style={{
1165
+ flexDirection: 'row',
1166
+ alignItems: 'center',
1167
+ gap: 10,
1168
+ flex: 1,
1169
+ }}>
1170
+ <View
1171
+ style={{
1172
+ width: 32,
1173
+ height: 32,
1174
+ borderRadius: 8,
1175
+ backgroundColor: AppColors.purpleShade50,
1176
+ alignItems: 'center',
1177
+ justifyContent: 'center',
1178
+ }}>
1179
+ {isDark ? (
1180
+ <SunIcon color={AppColors.purple} size={15} />
1181
+ ) : (
1182
+ <MoonIcon color={AppColors.purple} size={15} />
1183
+ )}
1184
+ </View>
1185
+ <View style={{flex: 1}}>
1186
+ <Text
1187
+ style={{
1188
+ fontFamily: AppFonts.interBold,
1189
+ fontSize: 13.5,
1190
+ lineHeight: 18,
1191
+ color: AppColors.primaryBlack,
1192
+ }}>
1193
+ {t('settings.general.darkMode')}
1194
+ </Text>
1195
+ <Text
1196
+ style={{
1197
+ fontFamily: AppFonts.interRegular,
1198
+ fontSize: 11,
1199
+ lineHeight: 15,
1200
+ color: AppColors.grayText,
1201
+ marginTop: 1,
1202
+ }}>
1203
+ {t('settings.general.darkModeDescription')}
1204
+ </Text>
1205
+ </View>
1206
+ </View>
1207
+
1208
+ <TouchableScale
1209
+ accessible={true}
1210
+ accessibilityRole="switch"
1211
+ accessibilityLabel="Toggle Dark Theme"
1212
+ accessibilityState={{checked: isDark}}
1213
+ onPress={() => {
1214
+ const newTheme = !isDark;
1215
+ setIsDark(newTheme);
1216
+ toggleGlobalTheme(newTheme);
1217
+ }}
1218
+ style={{
1219
+ width: 42,
1220
+ height: 24,
1221
+ borderRadius: 12,
1222
+ backgroundColor: isDark
1223
+ ? AppColors.purple
1224
+ : AppColors.grayBorderSecondary,
1225
+ padding: 2,
1226
+ justifyContent: 'center',
1227
+ alignItems: isDark ? 'flex-end' : 'flex-start',
1228
+ }}>
1229
+ <View
1230
+ style={{
1231
+ width: 20,
1232
+ height: 20,
1233
+ borderRadius: 10,
1234
+ backgroundColor: AppColors.white,
1235
+ shadowColor: AppColors.black,
1236
+ shadowOpacity: 0.18,
1237
+ shadowRadius: 2,
1238
+ shadowOffset: {width: 0, height: 1},
1239
+ }}
1240
+ />
1241
+ </TouchableScale>
1242
+ </View>
1243
+ </View>
1244
+
1245
+ {/* Section 2: Window & Layout */}
1246
+ <View
1247
+ style={{
1248
+ backgroundColor: AppColors.primaryLight,
1249
+ borderRadius: 14,
1250
+ borderWidth: 1,
1251
+ borderColor: AppColors.grayBorderSecondary,
1252
+ overflow: 'hidden',
1253
+ padding: 14,
1254
+ gap: 14,
1255
+ }}>
1256
+ <Text
1257
+ style={{
1258
+ fontFamily: AppFonts.interBold,
1259
+ fontSize: 11,
1260
+ lineHeight: 14,
1261
+ color: AppColors.grayTextWeak,
1262
+ letterSpacing: 0.8,
1263
+ }}>
1264
+ {t('settings.windowLayout')}
1265
+ </Text>
1266
+
1267
+ {/* Modal Height */}
1268
+ <View style={{gap: 8}}>
1269
+ <View
1270
+ style={{
1271
+ flexDirection: 'row',
1272
+ alignItems: 'center',
1273
+ justifyContent: 'space-between',
1274
+ gap: 10,
1275
+ }}>
1276
+ <View
1277
+ style={{
1278
+ flexDirection: 'row',
1279
+ alignItems: 'center',
1280
+ gap: 10,
1281
+ flex: 1,
1282
+ }}>
1283
+ <View
1284
+ style={{
1285
+ width: 32,
1286
+ height: 32,
1287
+ borderRadius: 8,
1288
+ backgroundColor: AppColors.purpleShade50,
1289
+ alignItems: 'center',
1290
+ justifyContent: 'center',
1291
+ }}>
1292
+ <ScreenIcon color={AppColors.purple} size={15} />
1293
+ </View>
1294
+ <View style={{flex: 1}}>
1295
+ <Text
1296
+ style={{
1297
+ fontFamily: AppFonts.interBold,
1298
+ fontSize: 13.5,
1299
+ lineHeight: 18,
1300
+ color: AppColors.primaryBlack,
1301
+ }}>
1302
+ {t('settings.general.modalHeight')}
1303
+ </Text>
1304
+ <Text
1305
+ style={{
1306
+ fontFamily: AppFonts.interRegular,
1307
+ fontSize: 11,
1308
+ lineHeight: 15,
1309
+ color: AppColors.grayText,
1310
+ marginTop: 1,
1311
+ }}>
1312
+ {t('settings.general.modalHeightDescription')}
1313
+ </Text>
1314
+ </View>
1315
+ </View>
1316
+
1317
+ {stagedHeight !== modalHeightPercent && (
1318
+ <TouchableScale
1319
+ onPress={() => {
1320
+ setModalHeightPercent(stagedHeight);
1321
+ }}
1322
+ style={{
1323
+ flexDirection: 'row',
1324
+ alignItems: 'center',
1325
+ gap: 5,
1326
+ backgroundColor: AppColors.purple,
1327
+ paddingHorizontal: 10,
1328
+ paddingVertical: 5,
1329
+ borderRadius: 7,
1330
+ shadowColor: AppColors.purple,
1331
+ shadowOffset: {width: 0, height: 1},
1332
+ shadowOpacity: 0.28,
1333
+ shadowRadius: 2,
1334
+ elevation: 2,
1335
+ }}>
1336
+ <CheckIcon size={11} color={AppColors.white} />
1337
+ <Text
1338
+ style={{
1339
+ fontFamily: AppFonts.interBold,
1340
+ fontSize: 11,
1341
+ color: AppColors.white,
1342
+ }}>
1343
+ Save ({stagedHeight}%)
1344
+ </Text>
1345
+ </TouchableScale>
1346
+ )}
1347
+ </View>
1348
+
1349
+ <View style={{marginTop: 6}}>
1350
+ <Slider
1351
+ value={stagedHeight}
1352
+ onValueChange={setStagedHeight}
1353
+ min={50}
1354
+ max={90}
1355
+ step={5}
1356
+ quickPresets={[50, 60, 70, 80, 90]}
1357
+ formatLabel={val => `${Math.round(val)}%`}
1358
+ />
1359
+ </View>
1360
+ </View>
1361
+
1362
+ <View
1363
+ style={{height: 1, backgroundColor: AppColors.dividerColor}}
1364
+ />
1365
+
1366
+ {/* Modal Animation */}
1367
+ <View style={{gap: 8}}>
1368
+ <View
1369
+ style={{
1370
+ flexDirection: 'row',
1371
+ alignItems: 'center',
1372
+ gap: 10,
1373
+ }}>
1374
+ <View
1375
+ style={{
1376
+ width: 32,
1377
+ height: 32,
1378
+ borderRadius: 8,
1379
+ backgroundColor: AppColors.purpleShade50,
1380
+ alignItems: 'center',
1381
+ justifyContent: 'center',
1382
+ }}>
1383
+ <MotionIcon color={AppColors.purple} size={15} />
1384
+ </View>
1385
+ <View style={{flex: 1}}>
1386
+ <Text
1387
+ style={{
1388
+ fontFamily: AppFonts.interBold,
1389
+ fontSize: 13.5,
1390
+ lineHeight: 18,
1391
+ color: AppColors.primaryBlack,
1392
+ }}>
1393
+ {t('settings.general.modalAnimation')}
1394
+ </Text>
1395
+ <Text
1396
+ style={{
1397
+ fontFamily: AppFonts.interRegular,
1398
+ fontSize: 11,
1399
+ lineHeight: 15,
1400
+ color: AppColors.grayText,
1401
+ marginTop: 1,
1402
+ }}>
1403
+ {t('settings.general.modalAnimationDescription')}
1404
+ </Text>
1405
+ </View>
1406
+ </View>
1407
+
1408
+ <View
1409
+ style={{
1410
+ flexDirection: 'row',
1411
+ backgroundColor: AppColors.grayBackground,
1412
+ borderRadius: 10,
1413
+ padding: 3,
1414
+ borderWidth: 1,
1415
+ borderColor: AppColors.dividerColor,
1416
+ }}>
1417
+ {[
1418
+ {key: 'slide' as const, label: 'Slide Up'},
1419
+ {key: 'fade' as const, label: 'Fade'},
1420
+ {key: 'none' as const, label: 'None'},
1421
+ ].map(opt => {
1422
+ const isActive = modalAnimationType === opt.key;
1423
+ return (
1424
+ <TouchableScale
1425
+ key={opt.key}
1426
+ accessible={true}
1427
+ accessibilityRole="button"
1428
+ accessibilityLabel={`Set transition animation to ${opt.label}`}
1429
+ accessibilityState={{selected: isActive}}
1430
+ onPress={() => setModalAnimationType(opt.key)}
1431
+ style={{
1432
+ flex: 1,
1433
+ paddingVertical: 7,
1434
+ alignItems: 'center',
1435
+ borderRadius: 8,
1436
+ backgroundColor: isActive
1437
+ ? AppColors.purple
1438
+ : 'transparent',
1439
+ }}>
1440
+ <Text
1441
+ style={{
1442
+ fontFamily: AppFonts.interBold,
1443
+ fontSize: 11.5,
1444
+ lineHeight: 15,
1445
+ color: isActive
1446
+ ? AppColors.white
1447
+ : AppColors.grayText,
1448
+ }}>
1449
+ {opt.label}
1450
+ </Text>
1451
+ </TouchableScale>
1452
+ );
1453
+ })}
1454
+ </View>
1455
+ </View>
1456
+ </View>
1457
+
1458
+ {/* Section 3: Default Startup Tab */}
1459
+ <View
1460
+ style={{
1461
+ backgroundColor: AppColors.primaryLight,
1462
+ borderRadius: 14,
1463
+ borderWidth: 1,
1464
+ borderColor: AppColors.grayBorderSecondary,
1465
+ overflow: 'hidden',
1466
+ padding: 14,
1467
+ gap: 12,
1468
+ }}>
1469
+ <Text
1470
+ style={{
1471
+ fontFamily: AppFonts.interBold,
1472
+ fontSize: 11,
1473
+ lineHeight: 14,
1474
+ color: AppColors.grayTextWeak,
1475
+ letterSpacing: 0.8,
1476
+ }}>
1477
+ {t('settings.startupDefault')}
1478
+ </Text>
1479
+
1480
+ <View
1481
+ style={{
1482
+ flexDirection: 'row',
1483
+ alignItems: 'center',
1484
+ gap: 10,
1485
+ }}>
1486
+ <View
1487
+ style={{
1488
+ width: 32,
1489
+ height: 32,
1490
+ borderRadius: 8,
1491
+ backgroundColor: AppColors.purpleShade50,
1492
+ alignItems: 'center',
1493
+ justifyContent: 'center',
1494
+ }}>
1495
+ <LayersIcon color={AppColors.purple} size={15} />
1496
+ </View>
1497
+ <View style={{flex: 1}}>
1498
+ <Text
1499
+ style={{
1500
+ fontFamily: AppFonts.interBold,
1501
+ fontSize: 13.5,
1502
+ lineHeight: 18,
1503
+ color: AppColors.primaryBlack,
1504
+ }}>
1505
+ {t('settings.general.defaultOpeningTab')}
1506
+ </Text>
1507
+ <Text
1508
+ style={{
1509
+ fontFamily: AppFonts.interRegular,
1510
+ fontSize: 11,
1511
+ lineHeight: 15,
1512
+ color: AppColors.grayText,
1513
+ marginTop: 1,
1514
+ }}>
1515
+ {t('settings.general.defaultOpeningTabDesc')}
1516
+ </Text>
1517
+ </View>
1518
+ </View>
1519
+
1520
+ {/* Sleek Interactive Dropdown Picker */}
1521
+ <View style={{marginTop: 6, gap: 6}}>
1522
+ <TouchableScale
1523
+ accessible={true}
1524
+ accessibilityRole="button"
1525
+ accessibilityLabel={`Default opening tab: ${
1526
+ allModules.find(m => m.key === defaultTab)?.label || 'APIs'
1527
+ }`}
1528
+ onPress={() => setIsDefaultTabDropdownOpen(prev => !prev)}
1529
+ style={{
1530
+ flexDirection: 'row',
1531
+ alignItems: 'center',
1532
+ justifyContent: 'space-between',
1533
+ backgroundColor: AppColors.grayBackground,
1534
+ borderRadius: 10,
1535
+ borderWidth: 1.5,
1536
+ borderColor: isDefaultTabDropdownOpen
1537
+ ? AppColors.purple
1538
+ : AppColors.grayBorderSecondary,
1539
+ paddingHorizontal: 14,
1540
+ paddingVertical: 11,
1541
+ }}>
1542
+ <View
1543
+ style={{
1544
+ flexDirection: 'row',
1545
+ alignItems: 'center',
1546
+ gap: 10,
1547
+ flex: 1,
1548
+ }}>
1549
+ <View
1550
+ style={{
1551
+ width: 8,
1552
+ height: 8,
1553
+ borderRadius: 4,
1554
+ backgroundColor: AppColors.purple,
1555
+ }}
1556
+ />
1557
+ <Text
1558
+ style={{
1559
+ fontFamily: AppFonts.interBold,
1560
+ fontSize: 13.5,
1561
+ lineHeight: 18,
1562
+ color: AppColors.primaryBlack,
1563
+ }}>
1564
+ {allModules.find(m => m.key === defaultTab)?.label ||
1565
+ 'APIs (Network)'}
1566
+ </Text>
1567
+ <View
1568
+ style={{
1569
+ backgroundColor: `${AppColors.purple}14`,
1570
+ paddingHorizontal: 6,
1571
+ paddingVertical: 1.5,
1572
+ borderRadius: 6,
1573
+ }}>
1574
+ <Text
1575
+ style={{
1576
+ fontFamily: AppFonts.interBold,
1577
+ fontSize: 8.5,
1578
+ color: AppColors.purple,
1579
+ }}>
1580
+ DEFAULT
1581
+ </Text>
1582
+ </View>
1583
+ </View>
1584
+
1585
+ <View
1586
+ style={{
1587
+ transform: [
1588
+ {rotate: isDefaultTabDropdownOpen ? '180deg' : '0deg'},
1589
+ ],
1590
+ }}>
1591
+ <ChevronDownIcon color={AppColors.grayText} size={15} />
1592
+ </View>
1593
+ </TouchableScale>
1594
+
1595
+ {/* Dropdown Options List */}
1596
+ {isDefaultTabDropdownOpen && (
1597
+ <View
1598
+ style={{
1599
+ backgroundColor: AppColors.primaryLight,
1600
+ borderRadius: 10,
1601
+ borderWidth: 1,
1602
+ borderColor: AppColors.grayBorderSecondary,
1603
+ overflow: 'hidden',
1604
+ shadowColor: AppColors.black,
1605
+ shadowOpacity: 0.08,
1606
+ shadowRadius: 6,
1607
+ shadowOffset: {width: 0, height: 3},
1608
+ elevation: 4,
1609
+ }}>
1610
+ {allModules
1611
+ .filter(
1612
+ tab =>
1613
+ tab.key === 'apis' ||
1614
+ stagedTabVisibility?.[tab.key as ActiveTab] ||
1615
+ tabVisibility?.[tab.key as ActiveTab],
1616
+ )
1617
+ .map((tab, idx, arr) => {
1618
+ const isActive = defaultTab === tab.key;
1619
+ const isLast = idx === arr.length - 1;
1620
+ return (
1621
+ <TouchableScale
1622
+ key={tab.key}
1623
+ accessible={true}
1624
+ accessibilityRole="button"
1625
+ accessibilityLabel={`Select ${tab.label} as default tab`}
1626
+ onPress={() => {
1627
+ setDefaultTab(tab.key);
1628
+ setIsDefaultTabDropdownOpen(false);
1629
+ }}
1630
+ style={{
1631
+ flexDirection: 'row',
1632
+ alignItems: 'center',
1633
+ justifyContent: 'space-between',
1634
+ paddingHorizontal: 14,
1635
+ paddingVertical: 11,
1636
+ backgroundColor: isActive
1637
+ ? `${AppColors.purple}0F`
1638
+ : 'transparent',
1639
+ borderBottomWidth: isLast ? 0 : 1,
1640
+ borderBottomColor: AppColors.dividerColor,
1641
+ }}>
1642
+ <View
1643
+ style={{
1644
+ flexDirection: 'row',
1645
+ alignItems: 'center',
1646
+ gap: 10,
1647
+ }}>
1648
+ <View
1649
+ style={{
1650
+ width: 6,
1651
+ height: 6,
1652
+ borderRadius: 3,
1653
+ backgroundColor: isActive
1654
+ ? AppColors.purple
1655
+ : AppColors.grayTextWeak,
1656
+ }}
1657
+ />
1658
+ <Text
1659
+ style={{
1660
+ fontFamily: isActive
1661
+ ? AppFonts.interBold
1662
+ : AppFonts.interMedium,
1663
+ fontSize: 13,
1664
+ color: isActive
1665
+ ? AppColors.purple
1666
+ : AppColors.primaryBlack,
1667
+ }}>
1668
+ {tab.label}
1669
+ </Text>
1670
+ </View>
1671
+ {isActive && (
1672
+ <CheckIcon size={14} color={AppColors.purple} />
1673
+ )}
1674
+ </TouchableScale>
1675
+ );
1676
+ })}
1677
+ </View>
1678
+ )}
1679
+ </View>
1680
+ </View>
1681
+
1682
+ {/* Section 4: Privacy & Diagnostics */}
1683
+ <View
1684
+ style={{
1685
+ backgroundColor: AppColors.primaryLight,
1686
+ borderRadius: 14,
1687
+ borderWidth: 1,
1688
+ borderColor: AppColors.grayBorderSecondary,
1689
+ overflow: 'hidden',
1690
+ padding: 14,
1691
+ gap: 12,
1692
+ }}>
1693
+ <Text
1694
+ style={{
1695
+ fontFamily: AppFonts.interBold,
1696
+ fontSize: 11,
1697
+ lineHeight: 14,
1698
+ color: AppColors.grayTextWeak,
1699
+ letterSpacing: 0.8,
1700
+ }}>
1701
+ PRIVACY & ANONYMOUS DIAGNOSTICS
1702
+ </Text>
1703
+
1704
+ <View
1705
+ style={{
1706
+ flexDirection: 'row',
1707
+ alignItems: 'center',
1708
+ justifyContent: 'space-between',
1709
+ }}>
1710
+ <View
1711
+ style={{
1712
+ flexDirection: 'row',
1713
+ alignItems: 'center',
1714
+ gap: 10,
1715
+ flex: 1,
1716
+ marginRight: 10,
1717
+ }}>
1718
+ <View
1719
+ style={{
1720
+ width: 32,
1721
+ height: 32,
1722
+ borderRadius: 8,
1723
+ backgroundColor: AppColors.purpleShade50,
1724
+ alignItems: 'center',
1725
+ justifyContent: 'center',
1726
+ }}>
1727
+ <ShieldAlertIcon color={AppColors.purple} size={15} />
1728
+ </View>
1729
+ <View style={{flex: 1}}>
1730
+ <Text
1731
+ style={{
1732
+ fontFamily: AppFonts.interBold,
1733
+ fontSize: 13.5,
1734
+ lineHeight: 18,
1735
+ color: AppColors.primaryBlack,
1736
+ }}>
1737
+ Help Improve In-App Inspector
1738
+ </Text>
1739
+ <Text
1740
+ style={{
1741
+ fontFamily: AppFonts.interRegular,
1742
+ fontSize: 11,
1743
+ lineHeight: 15,
1744
+ color: AppColors.grayText,
1745
+ marginTop: 1,
1746
+ }}>
1747
+ Share non-identifiable technical metrics (RN version, JS
1748
+ engine, device model). No user data or network payloads
1749
+ are captured.
1750
+ </Text>
1751
+ </View>
1752
+ </View>
1753
+
1754
+ <TouchableScale
1755
+ accessible={true}
1756
+ accessibilityRole="switch"
1757
+ accessibilityLabel="Toggle Anonymous Telemetry"
1758
+ accessibilityState={{checked: telemetryConsent}}
1759
+ onPress={handleToggleTelemetry}
1760
+ style={{
1761
+ width: 42,
1762
+ height: 24,
1763
+ borderRadius: 12,
1764
+ backgroundColor: telemetryConsent
1765
+ ? AppColors.purple
1766
+ : AppColors.grayBorderSecondary,
1767
+ padding: 2,
1768
+ justifyContent: 'center',
1769
+ alignItems: telemetryConsent ? 'flex-end' : 'flex-start',
1770
+ }}>
1771
+ <View
1772
+ style={{
1773
+ width: 20,
1774
+ height: 20,
1775
+ borderRadius: 10,
1776
+ backgroundColor: AppColors.white,
1777
+ shadowColor: AppColors.black,
1778
+ shadowOpacity: 0.18,
1779
+ shadowRadius: 2,
1780
+ shadowOffset: {width: 0, height: 1},
1781
+ }}
1782
+ />
1783
+ </TouchableScale>
1784
+ </View>
1785
+ </View>
1786
+
1787
+ {/* Section 5: Notifications & Toasts */}
1788
+ <View
1789
+ style={{
1790
+ backgroundColor: AppColors.primaryLight,
1791
+ borderRadius: 14,
1792
+ borderWidth: 1,
1793
+ borderColor: AppColors.grayBorderSecondary,
1794
+ overflow: 'hidden',
1795
+ padding: 14,
1796
+ gap: 12,
1797
+ }}>
1798
+ <Text
1799
+ style={{
1800
+ fontFamily: AppFonts.interBold,
1801
+ fontSize: 11,
1802
+ lineHeight: 14,
1803
+ color: AppColors.grayTextWeak,
1804
+ letterSpacing: 0.8,
1805
+ }}>
1806
+ NOTIFICATIONS & TOASTS
1807
+ </Text>
1808
+
1809
+ <View
1810
+ style={{
1811
+ flexDirection: 'row',
1812
+ alignItems: 'center',
1813
+ justifyContent: 'space-between',
1814
+ }}>
1815
+ <View
1816
+ style={{
1817
+ flexDirection: 'row',
1818
+ alignItems: 'center',
1819
+ gap: 10,
1820
+ flex: 1,
1821
+ marginRight: 10,
1822
+ }}>
1823
+ <View
1824
+ style={{
1825
+ width: 32,
1826
+ height: 32,
1827
+ borderRadius: 8,
1828
+ backgroundColor: AppColors.purpleShade50,
1829
+ alignItems: 'center',
1830
+ justifyContent: 'center',
1831
+ }}>
1832
+ <PackageIcon color={AppColors.purple} size={15} />
1833
+ </View>
1834
+ <View style={{flex: 1}}>
1835
+ <Text
1836
+ style={{
1837
+ fontFamily: AppFonts.interBold,
1838
+ fontSize: 13.5,
1839
+ lineHeight: 18,
1840
+ color: AppColors.primaryBlack,
1841
+ }}>
1842
+ NPM Update Toast
1843
+ </Text>
1844
+ <Text
1845
+ style={{
1846
+ fontFamily: AppFonts.interRegular,
1847
+ fontSize: 11,
1848
+ lineHeight: 15,
1849
+ color: AppColors.grayText,
1850
+ marginTop: 1,
1851
+ }}>
1852
+ Show a floating toast banner with countdown progress when
1853
+ a newer release is published on npm.
1854
+ </Text>
1855
+ </View>
1856
+ </View>
1857
+
1858
+ <TouchableScale
1859
+ accessible={true}
1860
+ accessibilityRole="switch"
1861
+ accessibilityLabel="Toggle NPM Update Toast"
1862
+ accessibilityState={{checked: showUpdateToast}}
1863
+ onPress={() => setShowUpdateToast(prev => !prev)}
1864
+ style={{
1865
+ width: 42,
1866
+ height: 24,
1867
+ borderRadius: 12,
1868
+ backgroundColor: showUpdateToast
1869
+ ? AppColors.purple
1870
+ : AppColors.grayBorderSecondary,
1871
+ padding: 2,
1872
+ justifyContent: 'center',
1873
+ alignItems: showUpdateToast ? 'flex-end' : 'flex-start',
1874
+ }}>
1875
+ <View
1876
+ style={{
1877
+ width: 20,
1878
+ height: 20,
1879
+ borderRadius: 10,
1880
+ backgroundColor: AppColors.white,
1881
+ shadowColor: AppColors.black,
1882
+ shadowOpacity: 0.18,
1883
+ shadowRadius: 2,
1884
+ shadowOffset: {width: 0, height: 1},
1885
+ }}
1886
+ />
1887
+ </TouchableScale>
1888
+ </View>
1889
+ </View>
1890
+
1891
+ {/* Section 6: NPM Package & Updates */}
1892
+ <View
1893
+ style={{
1894
+ backgroundColor: AppColors.primaryLight,
1895
+ borderRadius: 14,
1896
+ borderWidth: 1,
1897
+ borderColor: AppColors.grayBorderSecondary,
1898
+ overflow: 'hidden',
1899
+ padding: 14,
1900
+ gap: 12,
1901
+ }}>
1902
+ <Text
1903
+ style={{
1904
+ fontFamily: AppFonts.interBold,
1905
+ fontSize: 11,
1906
+ lineHeight: 14,
1907
+ color: AppColors.grayTextWeak,
1908
+ letterSpacing: 0.8,
1909
+ }}>
1910
+ PACKAGE VERSION & UPDATES
1911
+ </Text>
1912
+
1913
+ <View
1914
+ style={{
1915
+ flexDirection: 'row',
1916
+ alignItems: 'center',
1917
+ justifyContent: 'space-between',
1918
+ }}>
1919
+ <View
1920
+ style={{
1921
+ flexDirection: 'row',
1922
+ alignItems: 'center',
1923
+ gap: 10,
1924
+ flex: 1,
1925
+ marginRight: 8,
1926
+ }}>
1927
+ <View
1928
+ style={{
1929
+ width: 32,
1930
+ height: 32,
1931
+ borderRadius: 8,
1932
+ backgroundColor: '#CB383715',
1933
+ alignItems: 'center',
1934
+ justifyContent: 'center',
1935
+ }}>
1936
+ <NpmIcon color="#CB3837" size={16} />
1937
+ </View>
1938
+ <View style={{flex: 1}}>
1939
+ <View
1940
+ style={{
1941
+ flexDirection: 'row',
1942
+ alignItems: 'center',
1943
+ gap: 6,
1944
+ }}>
1945
+ <Text
1946
+ style={{
1947
+ fontFamily: AppFonts.interBold,
1948
+ fontSize: 13.5,
1949
+ lineHeight: 18,
1950
+ color: AppColors.primaryBlack,
1951
+ }}>
1952
+ v{LIB_VERSION}
1953
+ </Text>
1954
+ {updateAvailable ? (
1955
+ <View
1956
+ style={{
1957
+ flexDirection: 'row',
1958
+ alignItems: 'center',
1959
+ gap: 3,
1960
+ backgroundColor: '#F59E0B20',
1961
+ paddingHorizontal: 6,
1962
+ paddingVertical: 1.5,
1963
+ borderRadius: 5,
1964
+ borderWidth: 1,
1965
+ borderColor: '#F59E0B60',
1966
+ }}>
1967
+ <Text
1968
+ style={{
1969
+ fontFamily: AppFonts.interBold,
1970
+ fontSize: 9.5,
1971
+ color: '#D97706',
1972
+ }}>
1973
+ v{latestNpmVersion} Available
1974
+ </Text>
1975
+ <BoltIcon size={9} color="#D97706" />
1976
+ </View>
1977
+ ) : (
1978
+ <View
1979
+ style={{
1980
+ backgroundColor: `${AppColors.emerald500}20`,
1981
+ paddingHorizontal: 6,
1982
+ paddingVertical: 1.5,
1983
+ borderRadius: 5,
1984
+ borderWidth: 1,
1985
+ borderColor: `${AppColors.emerald500}50`,
1986
+ }}>
1987
+ <Text
1988
+ style={{
1989
+ fontFamily: AppFonts.interBold,
1990
+ fontSize: 9.5,
1991
+ color: AppColors.emerald600,
1992
+ }}>
1993
+ Up to date
1994
+ </Text>
1995
+ </View>
1996
+ )}
1997
+ </View>
1998
+ <Text
1999
+ style={{
2000
+ fontFamily: AppFonts.interRegular,
2001
+ fontSize: 11,
2002
+ lineHeight: 15,
2003
+ color: AppColors.grayText,
2004
+ marginTop: 1,
2005
+ }}>
2006
+ {updateAvailable
2007
+ ? `A newer version (v${latestNpmVersion}) is available on npm registry.`
2008
+ : 'You are running the latest version from npm registry.'}
2009
+ </Text>
2010
+ </View>
2011
+ </View>
2012
+
2013
+ {updateAvailable && (
2014
+ <TouchableScale
2015
+ onPress={() => {
2016
+ copyToClipboard(
2017
+ 'npm install react-native-inapp-inspector@latest',
2018
+ 'Install Command',
2019
+ );
2020
+ showToast('Copied npm install command!');
2021
+ }}
2022
+ style={{
2023
+ backgroundColor: AppColors.purple,
2024
+ paddingVertical: 6,
2025
+ paddingHorizontal: 10,
2026
+ borderRadius: 8,
2027
+ }}>
2028
+ <Text
2029
+ style={{
2030
+ fontFamily: AppFonts.interBold,
2031
+ fontSize: 11,
2032
+ color: AppColors.white,
2033
+ }}>
2034
+ Copy Upgrade
2035
+ </Text>
2036
+ </TouchableScale>
2037
+ )}
2038
+ </View>
2039
+ </View>
2040
+ </View>
2041
+ )}
2042
+
2043
+ {settingsActiveSubTab === 'limits' && (
2044
+ <View style={{gap: 14}}>
2045
+ {/* Section 1: Device RAM & Auto Profile */}
2046
+ <View
2047
+ style={{
2048
+ backgroundColor: AppColors.primaryLight,
2049
+ borderRadius: 14,
2050
+ borderWidth: 1,
2051
+ borderColor: AppColors.grayBorderSecondary,
2052
+ overflow: 'hidden',
2053
+ padding: 14,
2054
+ gap: 12,
2055
+ }}>
2056
+ <Text
2057
+ style={{
2058
+ fontFamily: AppFonts.interBold,
2059
+ fontSize: 11,
2060
+ lineHeight: 14,
2061
+ color: AppColors.grayTextWeak,
2062
+ letterSpacing: 0.8,
2063
+ }}>
2064
+ DEVICE MEMORY & AUTO PROFILES
2065
+ </Text>
2066
+
2067
+ {/* Auto RAM Limit Toggle */}
2068
+ <View
2069
+ style={{
2070
+ flexDirection: 'row',
2071
+ alignItems: 'center',
2072
+ justifyContent: 'space-between',
2073
+ }}>
2074
+ <View
2075
+ style={{
2076
+ flexDirection: 'row',
2077
+ alignItems: 'center',
2078
+ gap: 10,
2079
+ flex: 1,
2080
+ }}>
2081
+ <View
2082
+ style={{
2083
+ width: 32,
2084
+ height: 32,
2085
+ borderRadius: 8,
2086
+ backgroundColor: AppColors.purpleShade50,
2087
+ alignItems: 'center',
2088
+ justifyContent: 'center',
2089
+ }}>
2090
+ <BrainIcon color={AppColors.purple} size={16} />
2091
+ </View>
2092
+ <View style={{flex: 1}}>
2093
+ <Text
2094
+ style={{
2095
+ fontFamily: AppFonts.interBold,
2096
+ fontSize: 13.5,
2097
+ lineHeight: 18,
2098
+ color: AppColors.primaryBlack,
2099
+ }}>
2100
+ Auto-Calculate Limits from RAM
2101
+ </Text>
2102
+ <Text
2103
+ style={{
2104
+ fontFamily: AppFonts.interRegular,
2105
+ fontSize: 11,
2106
+ lineHeight: 15,
2107
+ color: AppColors.grayText,
2108
+ marginTop: 1,
2109
+ }}>
2110
+ Dynamically sets log limits according to available device memory
2111
+ </Text>
2112
+ </View>
2113
+ </View>
2114
+
2115
+ <TouchableScale
2116
+ accessible={true}
2117
+ accessibilityRole="switch"
2118
+ accessibilityLabel="Toggle auto RAM limit calculation"
2119
+ accessibilityState={{checked: isAutoRamLimitEnabled}}
2120
+ onPress={() => setIsAutoRamLimitEnabled(prev => !prev)}
2121
+ style={{
2122
+ width: 42,
2123
+ height: 24,
2124
+ borderRadius: 12,
2125
+ backgroundColor: isAutoRamLimitEnabled
2126
+ ? AppColors.purple
2127
+ : AppColors.grayBorderSecondary,
2128
+ padding: 2,
2129
+ justifyContent: 'center',
2130
+ alignItems: isAutoRamLimitEnabled
2131
+ ? 'flex-end'
2132
+ : 'flex-start',
2133
+ }}>
2134
+ <View
2135
+ style={{
2136
+ width: 20,
2137
+ height: 20,
2138
+ borderRadius: 10,
2139
+ backgroundColor: AppColors.white,
2140
+ shadowColor: AppColors.black,
2141
+ shadowOpacity: 0.18,
2142
+ shadowRadius: 2,
2143
+ shadowOffset: {width: 0, height: 1},
2144
+ }}
2145
+ />
2146
+ </TouchableScale>
2147
+ </View>
2148
+
2149
+ <View
2150
+ style={{height: 1, backgroundColor: AppColors.dividerColor}}
2151
+ />
2152
+
2153
+ {/* Device Free RAM Display */}
2154
+ <View
2155
+ style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
2156
+ <View
2157
+ style={{
2158
+ width: 32,
2159
+ height: 32,
2160
+ borderRadius: 8,
2161
+ backgroundColor: isAutoRamLimitEnabled
2162
+ ? `${AppColors.purple}14`
2163
+ : AppColors.grayBackground,
2164
+ alignItems: 'center',
2165
+ justifyContent: 'center',
2166
+ }}>
2167
+ <SignalIcon
2168
+ color={
2169
+ isAutoRamLimitEnabled
2170
+ ? AppColors.purple
2171
+ : AppColors.grayTextWeak
2172
+ }
2173
+ size={15}
2174
+ />
2175
+ </View>
2176
+ <View style={{flex: 1}}>
2177
+ <Text
2178
+ style={{
2179
+ fontFamily: AppFonts.interBold,
2180
+ fontSize: 13.5,
2181
+ lineHeight: 18,
2182
+ color: isAutoRamLimitEnabled
2183
+ ? AppColors.primaryBlack
2184
+ : AppColors.grayText,
2185
+ }}>
2186
+ Detected Available RAM
2187
+ </Text>
2188
+ <Text
2189
+ style={{
2190
+ fontFamily: AppFonts.interRegular,
2191
+ fontSize: 11,
2192
+ lineHeight: 15,
2193
+ color: AppColors.grayText,
2194
+ marginTop: 1,
2195
+ }}>
2196
+ {deviceFreeRamMb} MB available on device
2197
+ </Text>
2198
+ </View>
2199
+ <View
2200
+ style={{
2201
+ paddingHorizontal: 10,
2202
+ paddingVertical: 5,
2203
+ borderRadius: 8,
2204
+ backgroundColor: isAutoRamLimitEnabled
2205
+ ? `${AppColors.purple}12`
2206
+ : AppColors.grayBackground,
2207
+ borderWidth: 1,
2208
+ borderColor: isAutoRamLimitEnabled
2209
+ ? `${AppColors.purple}26`
2210
+ : AppColors.dividerColor,
2211
+ }}>
2212
+ <Text
2213
+ style={{
2214
+ fontFamily: AppFonts.interBold,
2215
+ fontSize: 12,
2216
+ lineHeight: 15,
2217
+ color: isAutoRamLimitEnabled
2218
+ ? AppColors.purple
2219
+ : AppColors.grayText,
2220
+ }}>
2221
+ {deviceFreeRamMb} MB
2222
+ </Text>
2223
+ </View>
2224
+ </View>
2225
+
2226
+ {/* Profile Overview */}
2227
+ <View
2228
+ style={{
2229
+ marginTop: 4,
2230
+ paddingTop: 8,
2231
+ borderTopWidth: 1,
2232
+ borderTopColor: AppColors.dividerColor,
2233
+ gap: 6,
2234
+ }}>
2235
+ <View
2236
+ style={{
2237
+ flexDirection: 'row',
2238
+ alignItems: 'center',
2239
+ justifyContent: 'space-between',
2240
+ }}>
2241
+ <Text
2242
+ style={{
2243
+ fontFamily: AppFonts.interBold,
2244
+ fontSize: 11.5,
2245
+ lineHeight: 15,
2246
+ color: AppColors.purple,
2247
+ }}>
2248
+ Profile: {autoRamProfile.profileName}
2249
+ </Text>
2250
+ <View
2251
+ style={{
2252
+ backgroundColor: isAutoRamLimitEnabled
2253
+ ? `${AppColors.emerald500}18`
2254
+ : `${AppColors.purple}18`,
2255
+ paddingHorizontal: 7,
2256
+ paddingVertical: 2.5,
2257
+ borderRadius: 4,
2258
+ }}>
2259
+ <Text
2260
+ style={{
2261
+ fontFamily: AppFonts.interBold,
2262
+ fontSize: 9.5,
2263
+ color: isAutoRamLimitEnabled
2264
+ ? AppColors.emerald500
2265
+ : AppColors.purple,
2266
+ }}>
2267
+ {isAutoRamLimitEnabled ? 'AUTO-TUNED' : 'MANUAL'}
2268
+ </Text>
2269
+ </View>
2270
+ </View>
2271
+
2272
+ <View
2273
+ style={{flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 4}}>
2274
+ {[
2275
+ {
2276
+ label: 'APIs',
2277
+ value: isAutoRamLimitEnabled
2278
+ ? autoRamProfile.maxNetworkLogs
2279
+ : maxNetworkLogs,
2280
+ color: AppColors.blue500,
2281
+ },
2282
+ {
2283
+ label: 'Logs',
2284
+ value: isAutoRamLimitEnabled
2285
+ ? autoRamProfile.maxConsoleLogs
2286
+ : maxConsoleLogs,
2287
+ color: AppColors.sky500,
2288
+ },
2289
+ {
2290
+ label: 'Analytics',
2291
+ value: isAutoRamLimitEnabled
2292
+ ? autoRamProfile.maxAnalyticsEvents
2293
+ : maxAnalyticsEventsLimit,
2294
+ color: AppColors.purple,
2295
+ },
2296
+ {
2297
+ label: 'Crash',
2298
+ value: isAutoRamLimitEnabled
2299
+ ? autoRamProfile.maxCrashRecords
2300
+ : maxCrashLogs,
2301
+ color: AppColors.errorColor,
2302
+ },
2303
+ ].map(item => (
2304
+ <View
2305
+ key={item.label}
2306
+ style={{
2307
+ flexDirection: 'row',
2308
+ alignItems: 'center',
2309
+ gap: 4,
2310
+ paddingHorizontal: 8,
2311
+ paddingVertical: 4,
2312
+ borderRadius: 6,
2313
+ backgroundColor: `${item.color}14`,
2314
+ borderWidth: 1,
2315
+ borderColor: `${item.color}33`,
2316
+ }}>
2317
+ <Text
2318
+ style={{
2319
+ fontFamily: AppFonts.interBold,
2320
+ fontSize: 10.5,
2321
+ lineHeight: 14,
2322
+ color: item.color,
2323
+ }}>
2324
+ {item.label}:
2325
+ </Text>
2326
+ <Text
2327
+ style={{
2328
+ fontFamily: AppFonts.interBold,
2329
+ fontSize: 10.5,
2330
+ lineHeight: 14,
2331
+ color: item.color,
2332
+ }}>
2333
+ {item.value} max
2334
+ </Text>
2335
+ </View>
2336
+ ))}
2337
+ </View>
2338
+ </View>
2339
+ </View>
2340
+
2341
+ {/* Section 2: Memory-Safe OS Pruning Shield */}
2342
+ <View
2343
+ style={{
2344
+ backgroundColor: AppColors.primaryLight,
2345
+ borderRadius: 14,
2346
+ borderWidth: 1,
2347
+ borderColor: AppColors.grayBorderSecondary,
2348
+ overflow: 'hidden',
2349
+ padding: 14,
2350
+ gap: 12,
2351
+ }}>
2352
+ <Text
2353
+ style={{
2354
+ fontFamily: AppFonts.interBold,
2355
+ fontSize: 11,
2356
+ lineHeight: 14,
2357
+ color: AppColors.grayTextWeak,
2358
+ letterSpacing: 0.8,
2359
+ }}>
2360
+ LOW MEMORY PRESSURE PROTECTION
2361
+ </Text>
2362
+
2363
+ <View
2364
+ style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
2365
+ <View
2366
+ style={{
2367
+ width: 32,
2368
+ height: 32,
2369
+ borderRadius: 8,
2370
+ backgroundColor: `${AppColors.emerald500}14`,
2371
+ alignItems: 'center',
2372
+ justifyContent: 'center',
2373
+ }}>
2374
+ <ShieldAlertIcon color={AppColors.emerald500} size={15} />
2375
+ </View>
2376
+ <View style={{flex: 1}}>
2377
+ <Text
2378
+ style={{
2379
+ fontFamily: AppFonts.interBold,
2380
+ fontSize: 13.5,
2381
+ lineHeight: 18,
2382
+ color: AppColors.primaryBlack,
2383
+ }}>
2384
+ OS Memory Warning Guard
2385
+ </Text>
2386
+ <Text
2387
+ style={{
2388
+ fontFamily: AppFonts.interRegular,
2389
+ fontSize: 11,
2390
+ lineHeight: 15,
2391
+ color: AppColors.grayText,
2392
+ marginTop: 1,
2393
+ }}>
2394
+ Automatically prunes in-memory buffers by 50% when the OS signals low memory
2395
+ </Text>
2396
+ </View>
2397
+ <View
2398
+ style={{
2399
+ paddingHorizontal: 8,
2400
+ paddingVertical: 3,
2401
+ borderRadius: 6,
2402
+ backgroundColor: `${AppColors.emerald500}18`,
2403
+ }}>
2404
+ <Text
2405
+ style={{
2406
+ fontFamily: AppFonts.interBold,
2407
+ fontSize: 10,
2408
+ color: AppColors.emerald500,
2409
+ }}>
2410
+ ACTIVE
2411
+ </Text>
2412
+ </View>
2413
+ </View>
2414
+
2415
+ <View
2416
+ style={{height: 1, backgroundColor: AppColors.dividerColor}}
2417
+ />
2418
+
2419
+ {/* Instant Prune Button */}
2420
+ <View
2421
+ style={{
2422
+ flexDirection: 'row',
2423
+ alignItems: 'center',
2424
+ justifyContent: 'space-between',
2425
+ gap: 10,
2426
+ }}>
2427
+ <View style={{flex: 1}}>
2428
+ <Text
2429
+ style={{
2430
+ fontFamily: AppFonts.interBold,
2431
+ fontSize: 13,
2432
+ color: AppColors.primaryBlack,
2433
+ }}>
2434
+ Manual Memory Cleanup
2435
+ </Text>
2436
+ <Text
2437
+ style={{
2438
+ fontFamily: AppFonts.interRegular,
2439
+ fontSize: 11,
2440
+ color: AppColors.grayText,
2441
+ marginTop: 1,
2442
+ }}>
2443
+ Prune older entries across all log stores and free RAM
2444
+ </Text>
2445
+ </View>
2446
+ <TouchableScale
2447
+ onPress={() => {
2448
+ const summary = pruneAllLogs('manual', 0.5);
2449
+ showToast(
2450
+ `Pruned ${summary.totalPruned} items from memory`,
2451
+ );
2452
+ }}
2453
+ style={{
2454
+ backgroundColor: AppColors.purple,
2455
+ paddingHorizontal: 12,
2456
+ paddingVertical: 8,
2457
+ borderRadius: 8,
2458
+ flexDirection: 'row',
2459
+ alignItems: 'center',
2460
+ gap: 5,
2461
+ }}>
2462
+ <TrashIcon size={13} color={AppColors.white} />
2463
+ <Text
2464
+ style={{
2465
+ fontFamily: AppFonts.interBold,
2466
+ fontSize: 11.5,
2467
+ color: AppColors.white,
2468
+ }}>
2469
+ Prune Now
2470
+ </Text>
2471
+ </TouchableScale>
2472
+ </View>
2473
+ </View>
2474
+ </View>
2475
+ )}
2476
+ <View style={{height: 48}} />
2477
+ </ScrollView>
2478
+
2479
+ {/* Bottom Sticky Action Bar for Save Changes */}
2480
+ {settingsActiveSubTab === 'module' && (
2481
+ <View
2482
+ style={{
2483
+ paddingHorizontal: 16,
2484
+ paddingTop: 12,
2485
+ paddingBottom: Platform.OS === 'ios' ? 36 : 24,
2486
+ backgroundColor: AppColors.primaryLight,
2487
+ borderTopWidth: 1,
2488
+ borderTopColor: AppColors.dividerColor,
2489
+ flexDirection: 'row',
2490
+ alignItems: 'center',
2491
+ gap: 12,
2492
+ shadowColor: AppColors.black,
2493
+ shadowOpacity: 0.08,
2494
+ shadowRadius: 8,
2495
+ shadowOffset: {width: 0, height: -3},
2496
+ elevation: 8,
2497
+ }}>
2498
+ <View style={{flex: 1}}>
2499
+ <Text
2500
+ style={{
2501
+ fontFamily: AppFonts.interBold,
2502
+ fontSize: 13,
2503
+ lineHeight: 17,
2504
+ color: AppColors.primaryBlack,
2505
+ }}>
2506
+ {stagedActiveCount} of {allModules.length} Modules Active
2507
+ </Text>
2508
+ <Text
2509
+ style={{
2510
+ fontFamily: AppFonts.interRegular,
2511
+ fontSize: 11,
2512
+ lineHeight: 15,
2513
+ color: hasUnsavedChanges
2514
+ ? AppColors.purple
2515
+ : AppColors.grayText,
2516
+ marginTop: 1,
2517
+ }}>
2518
+ {hasUnsavedChanges
2519
+ ? t('settings.unsavedPending')
2520
+ : t('settings.allSynchronized')}
2521
+ </Text>
2522
+ </View>
2523
+
2524
+ <TouchableScale
2525
+ accessible={true}
2526
+ accessibilityRole="button"
2527
+ accessibilityLabel="Save Changes"
2528
+ onPress={handleSaveChanges}
2529
+ style={{
2530
+ flexDirection: 'row',
2531
+ alignItems: 'center',
2532
+ gap: 6,
2533
+ backgroundColor: hasUnsavedChanges
2534
+ ? AppColors.purple
2535
+ : `${AppColors.purple}22`,
2536
+ paddingHorizontal: 18,
2537
+ paddingVertical: 10,
2538
+ borderRadius: 10,
2539
+ }}>
2540
+ <CheckIcon
2541
+ size={14}
2542
+ color={hasUnsavedChanges ? AppColors.white : AppColors.purple}
2543
+ />
2544
+ <Text
2545
+ style={{
2546
+ fontFamily: AppFonts.interBold,
2547
+ fontSize: 13,
2548
+ lineHeight: 17,
2549
+ color: hasUnsavedChanges ? AppColors.white : AppColors.purple,
2550
+ }}>
2551
+ {t('settings.saveChanges')}
2552
+ </Text>
2553
+ </TouchableScale>
2554
+ </View>
2555
+ )}
2556
+ </View>
2557
+ );
2558
+
2559
+ let content: React.ReactNode = null;
2560
+ let title = '';
2561
+ let icon: React.ReactNode = null;
2562
+ let rightInfo = '';
2563
+
2564
+ if (settingsPage === 'apis') {
2565
+ title = t('settings.apis.title');
2566
+ icon = <SignalIcon color={AppColors.white} size={16} />;
2567
+ rightInfo = t('settings.apis.total', {count: logs.length});
2568
+ content = (
2569
+ <ScrollView
2570
+ style={{flex: 1}}
2571
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
2572
+ <View
2573
+ style={{
2574
+ backgroundColor: AppColors.primaryLight,
2575
+ padding: 16,
2576
+ borderRadius: 14,
2577
+ borderWidth: 1,
2578
+ borderColor: AppColors.grayBorderSecondary,
2579
+ }}>
2580
+ {renderSettingRow({
2581
+ icon: <SignalIcon color={AppColors.purple} size={16} />,
2582
+ label: t('settings.apis.maxRequestLogs'),
2583
+ description: t('settings.apis.maxRequestLogsDescription'),
2584
+ numericInput: {
2585
+ value: maxNetworkLogs,
2586
+ onChange: setMaxNetworkLogs,
2587
+ min: 10,
2588
+ max: 100,
2589
+ placeholder: 'Enter max requests (10-100)',
2590
+ },
2591
+ isLast: true,
2592
+ })}
2593
+ </View>
2594
+
2595
+ <View
2596
+ style={{
2597
+ backgroundColor: AppColors.primaryLight,
2598
+ borderRadius: 14,
2599
+ borderWidth: 1,
2600
+ borderColor: AppColors.grayBorderSecondary,
2601
+ padding: 16,
2602
+ }}>
2603
+ {renderSettingRow({
2604
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
2605
+ label: t('settings.apis.clearNetworkLogs'),
2606
+ description: t('settings.apis.clearNetworkLogsDescription', {
2607
+ count: logs.length,
2608
+ }),
2609
+ isLast: true,
2610
+ onPress: () => {
2611
+ clearNetworkLogs();
2612
+ setSelected(null);
2613
+ Alert.alert(
2614
+ t('common.success'),
2615
+ t('settings.apis.networkLogsCleared'),
2616
+ );
2617
+ },
2618
+ right: (
2619
+ <View
2620
+ style={{
2621
+ paddingHorizontal: 12,
2622
+ paddingVertical: 6,
2623
+ borderRadius: 8,
2624
+ backgroundColor: `${AppColors.errorColor}14`,
2625
+ borderWidth: 1,
2626
+ borderColor: `${AppColors.errorColor}33`,
2627
+ }}>
2628
+ <Text
2629
+ style={{
2630
+ fontFamily: AppFonts.interBold,
2631
+ fontSize: 11,
2632
+ lineHeight: 14,
2633
+ color: AppColors.errorColor,
2634
+ }}>
2635
+ {t('common.clear')}
2636
+ </Text>
2637
+ </View>
2638
+ ),
2639
+ })}
2640
+ </View>
2641
+ <View style={{height: 48}} />
2642
+ </ScrollView>
2643
+ );
2644
+ } else if (settingsPage === 'logs') {
2645
+ title = t('settings.logs.title');
2646
+ icon = <TerminalIcon color={AppColors.white} size={16} />;
2647
+ rightInfo = t('settings.logs.total', {count: consoleLogs.length});
2648
+ content = (
2649
+ <ScrollView
2650
+ style={{flex: 1}}
2651
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
2652
+ <View
2653
+ style={{
2654
+ backgroundColor: AppColors.primaryLight,
2655
+ padding: 16,
2656
+ borderRadius: 14,
2657
+ borderWidth: 1,
2658
+ borderColor: AppColors.grayBorderSecondary,
2659
+ gap: 4,
2660
+ }}>
2661
+ {renderSettingRow({
2662
+ icon: <TerminalIcon color={AppColors.purple} size={16} />,
2663
+ label: t('settings.logs.maxConsoleLogs'),
2664
+ description: t('settings.logs.maxConsoleLogsDescription'),
2665
+ numericInput: {
2666
+ value: maxConsoleLogs,
2667
+ onChange: setMaxConsoleLogs,
2668
+ min: 10,
2669
+ max: 100,
2670
+ placeholder: 'Enter max logs (10-100)',
2671
+ },
2672
+ })}
2673
+ <View style={{height: 1, backgroundColor: AppColors.dividerColor}} />
2674
+ <Text
2675
+ style={{
2676
+ fontFamily: AppFonts.interBold,
2677
+ fontSize: 13,
2678
+ lineHeight: 17,
2679
+ color: AppColors.primaryBlack,
2680
+ paddingTop: 6,
2681
+ }}>
2682
+ {t('settings.logs.logLevels')}
2683
+ </Text>
2684
+ {(['info', 'warn', 'error'] as const).map(level => {
2685
+ const isLvlActive = showConsoleLevels?.[level];
2686
+ const levelColor =
2687
+ level === 'error'
2688
+ ? AppColors.errorColor
2689
+ : level === 'warn'
2690
+ ? AppColors.warningIconGold
2691
+ : AppColors.skyBlue;
2692
+ const label =
2693
+ level === 'info'
2694
+ ? t('settings.logs.showInfo')
2695
+ : level === 'warn'
2696
+ ? t('settings.logs.showWarn')
2697
+ : t('settings.logs.showError');
2698
+ const desc =
2699
+ level === 'info'
2700
+ ? t('settings.logs.showInfoDesc')
2701
+ : level === 'warn'
2702
+ ? t('settings.logs.showWarnDesc')
2703
+ : t('settings.logs.showErrorDesc');
2704
+
2705
+ return renderSettingRow({
2706
+ icon: (
2707
+ <View
2708
+ style={{
2709
+ width: 12,
2710
+ height: 12,
2711
+ borderRadius: 6,
2712
+ backgroundColor: levelColor,
2713
+ }}
2714
+ />
2715
+ ),
2716
+ label,
2717
+ description: desc,
2718
+ isLast: level === 'error',
2719
+ onPress: () =>
2720
+ setShowConsoleLevels(prev => ({
2721
+ ...prev,
2722
+ [level]: !prev[level],
2723
+ })),
2724
+ right: (
2725
+ <View
2726
+ style={{
2727
+ width: 22,
2728
+ height: 22,
2729
+ borderRadius: 6,
2730
+ borderWidth: 2,
2731
+ borderColor: isLvlActive
2732
+ ? AppColors.purple
2733
+ : AppColors.grayTextWeak,
2734
+ backgroundColor: isLvlActive
2735
+ ? `${AppColors.purple}1A`
2736
+ : 'transparent',
2737
+ alignItems: 'center',
2738
+ justifyContent: 'center',
2739
+ }}>
2740
+ {isLvlActive && (
2741
+ <CheckIcon size={12} color={AppColors.purple} />
2742
+ )}
2743
+ </View>
2744
+ ),
2745
+ });
2746
+ })}
2747
+ </View>
2748
+
2749
+ {/* Log Deduplication Card */}
2750
+ <View
2751
+ style={{
2752
+ backgroundColor: AppColors.primaryLight,
2753
+ borderRadius: 14,
2754
+ borderWidth: 1,
2755
+ borderColor: AppColors.grayBorderSecondary,
2756
+ overflow: 'hidden',
2757
+ padding: 16,
2758
+ gap: 14,
2759
+ }}>
2760
+ <Text
2761
+ style={{
2762
+ fontFamily: AppFonts.interBold,
2763
+ fontSize: 11,
2764
+ lineHeight: 14,
2765
+ color: AppColors.grayTextWeak,
2766
+ letterSpacing: 0.8,
2767
+ }}>
2768
+ {t('settings.logFiltersDeduplication')}
2769
+ </Text>
2770
+
2771
+ {/* Show Duplicate Logs */}
2772
+ <View
2773
+ style={{
2774
+ flexDirection: 'row',
2775
+ alignItems: 'center',
2776
+ justifyContent: 'space-between',
2777
+ }}>
2778
+ <View
2779
+ style={{
2780
+ flexDirection: 'row',
2781
+ alignItems: 'center',
2782
+ gap: 10,
2783
+ flex: 1,
2784
+ }}>
2785
+ <View
2786
+ style={{
2787
+ width: 32,
2788
+ height: 32,
2789
+ borderRadius: 8,
2790
+ backgroundColor: AppColors.purpleShade50,
2791
+ alignItems: 'center',
2792
+ justifyContent: 'center',
2793
+ }}>
2794
+ <EyeIcon color={AppColors.purple} size={15} />
2795
+ </View>
2796
+ <View style={{flex: 1}}>
2797
+ <Text
2798
+ style={{
2799
+ fontFamily: AppFonts.interBold,
2800
+ fontSize: 13.5,
2801
+ lineHeight: 18,
2802
+ color: AppColors.primaryBlack,
2803
+ }}>
2804
+ {t('settings.general.duplicateLogs')}
2805
+ </Text>
2806
+ <Text
2807
+ style={{
2808
+ fontFamily: AppFonts.interRegular,
2809
+ fontSize: 11,
2810
+ lineHeight: 15,
2811
+ color: AppColors.grayText,
2812
+ marginTop: 1,
2813
+ }}>
2814
+ {t('settings.general.duplicateLogsDescription')}
2815
+ </Text>
2816
+ </View>
2817
+ </View>
2818
+
2819
+ <TouchableScale
2820
+ accessible={true}
2821
+ accessibilityRole="switch"
2822
+ accessibilityLabel="Toggle show duplicate logs"
2823
+ accessibilityState={{checked: showDuplicateLogs}}
2824
+ onPress={() => setShowDuplicateLogs(prev => !prev)}
2825
+ style={{
2826
+ width: 42,
2827
+ height: 24,
2828
+ borderRadius: 12,
2829
+ backgroundColor: showDuplicateLogs
2830
+ ? AppColors.purple
2831
+ : AppColors.grayBorderSecondary,
2832
+ padding: 2,
2833
+ justifyContent: 'center',
2834
+ alignItems: showDuplicateLogs ? 'flex-end' : 'flex-start',
2835
+ }}>
2836
+ <View
2837
+ style={{
2838
+ width: 20,
2839
+ height: 20,
2840
+ borderRadius: 10,
2841
+ backgroundColor: AppColors.white,
2842
+ shadowColor: AppColors.black,
2843
+ shadowOpacity: 0.18,
2844
+ shadowRadius: 2,
2845
+ shadowOffset: {width: 0, height: 1},
2846
+ }}
2847
+ />
2848
+ </TouchableScale>
2849
+ </View>
2850
+ </View>
2851
+
2852
+ <View
2853
+ style={{
2854
+ backgroundColor: AppColors.primaryLight,
2855
+ borderRadius: 14,
2856
+ borderWidth: 1,
2857
+ borderColor: AppColors.grayBorderSecondary,
2858
+ padding: 16,
2859
+ }}>
2860
+ {renderSettingRow({
2861
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
2862
+ label: t('settings.logs.clearConsoleLogs'),
2863
+ description: t('settings.logs.clearConsoleLogsDescription', {
2864
+ count: consoleLogs.length,
2865
+ }),
2866
+ isLast: true,
2867
+ onPress: () => {
2868
+ clearConsoleLogs();
2869
+ Alert.alert(
2870
+ t('common.success'),
2871
+ t('settings.logs.consoleLogsCleared'),
2872
+ );
2873
+ },
2874
+ right: (
2875
+ <View
2876
+ style={{
2877
+ paddingHorizontal: 12,
2878
+ paddingVertical: 6,
2879
+ borderRadius: 8,
2880
+ backgroundColor: `${AppColors.errorColor}14`,
2881
+ borderWidth: 1,
2882
+ borderColor: `${AppColors.errorColor}33`,
2883
+ }}>
2884
+ <Text
2885
+ style={{
2886
+ fontFamily: AppFonts.interBold,
2887
+ fontSize: 11,
2888
+ lineHeight: 14,
2889
+ color: AppColors.errorColor,
2890
+ }}>
2891
+ {t('common.clear')}
2892
+ </Text>
2893
+ </View>
2894
+ ),
2895
+ })}
2896
+ </View>
2897
+ <View style={{height: 48}} />
2898
+ </ScrollView>
2899
+ );
2900
+ } else if (settingsPage === 'analytics') {
2901
+ title = t('settings.analytics.title');
2902
+ icon = <AnalyticsIcon color={AppColors.white} size={16} />;
2903
+ rightInfo = t('settings.analytics.total', {count: analyticsEvents.length});
2904
+ content = (
2905
+ <ScrollView
2906
+ style={{flex: 1}}
2907
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
2908
+ <View
2909
+ style={{
2910
+ backgroundColor: AppColors.primaryLight,
2911
+ padding: 16,
2912
+ borderRadius: 14,
2913
+ borderWidth: 1,
2914
+ borderColor: AppColors.grayBorderSecondary,
2915
+ }}>
2916
+ {renderSettingRow({
2917
+ icon: <AnalyticsIcon color={AppColors.purple} size={16} />,
2918
+ label: t('settings.analytics.maxAnalyticsEvents'),
2919
+ description: t('settings.analytics.maxAnalyticsEventsDescription', {
2920
+ count: analyticsEvents.length,
2921
+ }),
2922
+ numericInput: {
2923
+ value: maxAnalyticsEventsLimit,
2924
+ onChange: setMaxAnalyticsEventsLimit,
2925
+ min: 10,
2926
+ max: 75,
2927
+ placeholder: 'Enter max events (10-75)',
2928
+ },
2929
+ isLast: true,
2930
+ })}
2931
+ </View>
2932
+ <View
2933
+ style={{
2934
+ backgroundColor: AppColors.primaryLight,
2935
+ borderRadius: 14,
2936
+ borderWidth: 1,
2937
+ borderColor: AppColors.grayBorderSecondary,
2938
+ padding: 16,
2939
+ }}>
2940
+ {renderSettingRow({
2941
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
2942
+ label: t('settings.analytics.clearAnalyticsEvents'),
2943
+ description: t(
2944
+ 'settings.analytics.clearAnalyticsEventsDescription',
2945
+ ),
2946
+ isLast: true,
2947
+ onPress: () => {
2948
+ clearAnalyticsEvents();
2949
+ setSelectedEvent(null);
2950
+ Alert.alert(
2951
+ t('common.success'),
2952
+ t('settings.analytics.analyticsEventsCleared'),
2953
+ );
2954
+ },
2955
+ right: (
2956
+ <View
2957
+ style={{
2958
+ paddingHorizontal: 12,
2959
+ paddingVertical: 6,
2960
+ borderRadius: 8,
2961
+ backgroundColor: `${AppColors.errorColor}14`,
2962
+ borderWidth: 1,
2963
+ borderColor: `${AppColors.errorColor}33`,
2964
+ }}>
2965
+ <Text
2966
+ style={{
2967
+ fontFamily: AppFonts.interBold,
2968
+ fontSize: 11,
2969
+ lineHeight: 14,
2970
+ color: AppColors.errorColor,
2971
+ }}>
2972
+ {t('common.clear')}
2973
+ </Text>
2974
+ </View>
2975
+ ),
2976
+ })}
2977
+ </View>
2978
+ <View style={{height: 48}} />
2979
+ </ScrollView>
2980
+ );
2981
+ } else if (settingsPage === 'redux') {
2982
+ title = t('settings.redux.title');
2983
+ icon = <ReduxIcon color={AppColors.white} size={16} />;
2984
+ rightInfo = t('settings.redux.reducers', {
2985
+ count: Object.keys(reduxState || {}).length,
2986
+ });
2987
+ content = (
2988
+ <ScrollView
2989
+ style={{flex: 1}}
2990
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
2991
+ <View
2992
+ style={{
2993
+ backgroundColor: AppColors.primaryLight,
2994
+ padding: 16,
2995
+ borderRadius: 14,
2996
+ borderWidth: 1,
2997
+ borderColor: AppColors.grayBorderSecondary,
2998
+ gap: 4,
2999
+ }}>
3000
+ {renderSettingRow({
3001
+ icon: <ReduxIcon color={AppColors.purple} size={16} />,
3002
+ label: t('settings.redux.autoRefresh'),
3003
+ description: t('settings.redux.autoRefreshDescription'),
3004
+ onPress: () => setReduxAutoRefreshState(prev => !prev),
3005
+ right: (
3006
+ <View
3007
+ style={{
3008
+ width: 22,
3009
+ height: 22,
3010
+ borderRadius: 6,
3011
+ borderWidth: 2,
3012
+ borderColor: reduxAutoRefresh
3013
+ ? AppColors.purple
3014
+ : AppColors.grayTextWeak,
3015
+ backgroundColor: reduxAutoRefresh
3016
+ ? `${AppColors.purple}1A`
3017
+ : 'transparent',
3018
+ alignItems: 'center',
3019
+ justifyContent: 'center',
3020
+ }}>
3021
+ {reduxAutoRefresh && (
3022
+ <CheckIcon size={12} color={AppColors.purple} />
3023
+ )}
3024
+ </View>
3025
+ ),
3026
+ })}
3027
+ <View style={{height: 1, backgroundColor: AppColors.dividerColor}} />
3028
+ {renderSettingRow({
3029
+ icon: <LayersIcon color={AppColors.purple} size={16} />,
3030
+ label: t('settings.redux.defaultJsonExpandDepth'),
3031
+ description: t('settings.redux.defaultJsonExpandDepthDescription'),
3032
+ picker: {
3033
+ options: [1, 2, 3, 5] as const,
3034
+ selectedValue: reduxExpandDepth,
3035
+ onSelect: setReduxExpandDepth,
3036
+ },
3037
+ isLast: true,
3038
+ })}
3039
+ </View>
3040
+
3041
+ <View
3042
+ style={{
3043
+ backgroundColor: AppColors.primaryLight,
3044
+ borderRadius: 14,
3045
+ borderWidth: 1,
3046
+ borderColor: AppColors.grayBorderSecondary,
3047
+ padding: 16,
3048
+ }}>
3049
+ {renderSettingRow({
3050
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
3051
+ label: t('settings.redux.clearReduxState'),
3052
+ description: reduxState
3053
+ ? t('settings.redux.clearReduxStateDescription')
3054
+ : t('settings.redux.clearReduxStateEmpty'),
3055
+ isLast: true,
3056
+ onPress: () => {
3057
+ setReduxState(null);
3058
+ Alert.alert(
3059
+ t('common.success'),
3060
+ t('settings.redux.reduxStateCleared'),
3061
+ );
3062
+ },
3063
+ right: (
3064
+ <View
3065
+ style={{
3066
+ paddingHorizontal: 12,
3067
+ paddingVertical: 6,
3068
+ borderRadius: 8,
3069
+ backgroundColor: `${AppColors.errorColor}14`,
3070
+ borderWidth: 1,
3071
+ borderColor: `${AppColors.errorColor}33`,
3072
+ }}>
3073
+ <Text
3074
+ style={{
3075
+ fontFamily: AppFonts.interBold,
3076
+ fontSize: 11,
3077
+ lineHeight: 14,
3078
+ color: AppColors.errorColor,
3079
+ }}>
3080
+ {t('common.clear')}
3081
+ </Text>
3082
+ </View>
3083
+ ),
3084
+ })}
3085
+ </View>
3086
+ <View style={{height: 48}} />
3087
+ </ScrollView>
3088
+ );
3089
+ } else if (settingsPage === 'crash') {
3090
+ title = t('settings.crash.title');
3091
+ icon = <CrashIcon color={AppColors.white} size={16} />;
3092
+ rightInfo = `Total: ${crashRecords?.length || 0}`;
3093
+ content = (
3094
+ <ScrollView
3095
+ style={{flex: 1}}
3096
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
3097
+ <View
3098
+ style={{
3099
+ backgroundColor: AppColors.primaryLight,
3100
+ padding: 16,
3101
+ borderRadius: 14,
3102
+ borderWidth: 1,
3103
+ borderColor: AppColors.grayBorderSecondary,
3104
+ gap: 4,
3105
+ }}>
3106
+ {renderSettingRow({
3107
+ icon: <LayersIcon color={AppColors.purple} size={16} />,
3108
+ label: t('settings.crash.maxCrashLogs'),
3109
+ description: t('settings.crash.maxCrashLogsDesc'),
3110
+ numericInput: {
3111
+ value: maxCrashLogs,
3112
+ onChange: setMaxCrashLogs,
3113
+ min: 5,
3114
+ max: 50,
3115
+ placeholder: 'Enter max crashes (5-50)',
3116
+ },
3117
+ })}
3118
+ <View style={{height: 1, backgroundColor: AppColors.dividerColor}} />
3119
+ {renderSettingRow({
3120
+ icon: <ShieldAlertIcon color={AppColors.greenColor} size={16} />,
3121
+ label: t('settings.crash.globalGuard'),
3122
+ description: t('settings.crash.globalGuardDesc'),
3123
+ isLast: true,
3124
+ right: (
3125
+ <View
3126
+ style={{
3127
+ paddingHorizontal: 8,
3128
+ paddingVertical: 4,
3129
+ borderRadius: 6,
3130
+ backgroundColor: `${AppColors.greenColor}1F`,
3131
+ }}>
3132
+ <Text
3133
+ style={{
3134
+ fontFamily: AppFonts.interBold,
3135
+ fontSize: 10,
3136
+ lineHeight: 13,
3137
+ color: AppColors.greenColor,
3138
+ }}>
3139
+ {t('settings.protectedBadge')}
3140
+ </Text>
3141
+ </View>
3142
+ ),
3143
+ })}
3144
+ </View>
3145
+
3146
+ <View
3147
+ style={{
3148
+ backgroundColor: AppColors.primaryLight,
3149
+ borderRadius: 14,
3150
+ borderWidth: 1,
3151
+ borderColor: AppColors.grayBorderSecondary,
3152
+ padding: 16,
3153
+ }}>
3154
+ {renderSettingRow({
3155
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
3156
+ label: t('settings.crash.clearHistory'),
3157
+ description: t('settings.crash.clearHistoryDesc', {
3158
+ count: crashRecords?.length || 0,
3159
+ }),
3160
+ isLast: true,
3161
+ onPress: () => {
3162
+ clearCrashRecords();
3163
+ Alert.alert(
3164
+ t('common.success'),
3165
+ t('settings.crash.historyCleared'),
3166
+ );
3167
+ },
3168
+ right: (
3169
+ <View
3170
+ style={{
3171
+ paddingHorizontal: 12,
3172
+ paddingVertical: 6,
3173
+ borderRadius: 8,
3174
+ backgroundColor: `${AppColors.errorColor}14`,
3175
+ borderWidth: 1,
3176
+ borderColor: `${AppColors.errorColor}33`,
3177
+ }}>
3178
+ <Text
3179
+ style={{
3180
+ fontFamily: AppFonts.interBold,
3181
+ fontSize: 11,
3182
+ lineHeight: 14,
3183
+ color: AppColors.errorColor,
3184
+ }}>
3185
+ {t('common.clear')}
3186
+ </Text>
3187
+ </View>
3188
+ ),
3189
+ })}
3190
+ </View>
3191
+ <View style={{height: 48}} />
3192
+ </ScrollView>
3193
+ );
3194
+ } else if (settingsPage === 'bundle') {
3195
+ title = t('settings.bundle.title');
3196
+ icon = <PackageIcon color={AppColors.white} size={16} />;
3197
+ rightInfo = 'Metro';
3198
+ content = (
3199
+ <ScrollView
3200
+ style={{flex: 1}}
3201
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
3202
+ <View
3203
+ style={{
3204
+ backgroundColor: AppColors.primaryLight,
3205
+ padding: 16,
3206
+ borderRadius: 14,
3207
+ borderWidth: 1,
3208
+ borderColor: AppColors.grayBorderSecondary,
3209
+ gap: 8,
3210
+ }}>
3211
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
3212
+ <View
3213
+ style={{
3214
+ width: 36,
3215
+ height: 36,
3216
+ borderRadius: 10,
3217
+ backgroundColor: AppColors.purpleShade50,
3218
+ alignItems: 'center',
3219
+ justifyContent: 'center',
3220
+ }}>
3221
+ <PackageIcon color={AppColors.purple} size={16} />
3222
+ </View>
3223
+ <View style={{flex: 1}}>
3224
+ <Text
3225
+ style={{
3226
+ fontFamily: AppFonts.interBold,
3227
+ fontSize: 14,
3228
+ lineHeight: 18,
3229
+ color: AppColors.primaryBlack,
3230
+ }}>
3231
+ {t('settings.bundle.sourceBundler')}
3232
+ </Text>
3233
+ <Text
3234
+ style={{
3235
+ fontFamily: AppFonts.interRegular,
3236
+ fontSize: 11,
3237
+ lineHeight: 15,
3238
+ color: AppColors.grayText,
3239
+ marginTop: 1,
3240
+ }}>
3241
+ {t('settings.bundle.sourceBundlerDesc')}
3242
+ </Text>
3243
+ </View>
3244
+ </View>
3245
+ </View>
3246
+
3247
+ <View
3248
+ style={{
3249
+ backgroundColor: AppColors.primaryLight,
3250
+ borderRadius: 14,
3251
+ borderWidth: 1,
3252
+ borderColor: AppColors.grayBorderSecondary,
3253
+ padding: 16,
3254
+ }}>
3255
+ {renderSettingRow({
3256
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
3257
+ label: t('settings.bundle.clearCache'),
3258
+ description: t('settings.bundle.clearCacheDesc'),
3259
+ isLast: true,
3260
+ onPress: () => {
3261
+ clearCachedBundleAnalysis();
3262
+ Alert.alert(
3263
+ t('common.success'),
3264
+ t('settings.bundle.cacheCleared'),
3265
+ );
3266
+ },
3267
+ right: (
3268
+ <View
3269
+ style={{
3270
+ paddingHorizontal: 12,
3271
+ paddingVertical: 6,
3272
+ borderRadius: 8,
3273
+ backgroundColor: `${AppColors.errorColor}14`,
3274
+ borderWidth: 1,
3275
+ borderColor: `${AppColors.errorColor}33`,
3276
+ }}>
3277
+ <Text
3278
+ style={{
3279
+ fontFamily: AppFonts.interBold,
3280
+ fontSize: 11,
3281
+ lineHeight: 14,
3282
+ color: AppColors.errorColor,
3283
+ }}>
3284
+ {t('common.clear')}
3285
+ </Text>
3286
+ </View>
3287
+ ),
3288
+ })}
3289
+ </View>
3290
+ <View style={{height: 48}} />
3291
+ </ScrollView>
3292
+ );
3293
+ } else if (settingsPage === 'performance') {
3294
+ title = t('settings.performance.title');
3295
+ icon = <PerformanceIcon color={AppColors.white} size={16} />;
3296
+ rightInfo = '60 FPS Target';
3297
+ content = (
3298
+ <ScrollView
3299
+ style={{flex: 1}}
3300
+ contentContainerStyle={{padding: 16, paddingBottom: 100, gap: 12}}>
3301
+ <View
3302
+ style={{
3303
+ backgroundColor: AppColors.primaryLight,
3304
+ padding: 16,
3305
+ borderRadius: 14,
3306
+ borderWidth: 1,
3307
+ borderColor: AppColors.grayBorderSecondary,
3308
+ gap: 8,
3309
+ }}>
3310
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
3311
+ <View
3312
+ style={{
3313
+ width: 36,
3314
+ height: 36,
3315
+ borderRadius: 10,
3316
+ backgroundColor: AppColors.purpleShade50,
3317
+ alignItems: 'center',
3318
+ justifyContent: 'center',
3319
+ }}>
3320
+ <PerformanceIcon color={AppColors.purple} size={16} />
3321
+ </View>
3322
+ <View style={{flex: 1}}>
3323
+ <Text
3324
+ style={{
3325
+ fontFamily: AppFonts.interBold,
3326
+ fontSize: 14,
3327
+ lineHeight: 18,
3328
+ color: AppColors.primaryBlack,
3329
+ }}>
3330
+ {t('settings.performance.frameMeasurement')}
3331
+ </Text>
3332
+ <Text
3333
+ style={{
3334
+ fontFamily: AppFonts.interRegular,
3335
+ fontSize: 11,
3336
+ lineHeight: 15,
3337
+ color: AppColors.grayText,
3338
+ marginTop: 1,
3339
+ }}>
3340
+ {t('settings.performance.frameMeasurementDesc')}
3341
+ </Text>
3342
+ </View>
3343
+ </View>
3344
+ </View>
3345
+
3346
+ <View
3347
+ style={{
3348
+ backgroundColor: AppColors.primaryLight,
3349
+ borderRadius: 14,
3350
+ borderWidth: 1,
3351
+ borderColor: AppColors.grayBorderSecondary,
3352
+ padding: 16,
3353
+ }}>
3354
+ {renderSettingRow({
3355
+ icon: <TrashIcon color={AppColors.errorColor} size={16} />,
3356
+ label: t('settings.performance.clearEvents'),
3357
+ description: t('settings.performance.clearEventsDesc'),
3358
+ isLast: true,
3359
+ onPress: () => {
3360
+ clearPerformanceEvents();
3361
+ Alert.alert(
3362
+ t('common.success'),
3363
+ t('settings.performance.eventsCleared'),
3364
+ );
3365
+ },
3366
+ right: (
3367
+ <View
3368
+ style={{
3369
+ paddingHorizontal: 12,
3370
+ paddingVertical: 6,
3371
+ borderRadius: 8,
3372
+ backgroundColor: `${AppColors.errorColor}14`,
3373
+ borderWidth: 1,
3374
+ borderColor: `${AppColors.errorColor}33`,
3375
+ }}>
3376
+ <Text
3377
+ style={{
3378
+ fontFamily: AppFonts.interBold,
3379
+ fontSize: 11,
3380
+ lineHeight: 14,
3381
+ color: AppColors.errorColor,
3382
+ }}>
3383
+ {t('common.clear')}
3384
+ </Text>
3385
+ </View>
3386
+ ),
3387
+ })}
3388
+ </View>
3389
+ <View style={{height: 48}} />
3390
+ </ScrollView>
3391
+ );
3392
+ }
3393
+
3394
+ const subPageAnim = useRef(new Animated.Value(0)).current;
3395
+ useEffect(() => {
3396
+ if (settingsPage !== 'main') {
3397
+ subPageAnim.setValue(0);
3398
+ Animated.spring(subPageAnim, {
3399
+ toValue: 1,
3400
+ friction: 8,
3401
+ tension: 65,
3402
+ useNativeDriver: true,
3403
+ }).start();
3404
+ }
3405
+ }, [settingsPage]);
3406
+
3407
+ return (
3408
+ <View
3409
+ style={{
3410
+ flex: 1,
3411
+ backgroundColor: AppColors.grayBackground,
3412
+ }}>
3413
+ {/* ─── Persistent Main Settings (Retains 100% scroll position) ─── */}
3414
+ <View
3415
+ style={[
3416
+ StyleSheet.absoluteFill,
3417
+ settingsPage !== 'main' && {pointerEvents: 'none'},
3418
+ ]}>
3419
+ {renderMainSettingsContent()}
3420
+ </View>
3421
+
3422
+ {/* ─── Sub-module Settings Overlay (Smooth Animated Slide) ─── */}
3423
+ {settingsPage !== 'main' && (
3424
+ <Animated.View
3425
+ style={[
3426
+ StyleSheet.absoluteFill,
3427
+ {
3428
+ backgroundColor: AppColors.grayBackground,
3429
+ opacity: subPageAnim,
3430
+ transform: [
3431
+ {
3432
+ translateX: subPageAnim.interpolate({
3433
+ inputRange: [0, 1],
3434
+ outputRange: [30, 0],
3435
+ }),
3436
+ },
3437
+ ],
3438
+ },
3439
+ ]}>
3440
+ {content}
3441
+ </Animated.View>
3442
+ )}
3443
+ </View>
3444
+ );
3445
+ };
3446
+
3447
+ export default SettingsPanel;