react-native-inapp-inspector 2.2.1 → 2.2.3

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