react-native-inapp-inspector 2.2.2 → 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 (97) hide show
  1. package/dist/commonjs/constants/version.d.ts +1 -1
  2. package/dist/commonjs/constants/version.js +1 -1
  3. package/dist/esm/constants/version.d.ts +1 -1
  4. package/dist/esm/constants/version.js +1 -1
  5. package/package.json +2 -1
  6. package/src/analytics.ts +35 -0
  7. package/src/bundle.ts +25 -0
  8. package/src/components/AnalyticsDetail.tsx +865 -0
  9. package/src/components/AnalyticsEventCard.tsx +441 -0
  10. package/src/components/AnalyticsGraph.tsx +583 -0
  11. package/src/components/AnimatedEntrance.tsx +64 -0
  12. package/src/components/AppHeaderLogo.tsx +109 -0
  13. package/src/components/BrandCircleIcon.tsx +144 -0
  14. package/src/components/BrandSquareIcon.tsx +144 -0
  15. package/src/components/CodeSnippet.tsx +725 -0
  16. package/src/components/ConsoleLogCard.tsx +628 -0
  17. package/src/components/CopyButton.tsx +87 -0
  18. package/src/components/DiffViewer.tsx +82 -0
  19. package/src/components/DomainHeader.tsx +237 -0
  20. package/src/components/EmptyState.tsx +73 -0
  21. package/src/components/EndOfListFooter.tsx +100 -0
  22. package/src/components/ErrorBoundary.tsx +688 -0
  23. package/src/components/HeadersSection.tsx +192 -0
  24. package/src/components/HighlightText.tsx +100 -0
  25. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  26. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  27. package/src/components/Inspector/BundleTab.tsx +4731 -0
  28. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  29. package/src/components/Inspector/CrashDetail.tsx +941 -0
  30. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  31. package/src/components/Inspector/CrashTab.tsx +871 -0
  32. package/src/components/Inspector/FabLauncher.tsx +80 -0
  33. package/src/components/Inspector/InspectorContext.tsx +28 -0
  34. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  35. package/src/components/Inspector/LogDetail.tsx +1427 -0
  36. package/src/components/Inspector/MainScreen.tsx +258 -0
  37. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  38. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  39. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  40. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  41. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  42. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  43. package/src/components/Inspector/ReduxTab.tsx +954 -0
  44. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  45. package/src/components/Inspector/TabBar.tsx +187 -0
  46. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  47. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  48. package/src/components/JsonViewer.tsx +486 -0
  49. package/src/components/LogCard.tsx +491 -0
  50. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  51. package/src/components/MetaAccordion.tsx +340 -0
  52. package/src/components/MiniBarChart.tsx +42 -0
  53. package/src/components/MiniLineChart.tsx +33 -0
  54. package/src/components/NetworkIcons.tsx +2118 -0
  55. package/src/components/SectionHeader.tsx +113 -0
  56. package/src/components/SegmentedTabs.tsx +83 -0
  57. package/src/components/Slider.tsx +300 -0
  58. package/src/components/SourcePageCard.tsx +148 -0
  59. package/src/components/Toast.tsx +131 -0
  60. package/src/components/TouchableScale.tsx +91 -0
  61. package/src/components/TreeNode.tsx +186 -0
  62. package/src/console.ts +24 -0
  63. package/src/constants/index.ts +38 -0
  64. package/src/constants/version.ts +3 -0
  65. package/src/crash.ts +32 -0
  66. package/src/customHooks/analyticsLogger.ts +336 -0
  67. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  68. package/src/customHooks/consoleLogger.ts +497 -0
  69. package/src/customHooks/crashHandler.ts +944 -0
  70. package/src/customHooks/logFilters.ts +32 -0
  71. package/src/customHooks/networkLogger.ts +419 -0
  72. package/src/customHooks/performanceTracker.ts +1014 -0
  73. package/src/customHooks/reduxLogger.ts +406 -0
  74. package/src/customHooks/useAccordion.tsx +60 -0
  75. package/src/decorators/index.ts +184 -0
  76. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  77. package/src/helpers/index.ts +860 -0
  78. package/src/helpers/memoryManager.ts +138 -0
  79. package/src/helpers/searchQueryParser.ts +283 -0
  80. package/src/helpers/settingsStore.ts +171 -0
  81. package/src/helpers/telemetry.ts +505 -0
  82. package/src/helpers/toast.ts +17 -0
  83. package/src/i18n/index.ts +69 -0
  84. package/src/i18n/locales/en.json +1052 -0
  85. package/src/index.tsx +2336 -0
  86. package/src/native/NativeInspector.ts +397 -0
  87. package/src/native/NativeNetworkInspector.ts +24 -0
  88. package/src/network.ts +22 -0
  89. package/src/performance.ts +38 -0
  90. package/src/redux.ts +23 -0
  91. package/src/styles/AppColors.ts +408 -0
  92. package/src/styles/AppFonts.ts +8 -0
  93. package/src/styles/common.ts +209 -0
  94. package/src/styles/index.ts +1570 -0
  95. package/src/types/enums.ts +194 -0
  96. package/src/types/index.ts +34 -0
  97. 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;