react-native-inapp-inspector 1.1.35 → 2.0.1

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 (104) hide show
  1. package/README.md +123 -101
  2. package/android/build.gradle +13 -1
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +890 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
  5. package/dist/commonjs/components/ConsoleLogCard.js +5 -5
  6. package/dist/commonjs/components/CopyButton.js +3 -0
  7. package/dist/commonjs/components/EndOfListFooter.js +1 -1
  8. package/dist/commonjs/components/ErrorBoundary.js +3 -0
  9. package/dist/commonjs/components/Inspector/AnalyticsTab.js +25 -73
  10. package/dist/commonjs/components/Inspector/BundleTab.js +671 -189
  11. package/dist/commonjs/components/Inspector/ConsoleTab.js +110 -99
  12. package/dist/commonjs/components/Inspector/CrashTab.js +99 -91
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +107 -20
  14. package/dist/commonjs/components/Inspector/MainScreen.js +111 -68
  15. package/dist/commonjs/components/Inspector/NetworkTab.js +151 -30
  16. package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
  17. package/dist/commonjs/components/Inspector/ReduxTab.js +18 -10
  18. package/dist/commonjs/components/Inspector/SettingsPanel.js +1218 -1238
  19. package/dist/commonjs/components/Inspector/TabBar.js +4 -2
  20. package/dist/commonjs/components/JsonViewer.js +9 -4
  21. package/dist/commonjs/components/LogCard.js +31 -1
  22. package/dist/commonjs/components/LogSyntaxHighlighter.d.ts +16 -0
  23. package/dist/commonjs/components/LogSyntaxHighlighter.js +130 -0
  24. package/dist/commonjs/components/NetworkIcons.d.ts +3 -0
  25. package/dist/commonjs/components/NetworkIcons.js +15 -1
  26. package/dist/commonjs/components/Slider.d.ts +13 -0
  27. package/dist/commonjs/components/Slider.js +261 -0
  28. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  29. package/dist/commonjs/components/TouchableScale.js +30 -32
  30. package/dist/commonjs/constants/index.d.ts +1 -2
  31. package/dist/commonjs/constants/index.js +4 -0
  32. package/dist/commonjs/constants/version.d.ts +1 -1
  33. package/dist/commonjs/constants/version.js +1 -1
  34. package/dist/commonjs/customHooks/analyticsLogger.d.ts +4 -0
  35. package/dist/commonjs/customHooks/analyticsLogger.js +8 -6
  36. package/dist/commonjs/customHooks/crashHandler.js +8 -1
  37. package/dist/commonjs/helpers/index.d.ts +1 -0
  38. package/dist/commonjs/helpers/index.js +25 -1
  39. package/dist/commonjs/helpers/searchQueryParser.d.ts +6 -0
  40. package/dist/commonjs/helpers/searchQueryParser.js +236 -0
  41. package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
  42. package/dist/commonjs/helpers/settingsStore.js +59 -3
  43. package/dist/commonjs/index.d.ts +1 -0
  44. package/dist/commonjs/index.js +220 -43
  45. package/dist/commonjs/native/NativeInspector.d.ts +125 -0
  46. package/dist/commonjs/native/NativeInspector.js +313 -0
  47. package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
  48. package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
  49. package/dist/commonjs/styles/index.js +41 -41
  50. package/dist/commonjs/types/enums.d.ts +1 -1
  51. package/dist/commonjs/types/interfaces.d.ts +9 -0
  52. package/dist/esm/components/ConsoleLogCard.js +5 -5
  53. package/dist/esm/components/CopyButton.js +3 -0
  54. package/dist/esm/components/EndOfListFooter.js +2 -2
  55. package/dist/esm/components/ErrorBoundary.js +3 -0
  56. package/dist/esm/components/Inspector/AnalyticsTab.js +27 -75
  57. package/dist/esm/components/Inspector/BundleTab.js +671 -189
  58. package/dist/esm/components/Inspector/ConsoleTab.js +111 -100
  59. package/dist/esm/components/Inspector/CrashTab.js +99 -91
  60. package/dist/esm/components/Inspector/InspectorHeader.js +108 -21
  61. package/dist/esm/components/Inspector/MainScreen.js +113 -70
  62. package/dist/esm/components/Inspector/NetworkTab.js +152 -31
  63. package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
  64. package/dist/esm/components/Inspector/ReduxTab.js +18 -10
  65. package/dist/esm/components/Inspector/SettingsPanel.js +1222 -1242
  66. package/dist/esm/components/Inspector/TabBar.js +4 -2
  67. package/dist/esm/components/JsonViewer.js +9 -4
  68. package/dist/esm/components/LogCard.js +32 -2
  69. package/dist/esm/components/LogSyntaxHighlighter.d.ts +16 -0
  70. package/dist/esm/components/LogSyntaxHighlighter.js +123 -0
  71. package/dist/esm/components/NetworkIcons.d.ts +3 -0
  72. package/dist/esm/components/NetworkIcons.js +11 -0
  73. package/dist/esm/components/Slider.d.ts +13 -0
  74. package/dist/esm/components/Slider.js +222 -0
  75. package/dist/esm/components/TouchableScale.d.ts +1 -0
  76. package/dist/esm/components/TouchableScale.js +31 -33
  77. package/dist/esm/constants/index.d.ts +1 -2
  78. package/dist/esm/constants/index.js +4 -0
  79. package/dist/esm/constants/version.d.ts +1 -1
  80. package/dist/esm/constants/version.js +1 -1
  81. package/dist/esm/customHooks/analyticsLogger.d.ts +4 -0
  82. package/dist/esm/customHooks/analyticsLogger.js +6 -5
  83. package/dist/esm/customHooks/crashHandler.js +8 -1
  84. package/dist/esm/helpers/index.d.ts +1 -0
  85. package/dist/esm/helpers/index.js +11 -1
  86. package/dist/esm/helpers/searchQueryParser.d.ts +6 -0
  87. package/dist/esm/helpers/searchQueryParser.js +233 -0
  88. package/dist/esm/helpers/settingsStore.d.ts +9 -0
  89. package/dist/esm/helpers/settingsStore.js +58 -3
  90. package/dist/esm/index.d.ts +1 -0
  91. package/dist/esm/index.js +207 -44
  92. package/dist/esm/native/NativeInspector.d.ts +125 -0
  93. package/dist/esm/native/NativeInspector.js +289 -0
  94. package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
  95. package/dist/esm/native/NativeNetworkInspector.js +2 -0
  96. package/dist/esm/styles/index.js +41 -41
  97. package/dist/esm/types/enums.d.ts +1 -1
  98. package/dist/esm/types/interfaces.d.ts +9 -0
  99. package/ios/NetworkInspectorModule.h +6 -0
  100. package/ios/NetworkInspectorModule.m +760 -5
  101. package/package.json +13 -2
  102. package/react-native-inapp-inspector.podspec +7 -2
  103. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
  104. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
@@ -1,14 +1,13 @@
1
- import React, { useMemo, useState, useEffect } from 'react';
2
- import { Alert, Platform, ScrollView, StatusBar, Text, View, } from 'react-native';
1
+ import React, { useMemo, useState, useEffect, useRef } from 'react';
2
+ import { Alert, Animated, Platform, ScrollView, StatusBar, StyleSheet, Text, TextInput, View, } from 'react-native';
3
3
  import Svg, { Path } from 'react-native-svg';
4
- import LinearGradient from 'react-native-linear-gradient';
5
4
  import { animateNextLayout, useInspector } from './InspectorContext';
6
5
  import TouchableScale from '../TouchableScale';
7
- import styles, { toggleGlobalTheme } from '../../styles';
6
+ import Slider from '../Slider';
7
+ import { toggleGlobalTheme } from '../../styles';
8
8
  import { AppColors } from '../../styles/AppColors';
9
9
  import { AppFonts } from '../../styles/AppFonts';
10
- import { LIB_VERSION } from '../../constants';
11
- import { isPersistentStorageAvailable } from '../../helpers/settingsStore';
10
+ import { isPersistentStorageAvailable, calculateRamBasedLimits, } from '../../helpers/settingsStore';
12
11
  import { clearNetworkLogs } from '../../customHooks/networkLogger';
13
12
  import { clearConsoleLogs } from '../../customHooks/consoleLogger';
14
13
  import { clearAnalyticsEvents } from '../../customHooks/analyticsLogger';
@@ -18,10 +17,15 @@ import { clearPerformanceEvents } from '../../customHooks/performanceTracker';
18
17
  import { isReduxConnected } from '../../customHooks/reduxLogger';
19
18
  import { isAnalyticsConnected } from '../../customHooks/analyticsLogger';
20
19
  import { useTranslation } from '../../i18n';
21
- import { SignalIcon, TerminalIcon, AnalyticsIcon, SettingsIcon, SunIcon, MoonIcon, ScreenIcon, MotionIcon, LayersIcon, EyeIcon, CheckIcon, TrashIcon, WhiteBackNavigation, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, ShieldAlertIcon, ForwardChevronIcon, } from '../NetworkIcons';
20
+ import { SignalIcon, TerminalIcon, AnalyticsIcon, SettingsIcon, SunIcon, MoonIcon, ScreenIcon, MotionIcon, LayersIcon, EyeIcon, CheckIcon, TrashIcon, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, ShieldAlertIcon, ForwardChevronIcon, ChevronDownIcon, } from '../NetworkIcons';
22
21
  const SettingsPanel = () => {
23
22
  const { t } = useTranslation();
24
- const { settingsPage, setSettingsPage, settingsActiveSubTab, setSettingsActiveSubTab, tabVisibility, toggleTabVisibility, defaultTab, setDefaultTab, isDark, setIsDark, modalHeightPercent, setModalHeightPercent, modalAnimationType, setModalAnimationType, showDuplicateLogs, setShowDuplicateLogs, showConsoleLevels, setShowConsoleLevels, resetToDefaults, storage, logs, consoleLogs, analyticsEvents, reduxState, maxNetworkLogs, setMaxNetworkLogs, maxConsoleLogs, setMaxConsoleLogs, reduxAutoRefresh, setReduxAutoRefreshState, reduxExpandDepth, setReduxExpandDepth, switchActiveTab, setSelected, setSelectedEvent, setReduxState, crashRecords, maxCrashLogs, setMaxCrashLogs, } = useInspector();
23
+ const { settingsPage, setSettingsPage, settingsActiveSubTab, setSettingsActiveSubTab, tabVisibility, toggleTabVisibility, defaultTab, setDefaultTab, isDark, setIsDark, modalHeightPercent, setModalHeightPercent, modalAnimationType, setModalAnimationType, showDuplicateLogs, setShowDuplicateLogs, showConsoleLevels, setShowConsoleLevels, resetToDefaults, storage, logs, consoleLogs, analyticsEvents, reduxState, maxNetworkLogs, setMaxNetworkLogs, maxConsoleLogs, setMaxConsoleLogs, maxAnalyticsEventsLimit, setMaxAnalyticsEventsLimit, isAutoRamLimitEnabled, setIsAutoRamLimitEnabled, deviceFreeRamMb, reduxAutoRefresh, setReduxAutoRefreshState, reduxExpandDepth, setReduxExpandDepth, switchActiveTab, setSelected, setSelectedEvent, setReduxState, crashRecords, maxCrashLogs, setMaxCrashLogs, } = useInspector();
24
+ const [stagedHeight, setStagedHeight] = useState(modalHeightPercent);
25
+ useEffect(() => {
26
+ setStagedHeight(modalHeightPercent);
27
+ }, [modalHeightPercent]);
28
+ const autoRamProfile = calculateRamBasedLimits(deviceFreeRamMb);
25
29
  const isPersistent = isPersistentStorageAvailable();
26
30
  // Safe Area & Status Bar Padding for All Device Form Factors
27
31
  const statusBarHeight = Platform.OS === 'android' ? StatusBar.currentHeight || 24 : 44;
@@ -117,1387 +121,1268 @@ const SettingsPanel = () => {
117
121
  });
118
122
  Alert.alert(t('settings.settingsSaved'), t('settings.settingsSavedDesc'));
119
123
  };
120
- if (settingsPage === 'main') {
124
+ const [isDefaultTabDropdownOpen, setIsDefaultTabDropdownOpen] = useState(false);
125
+ const goBackToMain = () => {
126
+ animateNextLayout();
127
+ setSettingsPage('main');
128
+ };
129
+ // Helper: settings row with icon + label + optional description
130
+ const renderSettingRow = (opts) => {
121
131
  return (<View style={{
122
- flex: 1,
123
- backgroundColor: AppColors.grayBackground,
132
+ paddingVertical: 12,
133
+ borderBottomWidth: opts.isLast ? 0 : 1,
134
+ borderBottomColor: AppColors.dividerColor,
124
135
  }}>
125
- {/* Modern Floating Gradient Header */}
126
- <LinearGradient colors={[AppColors.purple, AppColors.brandPurple]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={styles.headerGradient}>
127
- <View style={{ paddingTop: headerTopPadding, width: '100%' }}>
128
- <View style={[
129
- styles.header,
130
- { paddingHorizontal: 16, paddingVertical: 12, gap: 12 },
131
- ]}>
132
- <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={t('settings.back')} onPress={() => {
133
- animateNextLayout();
134
- setSettingsPage(null);
135
- switchActiveTab('apis');
136
- }} hitSlop={12} style={{
137
- padding: 8,
138
- borderRadius: 10,
139
- backgroundColor: `${AppColors.white}26`,
140
- borderWidth: 1,
141
- borderColor: `${AppColors.white}18`,
142
- }}>
143
- <WhiteBackNavigation color={AppColors.white} size={16}/>
144
- </TouchableScale>
145
-
146
- <View style={{ flex: 1 }}>
147
- <View style={{
136
+ <TouchableScale disabled={!opts.onPress} onPress={opts.onPress} style={{
148
137
  flexDirection: 'row',
149
138
  alignItems: 'center',
150
- gap: 8,
151
- }}>
152
- <Text style={{
153
- fontFamily: AppFonts.interBold,
154
- fontSize: 17,
155
- color: AppColors.white,
156
- letterSpacing: -0.3,
157
- lineHeight: 22,
158
- }}>
159
- {t('settings.mainTitle')}
160
- </Text>
161
- <View style={{
162
- backgroundColor: `${AppColors.white}26`,
163
- paddingHorizontal: 7,
164
- paddingVertical: 2,
165
- borderRadius: 12,
166
- }}>
167
- <Text style={{
168
- fontFamily: AppFonts.interBold,
169
- fontSize: 10,
170
- color: AppColors.white,
171
- lineHeight: 13,
172
- }}>
173
- {stagedActiveCount}/{allModules.length} Active
174
- </Text>
175
- </View>
176
- </View>
177
- <Text style={{
178
- fontFamily: AppFonts.interRegular,
179
- fontSize: 11,
180
- color: `${AppColors.white}CC`,
181
- marginTop: 2,
182
- lineHeight: 15,
139
+ gap: 12,
183
140
  }}>
184
- {t('settings.mainSubtitle')}
185
- </Text>
186
- </View>
187
-
188
- <View style={{
189
- backgroundColor: `${AppColors.white}22`,
190
- paddingHorizontal: 8,
191
- paddingVertical: 4,
192
- borderRadius: 8,
141
+ <View style={{
142
+ width: 36,
143
+ height: 36,
144
+ borderRadius: 10,
145
+ backgroundColor: AppColors.purpleShade50,
193
146
  borderWidth: 1,
194
- borderColor: `${AppColors.white}1A`,
147
+ borderColor: `${AppColors.purple}2E`,
148
+ alignItems: 'center',
149
+ justifyContent: 'center',
195
150
  }}>
196
- <Text style={{
151
+ {opts.icon}
152
+ </View>
153
+ <View style={{ flex: 1 }}>
154
+ <Text style={{
197
155
  fontFamily: AppFonts.interBold,
198
- fontSize: 10.5,
199
- color: AppColors.white,
200
- lineHeight: 14,
156
+ fontSize: 14,
157
+ lineHeight: 18,
158
+ color: AppColors.primaryBlack,
201
159
  }}>
202
- v{LIB_VERSION}
203
- </Text>
204
- </View>
205
- </View>
160
+ {opts.label}
161
+ </Text>
162
+ {opts.description ? (<Text style={{
163
+ fontFamily: AppFonts.interRegular,
164
+ fontSize: 11,
165
+ lineHeight: 15,
166
+ color: AppColors.grayText,
167
+ marginTop: 1,
168
+ }}>
169
+ {opts.description}
170
+ </Text>) : null}
206
171
  </View>
207
- </LinearGradient>
208
-
209
- <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 14 }} showsVerticalScrollIndicator={false}>
172
+ {opts.right || null}
173
+ </TouchableScale>
174
+ {opts.numericInput && (<View style={{ marginTop: 10, gap: 6 }}>
175
+ <View style={{
176
+ flexDirection: 'row',
177
+ alignItems: 'center',
178
+ backgroundColor: AppColors.grayBackground,
179
+ borderRadius: 8,
180
+ borderWidth: 1,
181
+ borderColor: AppColors.dividerColor,
182
+ paddingHorizontal: 12,
183
+ paddingVertical: 4,
184
+ }}>
185
+ <TextInput style={{
186
+ flex: 1,
187
+ fontFamily: AppFonts.interMedium,
188
+ fontSize: 14,
189
+ lineHeight: 18,
190
+ color: AppColors.primaryBlack,
191
+ paddingVertical: 6,
192
+ }} value={String(opts.numericInput.value || '')} onChangeText={text => {
193
+ const num = parseInt(text.replace(/[^0-9]/g, ''), 10);
194
+ if (!isNaN(num)) {
195
+ const clamped = Math.max(opts.numericInput?.min ?? 1, Math.min(opts.numericInput?.max ?? 10000, num));
196
+ opts.numericInput?.onChange(clamped);
197
+ }
198
+ }} keyboardType={opts.numericInput.keyboardType ?? 'number-pad'} placeholder={opts.numericInput.placeholder} placeholderTextColor={AppColors.grayTextWeak} maxLength={6} selectTextOnFocus/>
199
+ <Text style={{
200
+ fontFamily: AppFonts.interBold,
201
+ fontSize: 11,
202
+ color: AppColors.purple,
203
+ marginLeft: 6,
204
+ }}>
205
+ MAX
206
+ </Text>
207
+ </View>
208
+ {opts.numericInput.min !== undefined || opts.numericInput.max !== undefined ? (<Text style={{
209
+ fontFamily: AppFonts.interRegular,
210
+ fontSize: 10,
211
+ lineHeight: 13,
212
+ color: AppColors.grayTextWeak,
213
+ }}>
214
+ Range: {opts.numericInput.min ?? 1} - {opts.numericInput.max ?? '∞'}
215
+ </Text>) : null}
216
+ </View>)}
217
+ {opts.picker && (<View style={{
218
+ flexDirection: 'row',
219
+ backgroundColor: AppColors.grayBackground,
220
+ borderRadius: 8,
221
+ padding: 2.5,
222
+ marginTop: 10,
223
+ borderWidth: 1,
224
+ borderColor: AppColors.dividerColor,
225
+ }}>
226
+ {opts.picker.options.map(opt => {
227
+ const isActive = opts.picker.selectedValue === opt;
228
+ return (<TouchableScale key={String(opt)} onPress={() => opts.picker.onSelect(opt)} style={{
229
+ flex: 1,
230
+ paddingVertical: 6,
231
+ alignItems: 'center',
232
+ borderRadius: 6,
233
+ backgroundColor: isActive
234
+ ? AppColors.purple
235
+ : 'transparent',
236
+ }}>
237
+ <Text style={{
238
+ fontFamily: AppFonts.interBold,
239
+ fontSize: 11,
240
+ lineHeight: 14,
241
+ color: isActive ? AppColors.white : AppColors.grayText,
242
+ }}>
243
+ {opts.picker.formatLabel
244
+ ? opts.picker.formatLabel(opt)
245
+ : opt}
246
+ </Text>
247
+ </TouchableScale>);
248
+ })}
249
+ </View>)}
250
+ </View>);
251
+ };
252
+ const renderMainSettingsContent = () => (<View style={{
253
+ flex: 1,
254
+ backgroundColor: AppColors.grayBackground,
255
+ }}>
256
+ <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 14 }} showsVerticalScrollIndicator={false}>
210
257
  {/* Segmented Top Navigation Sub-Tabs */}
211
258
  <View style={{
212
- flexDirection: 'row',
213
- backgroundColor: AppColors.primaryLight,
214
- borderRadius: 12,
215
- padding: 4,
216
- borderWidth: 1,
217
- borderColor: AppColors.grayBorderSecondary,
218
- shadowColor: AppColors.black,
219
- shadowOpacity: 0.04,
220
- shadowRadius: 4,
221
- shadowOffset: { width: 0, height: 2 },
222
- }}>
259
+ flexDirection: 'row',
260
+ backgroundColor: AppColors.primaryLight,
261
+ borderRadius: 12,
262
+ padding: 4,
263
+ borderWidth: 1,
264
+ borderColor: AppColors.grayBorderSecondary,
265
+ shadowColor: AppColors.black,
266
+ shadowOpacity: 0.04,
267
+ shadowRadius: 4,
268
+ shadowOffset: { width: 0, height: 2 },
269
+ }}>
223
270
  <TouchableScale accessible={true} accessibilityRole="tab" accessibilityLabel={t('settings.modulesAndTools')} accessibilityState={{ selected: settingsActiveSubTab === 'module' }} onPress={() => {
224
- animateNextLayout();
225
- setSettingsActiveSubTab('module');
226
- }} style={{
227
- flex: 1,
228
- paddingVertical: 9,
229
- flexDirection: 'row',
230
- alignItems: 'center',
231
- justifyContent: 'center',
232
- gap: 6,
233
- borderRadius: 9,
234
- backgroundColor: settingsActiveSubTab === 'module'
235
- ? AppColors.purple
236
- : 'transparent',
237
- }}>
271
+ animateNextLayout();
272
+ setSettingsActiveSubTab('module');
273
+ }} style={{
274
+ flex: 1,
275
+ paddingVertical: 9,
276
+ flexDirection: 'row',
277
+ alignItems: 'center',
278
+ justifyContent: 'center',
279
+ gap: 6,
280
+ borderRadius: 9,
281
+ backgroundColor: settingsActiveSubTab === 'module'
282
+ ? AppColors.purple
283
+ : 'transparent',
284
+ }}>
238
285
  <LayersIcon color={settingsActiveSubTab === 'module'
239
- ? AppColors.white
240
- : AppColors.grayText} size={13}/>
286
+ ? AppColors.white
287
+ : AppColors.grayText} size={13}/>
241
288
  <Text style={{
242
- fontFamily: AppFonts.interBold,
243
- fontSize: 12.5,
244
- lineHeight: 16,
245
- color: settingsActiveSubTab === 'module'
246
- ? AppColors.white
247
- : AppColors.grayText,
248
- }}>
289
+ fontFamily: AppFonts.interBold,
290
+ fontSize: 12.5,
291
+ lineHeight: 16,
292
+ color: settingsActiveSubTab === 'module'
293
+ ? AppColors.white
294
+ : AppColors.grayText,
295
+ }}>
249
296
  {t('settings.modulesAndTools')}
250
297
  </Text>
251
298
  </TouchableScale>
252
299
 
253
300
  <TouchableScale accessible={true} accessibilityRole="tab" accessibilityLabel={t('settings.uiPreferences')} accessibilityState={{ selected: settingsActiveSubTab === 'ui' }} onPress={() => {
254
- animateNextLayout();
255
- setSettingsActiveSubTab('ui');
256
- }} style={{
257
- flex: 1,
258
- paddingVertical: 9,
259
- flexDirection: 'row',
260
- alignItems: 'center',
261
- justifyContent: 'center',
262
- gap: 6,
263
- borderRadius: 9,
264
- backgroundColor: settingsActiveSubTab === 'ui'
265
- ? AppColors.purple
266
- : 'transparent',
267
- }}>
301
+ animateNextLayout();
302
+ setSettingsActiveSubTab('ui');
303
+ }} style={{
304
+ flex: 1,
305
+ paddingVertical: 9,
306
+ flexDirection: 'row',
307
+ alignItems: 'center',
308
+ justifyContent: 'center',
309
+ gap: 6,
310
+ borderRadius: 9,
311
+ backgroundColor: settingsActiveSubTab === 'ui'
312
+ ? AppColors.purple
313
+ : 'transparent',
314
+ }}>
268
315
  <ScreenIcon color={settingsActiveSubTab === 'ui'
269
- ? AppColors.white
270
- : AppColors.grayText} size={13}/>
316
+ ? AppColors.white
317
+ : AppColors.grayText} size={13}/>
271
318
  <Text style={{
272
- fontFamily: AppFonts.interBold,
273
- fontSize: 12.5,
274
- lineHeight: 16,
275
- color: settingsActiveSubTab === 'ui'
276
- ? AppColors.white
277
- : AppColors.grayText,
278
- }}>
319
+ fontFamily: AppFonts.interBold,
320
+ fontSize: 12.5,
321
+ lineHeight: 16,
322
+ color: settingsActiveSubTab === 'ui'
323
+ ? AppColors.white
324
+ : AppColors.grayText,
325
+ }}>
279
326
  {t('settings.uiPreferences')}
280
327
  </Text>
281
328
  </TouchableScale>
282
329
  </View>
283
330
 
284
331
  {settingsActiveSubTab === 'module' ? (<View style={{ gap: 12 }}>
285
- {/* Performance Advisory Banner */}
286
- <View style={{
287
- backgroundColor: `${AppColors.purple}0A`,
288
- borderRadius: 12,
289
- padding: 12,
290
- borderWidth: 1,
291
- borderColor: `${AppColors.purple}20`,
292
- flexDirection: 'row',
293
- alignItems: 'center',
294
- gap: 10,
295
- }}>
296
- <View style={{
297
- width: 32,
298
- height: 32,
299
- borderRadius: 8,
300
- backgroundColor: `${AppColors.purple}14`,
301
- alignItems: 'center',
302
- justifyContent: 'center',
303
- }}>
304
- <SignalIcon color={AppColors.purple} size={15}/>
305
- </View>
306
- <View style={{ flex: 1 }}>
307
- <Text style={{
308
- fontFamily: AppFonts.interBold,
309
- fontSize: 12,
310
- color: AppColors.primaryBlack,
311
- lineHeight: 16,
312
- }}>
313
- {t('settings.zeroOverheadTitle')}
314
- </Text>
315
- <Text style={{
316
- fontFamily: AppFonts.interRegular,
317
- fontSize: 10.5,
318
- color: AppColors.grayText,
319
- marginTop: 2,
320
- lineHeight: 14,
321
- }}>
322
- {t('settings.zeroOverheadDesc')}
323
- </Text>
324
- </View>
325
- </View>
326
-
327
- {/* Enhanced Individual Module Cards with Decorative Checkboxes */}
332
+ {/* Individual Module Cards with Left Checkboxes */}
328
333
  <View style={{ gap: 10 }}>
329
334
  {allModules.map(moduleItem => {
330
- const isReduxAvail = isReduxConnected();
331
- const isAnalyticsAvail = isAnalyticsConnected();
332
- const isUnavailable = (moduleItem.key === 'redux' && !isReduxAvail) ||
333
- (moduleItem.key === 'analytics' && !isAnalyticsAvail);
334
- const isLocked = moduleItem.key === 'apis' || isUnavailable;
335
- const isChecked = moduleItem.key === 'apis' ||
336
- (Boolean(stagedTabVisibility?.[moduleItem.key]) &&
337
- !isUnavailable);
338
- const liveStats = moduleItem.key === 'apis'
339
- ? `${logs.length} requests • Limit: ${maxNetworkLogs}`
340
- : moduleItem.key === 'logs'
341
- ? `${consoleLogs.length} logs • Limit: ${maxConsoleLogs}`
342
- : moduleItem.key === 'performance'
343
- ? '60 FPS Target • Memory Telemetry'
344
- : moduleItem.key === 'bundle'
345
- ? 'Metro Packager • Dependency Map'
346
- : moduleItem.key === 'crash'
347
- ? `${crashRecords?.length || 0} crashes recorded • Crash Guard`
348
- : moduleItem.key === 'analytics'
349
- ? `${analyticsEvents.length} events logged`
350
- : moduleItem.key === 'redux'
351
- ? `${Object.keys(reduxState || {}).length} slices • Depth: ${reduxExpandDepth}`
352
- : '';
353
- return (<View key={moduleItem.key} style={{
354
- backgroundColor: AppColors.primaryLight,
355
- borderRadius: 14,
356
- borderWidth: 1.5,
357
- borderColor: isChecked
358
- ? `${AppColors.purple}38`
359
- : AppColors.grayBorderSecondary,
360
- padding: 14,
361
- gap: 10,
362
- shadowColor: AppColors.black,
363
- shadowOpacity: isChecked ? 0.04 : 0.02,
364
- shadowRadius: 5,
365
- shadowOffset: { width: 0, height: 2 },
366
- }}>
367
- {/* Top Row: Icon + Title & Badges + Checkbox */}
335
+ const isReduxAvail = isReduxConnected();
336
+ const isAnalyticsAvail = isAnalyticsConnected();
337
+ const isUnavailable = (moduleItem.key === 'redux' && !isReduxAvail) ||
338
+ (moduleItem.key === 'analytics' && !isAnalyticsAvail);
339
+ const isLocked = moduleItem.key === 'apis' || isUnavailable;
340
+ const isChecked = moduleItem.key === 'apis' ||
341
+ (Boolean(stagedTabVisibility?.[moduleItem.key]) &&
342
+ !isUnavailable);
343
+ const liveStats = moduleItem.key === 'apis'
344
+ ? `${logs.length} requests • Limit: ${maxNetworkLogs}`
345
+ : moduleItem.key === 'logs'
346
+ ? `${consoleLogs.length} logs • Limit: ${maxConsoleLogs}`
347
+ : moduleItem.key === 'performance'
348
+ ? '60 FPS Target • Memory Telemetry'
349
+ : moduleItem.key === 'bundle'
350
+ ? 'Metro Packager • Dependency Map'
351
+ : moduleItem.key === 'crash'
352
+ ? `${crashRecords?.length || 0} crashes recorded • Crash Guard`
353
+ : moduleItem.key === 'analytics'
354
+ ? `${analyticsEvents.length} events logged`
355
+ : moduleItem.key === 'redux'
356
+ ? `${Object.keys(reduxState || {}).length} slices • Depth: ${reduxExpandDepth}`
357
+ : '';
358
+ return (<View key={moduleItem.key} style={{
359
+ backgroundColor: AppColors.primaryLight,
360
+ borderRadius: 14,
361
+ borderWidth: 1.5,
362
+ borderColor: isChecked
363
+ ? `${AppColors.purple}38`
364
+ : AppColors.grayBorderSecondary,
365
+ padding: 14,
366
+ gap: 10,
367
+ shadowColor: AppColors.black,
368
+ shadowOpacity: isChecked ? 0.04 : 0.02,
369
+ shadowRadius: 5,
370
+ shadowOffset: { width: 0, height: 2 },
371
+ }}>
372
+ {/* Top Row: [Checkbox] -> [Icon Tile] -> [Title & Subtitle] */}
368
373
  <View style={{
369
- flexDirection: 'row',
370
- alignItems: 'flex-start',
371
- justifyContent: 'space-between',
372
- gap: 10,
373
- }}>
374
- {/* Tap Zone for Details/Settings */}
374
+ flexDirection: 'row',
375
+ alignItems: 'flex-start',
376
+ gap: 10,
377
+ }}>
378
+ {/* 1. Checkbox on the Left */}
379
+ <TouchableScale accessible={true} accessibilityRole="checkbox" accessibilityLabel={`Select ${moduleItem.label}`} accessibilityState={{
380
+ checked: isChecked,
381
+ disabled: isLocked,
382
+ }} disabled={isLocked} onPress={() => {
383
+ if (isLocked)
384
+ return;
385
+ setStagedTabVisibility(prev => ({
386
+ ...prev,
387
+ [moduleItem.key]: !prev[moduleItem.key],
388
+ }));
389
+ }} hitSlop={8} style={{
390
+ width: 22,
391
+ height: 22,
392
+ borderRadius: 6,
393
+ borderWidth: isChecked ? 0 : 1.8,
394
+ borderColor: isLocked
395
+ ? AppColors.dividerColor
396
+ : AppColors.grayBorderSecondary,
397
+ backgroundColor: moduleItem.key === 'apis'
398
+ ? `${AppColors.blue500}22`
399
+ : isChecked
400
+ ? AppColors.purple
401
+ : AppColors.grayBackground,
402
+ alignItems: 'center',
403
+ justifyContent: 'center',
404
+ marginTop: 7,
405
+ shadowColor: isChecked
406
+ ? AppColors.purple
407
+ : 'transparent',
408
+ shadowOpacity: isChecked ? 0.25 : 0,
409
+ shadowRadius: 2,
410
+ elevation: isChecked ? 2 : 0,
411
+ }}>
412
+ {moduleItem.key === 'apis' ? (<CheckIcon size={12} color={AppColors.blue500}/>) : isChecked ? (<CheckIcon size={12} color={AppColors.white}/>) : isLocked ? (<Svg width={10} height={10} viewBox="0 0 24 24" fill="none">
413
+ <Path d="M7 10V7a5 5 0 0 1 10 0v3" stroke={AppColors.grayText} strokeWidth="2.2" strokeLinecap="round"/>
414
+ <Path d="M5 10h14v9a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z" fill={AppColors.grayText}/>
415
+ </Svg>) : null}
416
+ </TouchableScale>
417
+
418
+ {/* 2. Middle Tap Zone: Icon + Title & Desc */}
375
419
  <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={`${moduleItem.label} settings`} disabled={isUnavailable} onPress={() => {
376
- animateNextLayout();
377
- setSettingsPage(moduleItem.key);
378
- }} style={{
379
- flex: 1,
380
- flexDirection: 'row',
381
- alignItems: 'flex-start',
382
- gap: 10,
383
- }}>
420
+ animateNextLayout();
421
+ setSettingsPage(moduleItem.key);
422
+ }} style={{
423
+ flex: 1,
424
+ flexDirection: 'row',
425
+ alignItems: 'flex-start',
426
+ gap: 10,
427
+ }}>
384
428
  {/* Icon Tile */}
385
429
  <View style={{
386
- width: 36,
387
- height: 36,
388
- borderRadius: 10,
389
- backgroundColor: isLocked
390
- ? `${AppColors.grayBorderSecondary}4D`
391
- : isChecked
392
- ? `${AppColors.purple}14`
393
- : AppColors.grayBackground,
394
- borderWidth: 1,
395
- borderColor: isLocked
396
- ? AppColors.dividerColor
397
- : isChecked
398
- ? `${AppColors.purple}33`
399
- : AppColors.grayBorderSecondary,
400
- alignItems: 'center',
401
- justifyContent: 'center',
402
- marginTop: 1,
403
- }}>
430
+ width: 36,
431
+ height: 36,
432
+ borderRadius: 10,
433
+ backgroundColor: isLocked
434
+ ? `${AppColors.grayBorderSecondary}4D`
435
+ : isChecked
436
+ ? `${AppColors.purple}14`
437
+ : AppColors.grayBackground,
438
+ borderWidth: 1,
439
+ borderColor: isLocked
440
+ ? AppColors.dividerColor
441
+ : isChecked
442
+ ? `${AppColors.purple}33`
443
+ : AppColors.grayBorderSecondary,
444
+ alignItems: 'center',
445
+ justifyContent: 'center',
446
+ marginTop: 1,
447
+ }}>
404
448
  {moduleItem.icon === 'apis' && (<SignalIcon color={isChecked
405
- ? AppColors.purple
406
- : AppColors.grayTextWeak} size={16}/>)}
449
+ ? AppColors.purple
450
+ : AppColors.grayTextWeak} size={16}/>)}
407
451
  {moduleItem.icon === 'logs' && (<TerminalIcon color={isChecked
408
- ? AppColors.purple
409
- : AppColors.grayTextWeak} size={16}/>)}
452
+ ? AppColors.purple
453
+ : AppColors.grayTextWeak} size={16}/>)}
410
454
  {moduleItem.icon === 'performance' && (<PerformanceIcon color={isChecked
411
- ? AppColors.purple
412
- : AppColors.grayTextWeak} size={16}/>)}
455
+ ? AppColors.purple
456
+ : AppColors.grayTextWeak} size={16}/>)}
413
457
  {moduleItem.icon === 'bundle' && (<PackageIcon color={isChecked
414
- ? AppColors.purple
415
- : AppColors.grayTextWeak} size={16}/>)}
458
+ ? AppColors.purple
459
+ : AppColors.grayTextWeak} size={16}/>)}
416
460
  {moduleItem.icon === 'crash' && (<CrashIcon color={isChecked
417
- ? AppColors.purple
418
- : AppColors.grayTextWeak} size={16}/>)}
461
+ ? AppColors.purple
462
+ : AppColors.grayTextWeak} size={16}/>)}
419
463
  {moduleItem.icon === 'analytics' && (<AnalyticsIcon color={isChecked
420
- ? AppColors.purple
421
- : AppColors.grayTextWeak} size={16}/>)}
464
+ ? AppColors.purple
465
+ : AppColors.grayTextWeak} size={16}/>)}
422
466
  {moduleItem.icon === 'redux' && (<ReduxIcon color={isChecked
423
- ? AppColors.purple
424
- : AppColors.grayTextWeak} size={16}/>)}
467
+ ? AppColors.purple
468
+ : AppColors.grayTextWeak} size={16}/>)}
425
469
  </View>
426
470
 
427
471
  {/* Titles & Status Pill */}
428
472
  <View style={{ flex: 1, gap: 2 }}>
429
473
  <View style={{
430
- flexDirection: 'row',
431
- alignItems: 'center',
432
- gap: 6,
433
- flexWrap: 'wrap',
434
- }}>
474
+ flexDirection: 'row',
475
+ alignItems: 'center',
476
+ gap: 6,
477
+ flexWrap: 'wrap',
478
+ }}>
435
479
  <Text style={{
436
- fontFamily: AppFonts.interBold,
437
- fontSize: 14,
438
- lineHeight: 18,
439
- color: isLocked
440
- ? AppColors.grayText
441
- : AppColors.primaryBlack,
442
- }}>
480
+ fontFamily: AppFonts.interBold,
481
+ fontSize: 14,
482
+ lineHeight: 18,
483
+ color: isLocked
484
+ ? AppColors.grayText
485
+ : AppColors.primaryBlack,
486
+ }}>
443
487
  {moduleItem.label}
444
488
  </Text>
445
489
 
446
490
  {/* Default Startup Tab Badge */}
447
491
  {moduleItem.key === defaultTab &&
448
- !isUnavailable && (<View style={{
449
- flexDirection: 'row',
450
- alignItems: 'center',
451
- backgroundColor: `${AppColors.purple}14`,
452
- borderRadius: 12,
453
- paddingHorizontal: 5,
454
- paddingVertical: 1.5,
455
- borderWidth: 1,
456
- borderColor: `${AppColors.purple}2E`,
457
- gap: 3,
458
- }}>
492
+ !isUnavailable && (<View style={{
493
+ flexDirection: 'row',
494
+ alignItems: 'center',
495
+ backgroundColor: `${AppColors.purple}14`,
496
+ borderRadius: 12,
497
+ paddingHorizontal: 5,
498
+ paddingVertical: 1.5,
499
+ borderWidth: 1,
500
+ borderColor: `${AppColors.purple}2E`,
501
+ gap: 3,
502
+ }}>
459
503
  <View style={{
460
- width: 4,
461
- height: 4,
462
- borderRadius: 2,
463
- backgroundColor: AppColors.purple,
464
- }}/>
504
+ width: 4,
505
+ height: 4,
506
+ borderRadius: 2,
507
+ backgroundColor: AppColors.purple,
508
+ }}/>
465
509
  <Text style={{
466
- fontFamily: AppFonts.interBold,
467
- fontSize: 8,
468
- lineHeight: 11,
469
- color: AppColors.purple,
470
- letterSpacing: 0.4,
471
- }}>
472
- {t('settings.defaultBadge')}
510
+ fontFamily: AppFonts.interBold,
511
+ fontSize: 8,
512
+ lineHeight: 11,
513
+ color: AppColors.purple,
514
+ letterSpacing: 0.4,
515
+ }}>
516
+ {t('settings.defaultBadge')}
473
517
  </Text>
474
518
  </View>)}
475
519
 
476
520
  {/* Status Pill */}
477
521
  {moduleItem.key === 'apis' ? (<View style={{
478
- backgroundColor: `${AppColors.blue500}14`,
479
- borderRadius: 4,
480
- paddingHorizontal: 5,
481
- paddingVertical: 1.5,
482
- borderWidth: 1,
483
- borderColor: `${AppColors.blue500}33`,
484
- }}>
522
+ backgroundColor: `${AppColors.blue500}14`,
523
+ borderRadius: 4,
524
+ paddingHorizontal: 5,
525
+ paddingVertical: 1.5,
526
+ borderWidth: 1,
527
+ borderColor: `${AppColors.blue500}33`,
528
+ }}>
485
529
  <Text style={{
486
- fontFamily: AppFonts.interBold,
487
- fontSize: 8,
488
- lineHeight: 11,
489
- color: AppColors.blue500,
490
- letterSpacing: 0.3,
491
- }}>
530
+ fontFamily: AppFonts.interBold,
531
+ fontSize: 8,
532
+ lineHeight: 11,
533
+ color: AppColors.blue500,
534
+ letterSpacing: 0.3,
535
+ }}>
492
536
  {t('settings.coreBadge')}
493
537
  </Text>
494
538
  </View>) : isUnavailable ? (<View style={{
495
- backgroundColor: `${AppColors.amber500}18`,
496
- borderRadius: 4,
497
- paddingHorizontal: 5,
498
- paddingVertical: 1.5,
499
- borderWidth: 1,
500
- borderColor: `${AppColors.amber500}35`,
501
- }}>
539
+ backgroundColor: `${AppColors.amber500}18`,
540
+ borderRadius: 4,
541
+ paddingHorizontal: 5,
542
+ paddingVertical: 1.5,
543
+ borderWidth: 1,
544
+ borderColor: `${AppColors.amber500}35`,
545
+ }}>
502
546
  <Text style={{
503
- fontFamily: AppFonts.interBold,
504
- fontSize: 8,
505
- lineHeight: 11,
506
- color: AppColors.amber700,
507
- letterSpacing: 0.3,
508
- }}>
547
+ fontFamily: AppFonts.interBold,
548
+ fontSize: 8,
549
+ lineHeight: 11,
550
+ color: AppColors.amber700,
551
+ letterSpacing: 0.3,
552
+ }}>
509
553
  {moduleItem.key === 'redux'
510
- ? t('settings.notConnectedBadge')
511
- : t('settings.notDetectedBadge')}
554
+ ? t('settings.notConnectedBadge')
555
+ : t('settings.notDetectedBadge')}
512
556
  </Text>
513
557
  </View>) : isChecked ? (<View style={{
514
- backgroundColor: `${AppColors.liveGreen}18`,
515
- borderRadius: 4,
516
- paddingHorizontal: 5,
517
- paddingVertical: 1.5,
518
- borderWidth: 1,
519
- borderColor: `${AppColors.liveGreen}38`,
520
- }}>
558
+ backgroundColor: `${AppColors.liveGreen}18`,
559
+ borderRadius: 4,
560
+ paddingHorizontal: 5,
561
+ paddingVertical: 1.5,
562
+ borderWidth: 1,
563
+ borderColor: `${AppColors.liveGreen}38`,
564
+ }}>
521
565
  <Text style={{
522
- fontFamily: AppFonts.interBold,
523
- fontSize: 8,
524
- lineHeight: 11,
525
- color: AppColors.green700,
526
- letterSpacing: 0.3,
527
- }}>
566
+ fontFamily: AppFonts.interBold,
567
+ fontSize: 8,
568
+ lineHeight: 11,
569
+ color: AppColors.green700,
570
+ letterSpacing: 0.3,
571
+ }}>
528
572
  {t('settings.activeBadge')}
529
573
  </Text>
530
574
  </View>) : (<View style={{
531
- backgroundColor: `${AppColors.grayTextWeak}18`,
532
- borderRadius: 4,
533
- paddingHorizontal: 5,
534
- paddingVertical: 1.5,
535
- borderWidth: 1,
536
- borderColor: `${AppColors.grayTextWeak}2E`,
537
- }}>
575
+ backgroundColor: `${AppColors.grayTextWeak}18`,
576
+ borderRadius: 4,
577
+ paddingHorizontal: 5,
578
+ paddingVertical: 1.5,
579
+ borderWidth: 1,
580
+ borderColor: `${AppColors.grayTextWeak}2E`,
581
+ }}>
538
582
  <Text style={{
539
- fontFamily: AppFonts.interBold,
540
- fontSize: 8,
541
- lineHeight: 11,
542
- color: AppColors.grayTextWeak,
543
- letterSpacing: 0.3,
544
- }}>
583
+ fontFamily: AppFonts.interBold,
584
+ fontSize: 8,
585
+ lineHeight: 11,
586
+ color: AppColors.grayTextWeak,
587
+ letterSpacing: 0.3,
588
+ }}>
545
589
  {t('settings.dormantBadge')}
546
590
  </Text>
547
591
  </View>)}
548
592
  </View>
549
593
 
550
594
  <Text style={{
551
- fontFamily: AppFonts.interRegular,
552
- fontSize: 11,
553
- color: AppColors.grayText,
554
- lineHeight: 15,
555
- marginTop: 1,
556
- }}>
595
+ fontFamily: AppFonts.interRegular,
596
+ fontSize: 11,
597
+ color: AppColors.grayText,
598
+ lineHeight: 15,
599
+ marginTop: 1,
600
+ }}>
557
601
  {moduleItem.desc}
558
602
  </Text>
559
603
  </View>
560
604
  </TouchableScale>
561
-
562
- {/* Decorative Custom Checkbox */}
563
- <TouchableScale accessible={true} accessibilityRole="checkbox" accessibilityLabel={`Select ${moduleItem.label}`} accessibilityState={{
564
- checked: isChecked,
565
- disabled: isLocked,
566
- }} disabled={isLocked} onPress={() => {
567
- if (isLocked)
568
- return;
569
- setStagedTabVisibility(prev => ({
570
- ...prev,
571
- [moduleItem.key]: !prev[moduleItem.key],
572
- }));
573
- }} style={{
574
- width: 26,
575
- height: 26,
576
- borderRadius: 8,
577
- borderWidth: isChecked ? 0 : 2,
578
- borderColor: isLocked
579
- ? AppColors.dividerColor
580
- : AppColors.grayBorderSecondary,
581
- backgroundColor: moduleItem.key === 'apis'
582
- ? `${AppColors.blue500}22`
583
- : isChecked
584
- ? AppColors.purple
585
- : AppColors.grayBackground,
586
- alignItems: 'center',
587
- justifyContent: 'center',
588
- shadowColor: isChecked
589
- ? AppColors.purple
590
- : 'transparent',
591
- shadowOpacity: isChecked ? 0.3 : 0,
592
- shadowRadius: 3,
593
- shadowOffset: { width: 0, height: 1 },
594
- elevation: isChecked ? 2 : 0,
595
- }}>
596
- {moduleItem.key === 'apis' ? (<CheckIcon size={14} color={AppColors.blue500}/>) : isChecked ? (<CheckIcon size={14} color={AppColors.white}/>) : isLocked ? (<Svg width={10} height={10} viewBox="0 0 24 24" fill="none">
597
- <Path d="M7 10V7a5 5 0 0 1 10 0v3" stroke={AppColors.grayText} strokeWidth="2.2" strokeLinecap="round"/>
598
- <Path d="M5 10h14v9a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1z" fill={AppColors.grayText}/>
599
- </Svg>) : null}
600
- </TouchableScale>
601
605
  </View>
602
606
 
603
607
  {/* Card Footer: Live Stats + Configure Button */}
604
608
  {!isUnavailable && (<View style={{
605
- flexDirection: 'row',
606
- alignItems: 'center',
607
- justifyContent: 'space-between',
608
- paddingTop: 8,
609
- borderTopWidth: 1,
610
- borderTopColor: AppColors.dividerColor,
611
- }}>
609
+ flexDirection: 'row',
610
+ alignItems: 'center',
611
+ justifyContent: 'space-between',
612
+ paddingTop: 8,
613
+ borderTopWidth: 1,
614
+ borderTopColor: AppColors.dividerColor,
615
+ }}>
612
616
  {/* Live Info Pill */}
613
617
  <View style={{
614
- flexDirection: 'row',
615
- alignItems: 'center',
616
- gap: 6,
617
- flex: 1,
618
- }}>
618
+ flexDirection: 'row',
619
+ alignItems: 'center',
620
+ gap: 6,
621
+ flex: 1,
622
+ }}>
619
623
  <View style={{
620
- width: 6,
621
- height: 6,
622
- borderRadius: 3,
623
- backgroundColor: isChecked
624
- ? AppColors.liveGreen
625
- : AppColors.grayTextWeak,
626
- }}/>
624
+ width: 6,
625
+ height: 6,
626
+ borderRadius: 3,
627
+ backgroundColor: isChecked
628
+ ? AppColors.liveGreen
629
+ : AppColors.grayTextWeak,
630
+ }}/>
627
631
  <Text style={{
628
- fontFamily: AppFonts.interMedium,
629
- fontSize: 11,
630
- color: AppColors.grayText,
631
- lineHeight: 14,
632
- }}>
632
+ fontFamily: AppFonts.interMedium,
633
+ fontSize: 11,
634
+ color: AppColors.grayText,
635
+ lineHeight: 14,
636
+ }}>
633
637
  {liveStats}
634
638
  </Text>
635
639
  </View>
636
640
 
637
641
  {/* Sleek Configure Button */}
638
642
  <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={`Configure ${moduleItem.label} settings`} onPress={() => {
639
- animateNextLayout();
640
- setSettingsPage(moduleItem.key);
641
- }} style={{
642
- flexDirection: 'row',
643
- alignItems: 'center',
644
- gap: 5,
645
- backgroundColor: `${AppColors.purple}12`,
646
- paddingHorizontal: 10,
647
- paddingVertical: 5,
648
- borderRadius: 8,
649
- borderWidth: 1,
650
- borderColor: `${AppColors.purple}26`,
651
- }}>
643
+ animateNextLayout();
644
+ setSettingsPage(moduleItem.key);
645
+ }} style={{
646
+ flexDirection: 'row',
647
+ alignItems: 'center',
648
+ gap: 5,
649
+ backgroundColor: `${AppColors.purple}12`,
650
+ paddingHorizontal: 10,
651
+ paddingVertical: 5,
652
+ borderRadius: 8,
653
+ borderWidth: 1,
654
+ borderColor: `${AppColors.purple}26`,
655
+ }}>
652
656
  <SettingsIcon color={AppColors.purple} size={11}/>
653
657
  <Text style={{
654
- fontFamily: AppFonts.interBold,
655
- fontSize: 11,
656
- lineHeight: 14,
657
- color: AppColors.purple,
658
- }}>
658
+ fontFamily: AppFonts.interBold,
659
+ fontSize: 11,
660
+ lineHeight: 14,
661
+ color: AppColors.purple,
662
+ }}>
659
663
  {t('settings.configure')}
660
664
  </Text>
661
665
  <ForwardChevronIcon color={AppColors.purple} size={9}/>
662
666
  </TouchableScale>
663
667
  </View>)}
664
668
  </View>);
665
- })}
669
+ })}
666
670
  </View>
667
- </View>) : (
668
- /* UI Preferences Subtab */
669
- <View style={{ gap: 14 }}>
670
- {/* Section 1: Appearance */}
671
+
672
+ {/* Section 3.5: RAM-Based Auto Limits */}
671
673
  <View style={{
672
- backgroundColor: AppColors.primaryLight,
673
- borderRadius: 14,
674
- borderWidth: 1,
675
- borderColor: AppColors.grayBorderSecondary,
676
- overflow: 'hidden',
677
- padding: 14,
678
- gap: 12,
679
- }}>
674
+ backgroundColor: AppColors.primaryLight,
675
+ borderRadius: 14,
676
+ borderWidth: 1,
677
+ borderColor: AppColors.grayBorderSecondary,
678
+ overflow: 'hidden',
679
+ padding: 14,
680
+ gap: 12,
681
+ }}>
680
682
  <Text style={{
681
- fontFamily: AppFonts.interBold,
682
- fontSize: 11,
683
- lineHeight: 14,
684
- color: AppColors.grayTextWeak,
685
- letterSpacing: 0.8,
686
- }}>
687
- {t('settings.appearanceTheme')}
683
+ fontFamily: AppFonts.interBold,
684
+ fontSize: 11,
685
+ lineHeight: 14,
686
+ color: AppColors.grayTextWeak,
687
+ letterSpacing: 0.8,
688
+ }}>
689
+ RAM-Based Auto Limits
688
690
  </Text>
689
691
 
692
+ {/* Auto RAM Limit Toggle */}
690
693
  <View style={{
691
- flexDirection: 'row',
692
- alignItems: 'center',
693
- justifyContent: 'space-between',
694
- }}>
694
+ flexDirection: 'row',
695
+ alignItems: 'center',
696
+ justifyContent: 'space-between',
697
+ }}>
695
698
  <View style={{
696
- flexDirection: 'row',
697
- alignItems: 'center',
698
- gap: 10,
699
- flex: 1,
700
- }}>
699
+ flexDirection: 'row',
700
+ alignItems: 'center',
701
+ gap: 10,
702
+ flex: 1,
703
+ }}>
701
704
  <View style={{
702
- width: 32,
703
- height: 32,
704
- borderRadius: 8,
705
- backgroundColor: AppColors.purpleShade50,
706
- alignItems: 'center',
707
- justifyContent: 'center',
708
- }}>
709
- {isDark ? (<SunIcon color={AppColors.purple} size={15}/>) : (<MoonIcon color={AppColors.purple} size={15}/>)}
705
+ width: 32,
706
+ height: 32,
707
+ borderRadius: 8,
708
+ backgroundColor: AppColors.purpleShade50,
709
+ alignItems: 'center',
710
+ justifyContent: 'center',
711
+ }}>
712
+ <PerformanceIcon color={AppColors.purple} size={15}/>
710
713
  </View>
711
714
  <View style={{ flex: 1 }}>
712
715
  <Text style={{
713
- fontFamily: AppFonts.interBold,
714
- fontSize: 13.5,
715
- lineHeight: 18,
716
- color: AppColors.primaryBlack,
717
- }}>
718
- {t('settings.general.darkMode')}
716
+ fontFamily: AppFonts.interBold,
717
+ fontSize: 13.5,
718
+ lineHeight: 18,
719
+ color: AppColors.primaryBlack,
720
+ }}>
721
+ Auto-Calculate Limits from Device RAM
719
722
  </Text>
720
723
  <Text style={{
721
- fontFamily: AppFonts.interRegular,
722
- fontSize: 11,
723
- lineHeight: 15,
724
- color: AppColors.grayText,
725
- marginTop: 1,
726
- }}>
727
- {t('settings.general.darkModeDescription')}
724
+ fontFamily: AppFonts.interRegular,
725
+ fontSize: 11,
726
+ lineHeight: 15,
727
+ color: AppColors.grayText,
728
+ marginTop: 1,
729
+ }}>
730
+ Automatically set API, Logs, Analytics & Crash limits based on available memory
728
731
  </Text>
729
732
  </View>
730
733
  </View>
731
734
 
732
- <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle Dark Theme" accessibilityState={{ checked: isDark }} onPress={() => {
733
- const newTheme = !isDark;
734
- setIsDark(newTheme);
735
- toggleGlobalTheme(newTheme);
736
- }} style={{
737
- width: 42,
738
- height: 24,
739
- borderRadius: 12,
740
- backgroundColor: isDark
741
- ? AppColors.purple
742
- : AppColors.grayBorderSecondary,
743
- padding: 2,
744
- justifyContent: 'center',
745
- alignItems: isDark ? 'flex-end' : 'flex-start',
746
- }}>
735
+ <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle auto RAM limit calculation" accessibilityState={{ checked: isAutoRamLimitEnabled }} onPress={() => setIsAutoRamLimitEnabled(prev => !prev)} style={{
736
+ width: 42,
737
+ height: 24,
738
+ borderRadius: 12,
739
+ backgroundColor: isAutoRamLimitEnabled
740
+ ? AppColors.purple
741
+ : AppColors.grayBorderSecondary,
742
+ padding: 2,
743
+ justifyContent: 'center',
744
+ alignItems: isAutoRamLimitEnabled ? 'flex-end' : 'flex-start',
745
+ }}>
747
746
  <View style={{
748
- width: 20,
749
- height: 20,
750
- borderRadius: 10,
751
- backgroundColor: AppColors.white,
752
- shadowColor: AppColors.black,
753
- shadowOpacity: 0.18,
754
- shadowRadius: 2,
755
- shadowOffset: { width: 0, height: 1 },
756
- }}/>
747
+ width: 20,
748
+ height: 20,
749
+ borderRadius: 10,
750
+ backgroundColor: AppColors.white,
751
+ shadowColor: AppColors.black,
752
+ shadowOpacity: 0.18,
753
+ shadowRadius: 2,
754
+ shadowOffset: { width: 0, height: 1 },
755
+ }}/>
757
756
  </TouchableScale>
758
757
  </View>
759
- </View>
760
758
 
761
- {/* Section 2: Window & Layout */}
762
- <View style={{
763
- backgroundColor: AppColors.primaryLight,
764
- borderRadius: 14,
765
- borderWidth: 1,
766
- borderColor: AppColors.grayBorderSecondary,
767
- overflow: 'hidden',
768
- padding: 14,
769
- gap: 14,
770
- }}>
771
- <Text style={{
772
- fontFamily: AppFonts.interBold,
773
- fontSize: 11,
774
- lineHeight: 14,
775
- color: AppColors.grayTextWeak,
776
- letterSpacing: 0.8,
777
- }}>
778
- {t('settings.windowLayout')}
779
- </Text>
759
+ <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
780
760
 
781
- {/* Modal Height */}
782
- <View style={{ gap: 8 }}>
761
+ {/* Device Free RAM Display */}
762
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
783
763
  <View style={{
784
- flexDirection: 'row',
785
- alignItems: 'center',
786
- gap: 10,
787
- }}>
788
- <View style={{
789
- width: 32,
790
- height: 32,
791
- borderRadius: 8,
792
- backgroundColor: AppColors.purpleShade50,
793
- alignItems: 'center',
794
- justifyContent: 'center',
795
- }}>
796
- <ScreenIcon color={AppColors.purple} size={15}/>
797
- </View>
798
- <View style={{ flex: 1 }}>
799
- <Text style={{
800
- fontFamily: AppFonts.interBold,
801
- fontSize: 13.5,
802
- lineHeight: 18,
803
- color: AppColors.primaryBlack,
804
- }}>
805
- {t('settings.general.modalHeight')}
806
- </Text>
807
- <Text style={{
808
- fontFamily: AppFonts.interRegular,
809
- fontSize: 11,
810
- lineHeight: 15,
811
- color: AppColors.grayText,
812
- marginTop: 1,
813
- }}>
814
- {t('settings.general.modalHeightDescription')}
815
- </Text>
816
- </View>
764
+ width: 32,
765
+ height: 32,
766
+ borderRadius: 8,
767
+ backgroundColor: isAutoRamLimitEnabled
768
+ ? `${AppColors.purple}14`
769
+ : AppColors.grayBackground,
770
+ alignItems: 'center',
771
+ justifyContent: 'center',
772
+ }}>
773
+ <SignalIcon color={isAutoRamLimitEnabled ? AppColors.purple : AppColors.grayTextWeak} size={15}/>
774
+ </View>
775
+ <View style={{ flex: 1 }}>
776
+ <Text style={{
777
+ fontFamily: AppFonts.interBold,
778
+ fontSize: 13.5,
779
+ lineHeight: 18,
780
+ color: isAutoRamLimitEnabled
781
+ ? AppColors.primaryBlack
782
+ : AppColors.grayText,
783
+ }}>
784
+ Detected Free RAM
785
+ </Text>
786
+ <Text style={{
787
+ fontFamily: AppFonts.interRegular,
788
+ fontSize: 11,
789
+ lineHeight: 15,
790
+ color: AppColors.grayText,
791
+ marginTop: 1,
792
+ }}>
793
+ {deviceFreeRamMb} MB available
794
+ </Text>
817
795
  </View>
818
-
819
796
  <View style={{
820
- flexDirection: 'row',
821
- backgroundColor: AppColors.grayBackground,
822
- borderRadius: 10,
823
- padding: 3,
824
- borderWidth: 1,
825
- borderColor: AppColors.dividerColor,
826
- }}>
827
- {[50, 70, 90, 100].map(opt => {
828
- const isActive = modalHeightPercent === opt;
829
- return (<TouchableScale key={opt} accessible={true} accessibilityRole="button" accessibilityLabel={`Set window height to ${opt} percent`} accessibilityState={{ selected: isActive }} onPress={() => setModalHeightPercent(opt)} style={{
830
- flex: 1,
831
- paddingVertical: 7,
832
- alignItems: 'center',
833
- borderRadius: 8,
834
- backgroundColor: isActive
835
- ? AppColors.purple
836
- : 'transparent',
837
- }}>
838
- <Text style={{
839
- fontFamily: AppFonts.interBold,
840
- fontSize: 11.5,
841
- lineHeight: 15,
842
- color: isActive
843
- ? AppColors.white
844
- : AppColors.grayText,
845
- }}>
846
- {opt}%
847
- </Text>
848
- </TouchableScale>);
849
- })}
797
+ paddingHorizontal: 10,
798
+ paddingVertical: 5,
799
+ borderRadius: 8,
800
+ backgroundColor: isAutoRamLimitEnabled
801
+ ? `${AppColors.purple}12`
802
+ : AppColors.grayBackground,
803
+ borderWidth: 1,
804
+ borderColor: isAutoRamLimitEnabled
805
+ ? `${AppColors.purple}26`
806
+ : AppColors.dividerColor,
807
+ }}>
808
+ <Text style={{
809
+ fontFamily: AppFonts.interBold,
810
+ fontSize: 12,
811
+ lineHeight: 15,
812
+ color: isAutoRamLimitEnabled
813
+ ? AppColors.purple
814
+ : AppColors.grayText,
815
+ }}>
816
+ {deviceFreeRamMb} MB
817
+ </Text>
850
818
  </View>
851
819
  </View>
852
820
 
853
- <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
854
-
855
- {/* Modal Animation */}
856
- <View style={{ gap: 8 }}>
857
- <View style={{
858
- flexDirection: 'row',
859
- alignItems: 'center',
860
- gap: 10,
821
+ {/* Current Auto-Calculated Limits Preview */}
822
+ {isAutoRamLimitEnabled && (<View style={{ marginTop: 8, paddingTop: 8, borderTopWidth: 1, borderTopColor: AppColors.dividerColor, gap: 6 }}>
823
+ <Text style={{
824
+ fontFamily: AppFonts.interBold,
825
+ fontSize: 11.5,
826
+ lineHeight: 15,
827
+ color: AppColors.purple,
861
828
  }}>
862
- <View style={{
863
- width: 32,
864
- height: 32,
865
- borderRadius: 8,
866
- backgroundColor: AppColors.purpleShade50,
867
- alignItems: 'center',
868
- justifyContent: 'center',
869
- }}>
870
- <MotionIcon color={AppColors.purple} size={15}/>
871
- </View>
872
- <View style={{ flex: 1 }}>
873
- <Text style={{
874
- fontFamily: AppFonts.interBold,
875
- fontSize: 13.5,
876
- lineHeight: 18,
877
- color: AppColors.primaryBlack,
878
- }}>
879
- {t('settings.general.modalAnimation')}
880
- </Text>
881
- <Text style={{
882
- fontFamily: AppFonts.interRegular,
883
- fontSize: 11,
884
- lineHeight: 15,
885
- color: AppColors.grayText,
886
- marginTop: 1,
887
- }}>
888
- {t('settings.general.modalAnimationDescription')}
889
- </Text>
890
- </View>
891
- </View>
892
-
893
- <View style={{
894
- flexDirection: 'row',
895
- backgroundColor: AppColors.grayBackground,
896
- borderRadius: 10,
897
- padding: 3,
898
- borderWidth: 1,
899
- borderColor: AppColors.dividerColor,
900
- }}>
901
- {[
902
- { key: 'slide', label: 'Slide Up' },
903
- { key: 'fade', label: 'Fade' },
904
- { key: 'none', label: 'None' },
905
- ].map(opt => {
906
- const isActive = modalAnimationType === opt.key;
907
- return (<TouchableScale key={opt.key} accessible={true} accessibilityRole="button" accessibilityLabel={`Set transition animation to ${opt.label}`} accessibilityState={{ selected: isActive }} onPress={() => setModalAnimationType(opt.key)} style={{
908
- flex: 1,
909
- paddingVertical: 7,
910
- alignItems: 'center',
911
- borderRadius: 8,
912
- backgroundColor: isActive
913
- ? AppColors.purple
914
- : 'transparent',
915
- }}>
829
+ Auto Profile: {autoRamProfile.profileName}
830
+ </Text>
831
+ <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
832
+ {[
833
+ { label: 'APIs', value: autoRamProfile.maxNetworkLogs, color: AppColors.blue500 },
834
+ { label: 'Logs', value: autoRamProfile.maxConsoleLogs, color: AppColors.sky500 },
835
+ { label: 'Analytics', value: autoRamProfile.maxAnalyticsEvents, color: AppColors.purple },
836
+ { label: 'Crash', value: autoRamProfile.maxCrashRecords, color: AppColors.errorColor },
837
+ ].map(item => (<View key={item.label} style={{
838
+ flexDirection: 'row',
839
+ alignItems: 'center',
840
+ gap: 4,
841
+ paddingHorizontal: 8,
842
+ paddingVertical: 4,
843
+ borderRadius: 6,
844
+ backgroundColor: `${item.color}14`,
845
+ borderWidth: 1,
846
+ borderColor: `${item.color}33`,
847
+ }}>
916
848
  <Text style={{
917
- fontFamily: AppFonts.interBold,
918
- fontSize: 11.5,
919
- lineHeight: 15,
920
- color: isActive
921
- ? AppColors.white
922
- : AppColors.grayText,
923
- }}>
924
- {opt.label}
849
+ fontFamily: AppFonts.interBold,
850
+ fontSize: 10.5,
851
+ lineHeight: 14,
852
+ color: item.color,
853
+ }}>
854
+ {item.label}
925
855
  </Text>
926
- </TouchableScale>);
927
- })}
928
- </View>
929
- </View>
856
+ <Text style={{
857
+ fontFamily: AppFonts.interBold,
858
+ fontSize: 10.5,
859
+ lineHeight: 14,
860
+ color: item.color,
861
+ }}>
862
+ {item.value}
863
+ </Text>
864
+ </View>))}
865
+ </View>
866
+ </View>)}
930
867
  </View>
931
-
932
- {/* Section 3: Default Startup Tab */}
868
+ </View>) : (<View style={{ gap: 14 }}>
869
+ {/* Section 1: Appearance */}
933
870
  <View style={{
934
- backgroundColor: AppColors.primaryLight,
935
- borderRadius: 14,
936
- borderWidth: 1,
937
- borderColor: AppColors.grayBorderSecondary,
938
- overflow: 'hidden',
939
- padding: 14,
940
- gap: 12,
941
- }}>
871
+ backgroundColor: AppColors.primaryLight,
872
+ borderRadius: 14,
873
+ borderWidth: 1,
874
+ borderColor: AppColors.grayBorderSecondary,
875
+ overflow: 'hidden',
876
+ padding: 14,
877
+ gap: 12,
878
+ }}>
942
879
  <Text style={{
943
- fontFamily: AppFonts.interBold,
944
- fontSize: 11,
945
- lineHeight: 14,
946
- color: AppColors.grayTextWeak,
947
- letterSpacing: 0.8,
948
- }}>
949
- {t('settings.startupDefault')}
880
+ fontFamily: AppFonts.interBold,
881
+ fontSize: 11,
882
+ lineHeight: 14,
883
+ color: AppColors.grayTextWeak,
884
+ letterSpacing: 0.8,
885
+ }}>
886
+ {t('settings.appearanceTheme')}
950
887
  </Text>
951
888
 
952
889
  <View style={{
953
- flexDirection: 'row',
954
- alignItems: 'center',
955
- gap: 10,
956
- }}>
890
+ flexDirection: 'row',
891
+ alignItems: 'center',
892
+ justifyContent: 'space-between',
893
+ }}>
957
894
  <View style={{
958
- width: 32,
959
- height: 32,
960
- borderRadius: 8,
961
- backgroundColor: AppColors.purpleShade50,
962
- alignItems: 'center',
963
- justifyContent: 'center',
964
- }}>
965
- <LayersIcon color={AppColors.purple} size={15}/>
966
- </View>
967
- <View style={{ flex: 1 }}>
968
- <Text style={{
969
- fontFamily: AppFonts.interBold,
970
- fontSize: 13.5,
971
- lineHeight: 18,
972
- color: AppColors.primaryBlack,
973
- }}>
974
- {t('settings.general.defaultOpeningTab')}
975
- </Text>
976
- <Text style={{
977
- fontFamily: AppFonts.interRegular,
978
- fontSize: 11,
979
- lineHeight: 15,
980
- color: AppColors.grayText,
981
- marginTop: 1,
982
- }}>
983
- {t('settings.general.defaultOpeningTabDesc')}
984
- </Text>
895
+ flexDirection: 'row',
896
+ alignItems: 'center',
897
+ gap: 10,
898
+ flex: 1,
899
+ }}>
900
+ <View style={{
901
+ width: 32,
902
+ height: 32,
903
+ borderRadius: 8,
904
+ backgroundColor: AppColors.purpleShade50,
905
+ alignItems: 'center',
906
+ justifyContent: 'center',
907
+ }}>
908
+ {isDark ? (<SunIcon color={AppColors.purple} size={15}/>) : (<MoonIcon color={AppColors.purple} size={15}/>)}
909
+ </View>
910
+ <View style={{ flex: 1 }}>
911
+ <Text style={{
912
+ fontFamily: AppFonts.interBold,
913
+ fontSize: 13.5,
914
+ lineHeight: 18,
915
+ color: AppColors.primaryBlack,
916
+ }}>
917
+ {t('settings.general.darkMode')}
918
+ </Text>
919
+ <Text style={{
920
+ fontFamily: AppFonts.interRegular,
921
+ fontSize: 11,
922
+ lineHeight: 15,
923
+ color: AppColors.grayText,
924
+ marginTop: 1,
925
+ }}>
926
+ {t('settings.general.darkModeDescription')}
927
+ </Text>
928
+ </View>
985
929
  </View>
986
- </View>
987
930
 
988
- {/* Grid of Default Tab Cards */}
989
- <View style={{
990
- flexDirection: 'row',
991
- flexWrap: 'wrap',
992
- gap: 8,
993
- marginTop: 4,
994
- }}>
995
- {allModules
996
- .filter(tab => tab.key === 'apis' ||
997
- stagedTabVisibility?.[tab.key] ||
998
- tabVisibility?.[tab.key])
999
- .map(tab => {
1000
- const isActive = defaultTab === tab.key;
1001
- return (<TouchableScale key={tab.key} accessible={true} accessibilityRole="button" accessibilityLabel={`Set ${tab.label} as default tab`} accessibilityState={{ selected: isActive }} onPress={() => setDefaultTab(tab.key)} style={{
1002
- flexDirection: 'row',
1003
- alignItems: 'center',
1004
- gap: 8,
1005
- paddingVertical: 10,
1006
- paddingHorizontal: 12,
1007
- borderRadius: 10,
1008
- borderWidth: 1.5,
1009
- borderColor: isActive
1010
- ? AppColors.purple
1011
- : AppColors.grayBorderSecondary,
1012
- backgroundColor: isActive
1013
- ? `${AppColors.purple}0F`
1014
- : AppColors.primaryLight,
1015
- minWidth: '47%',
1016
- flex: 1,
1017
- }}>
1018
- <Text style={{
1019
- fontFamily: AppFonts.interBold,
1020
- fontSize: 12.5,
1021
- lineHeight: 16,
1022
- color: isActive
1023
- ? AppColors.purple
1024
- : AppColors.primaryBlack,
1025
- flex: 1,
1026
- }}>
1027
- {tab.label}
1028
- </Text>
1029
- {isActive && (<View style={{
1030
- width: 16,
1031
- height: 16,
1032
- borderRadius: 8,
1033
- backgroundColor: AppColors.purple,
1034
- alignItems: 'center',
1035
- justifyContent: 'center',
1036
- }}>
1037
- <CheckIcon size={9} color={AppColors.white}/>
1038
- </View>)}
1039
- </TouchableScale>);
1040
- })}
931
+ <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle Dark Theme" accessibilityState={{ checked: isDark }} onPress={() => {
932
+ const newTheme = !isDark;
933
+ setIsDark(newTheme);
934
+ toggleGlobalTheme(newTheme);
935
+ }} style={{
936
+ width: 42,
937
+ height: 24,
938
+ borderRadius: 12,
939
+ backgroundColor: isDark
940
+ ? AppColors.purple
941
+ : AppColors.grayBorderSecondary,
942
+ padding: 2,
943
+ justifyContent: 'center',
944
+ alignItems: isDark ? 'flex-end' : 'flex-start',
945
+ }}>
946
+ <View style={{
947
+ width: 20,
948
+ height: 20,
949
+ borderRadius: 10,
950
+ backgroundColor: AppColors.white,
951
+ shadowColor: AppColors.black,
952
+ shadowOpacity: 0.18,
953
+ shadowRadius: 2,
954
+ shadowOffset: { width: 0, height: 1 },
955
+ }}/>
956
+ </TouchableScale>
1041
957
  </View>
1042
958
  </View>
1043
959
 
1044
- {/* Section 4: Log Deduplication & Levels */}
960
+ {/* Section 2: Window & Layout */}
1045
961
  <View style={{
1046
- backgroundColor: AppColors.primaryLight,
1047
- borderRadius: 14,
1048
- borderWidth: 1,
1049
- borderColor: AppColors.grayBorderSecondary,
1050
- overflow: 'hidden',
1051
- padding: 14,
1052
- gap: 14,
1053
- }}>
962
+ backgroundColor: AppColors.primaryLight,
963
+ borderRadius: 14,
964
+ borderWidth: 1,
965
+ borderColor: AppColors.grayBorderSecondary,
966
+ overflow: 'hidden',
967
+ padding: 14,
968
+ gap: 14,
969
+ }}>
1054
970
  <Text style={{
1055
- fontFamily: AppFonts.interBold,
1056
- fontSize: 11,
1057
- lineHeight: 14,
1058
- color: AppColors.grayTextWeak,
1059
- letterSpacing: 0.8,
1060
- }}>
1061
- {t('settings.logFiltersDeduplication')}
971
+ fontFamily: AppFonts.interBold,
972
+ fontSize: 11,
973
+ lineHeight: 14,
974
+ color: AppColors.grayTextWeak,
975
+ letterSpacing: 0.8,
976
+ }}>
977
+ {t('settings.windowLayout')}
1062
978
  </Text>
1063
979
 
1064
- {/* Show Duplicate Logs */}
1065
- <View style={{
1066
- flexDirection: 'row',
1067
- alignItems: 'center',
1068
- justifyContent: 'space-between',
1069
- }}>
980
+ {/* Modal Height */}
981
+ <View style={{ gap: 8 }}>
1070
982
  <View style={{
1071
- flexDirection: 'row',
1072
- alignItems: 'center',
1073
- gap: 10,
1074
- flex: 1,
1075
- }}>
983
+ flexDirection: 'row',
984
+ alignItems: 'center',
985
+ justifyContent: 'space-between',
986
+ gap: 10,
987
+ }}>
1076
988
  <View style={{
1077
- width: 32,
1078
- height: 32,
1079
- borderRadius: 8,
1080
- backgroundColor: AppColors.purpleShade50,
989
+ flexDirection: 'row',
990
+ alignItems: 'center',
991
+ gap: 10,
992
+ flex: 1,
993
+ }}>
994
+ <View style={{
995
+ width: 32,
996
+ height: 32,
997
+ borderRadius: 8,
998
+ backgroundColor: AppColors.purpleShade50,
999
+ alignItems: 'center',
1000
+ justifyContent: 'center',
1001
+ }}>
1002
+ <ScreenIcon color={AppColors.purple} size={15}/>
1003
+ </View>
1004
+ <View style={{ flex: 1 }}>
1005
+ <Text style={{
1006
+ fontFamily: AppFonts.interBold,
1007
+ fontSize: 13.5,
1008
+ lineHeight: 18,
1009
+ color: AppColors.primaryBlack,
1010
+ }}>
1011
+ {t('settings.general.modalHeight')}
1012
+ </Text>
1013
+ <Text style={{
1014
+ fontFamily: AppFonts.interRegular,
1015
+ fontSize: 11,
1016
+ lineHeight: 15,
1017
+ color: AppColors.grayText,
1018
+ marginTop: 1,
1019
+ }}>
1020
+ {t('settings.general.modalHeightDescription')}
1021
+ </Text>
1022
+ </View>
1023
+ </View>
1024
+
1025
+ {stagedHeight !== modalHeightPercent && (<TouchableScale onPress={() => {
1026
+ setModalHeightPercent(stagedHeight);
1027
+ }} style={{
1028
+ flexDirection: 'row',
1081
1029
  alignItems: 'center',
1082
- justifyContent: 'center',
1030
+ gap: 5,
1031
+ backgroundColor: AppColors.purple,
1032
+ paddingHorizontal: 10,
1033
+ paddingVertical: 5,
1034
+ borderRadius: 7,
1035
+ shadowColor: AppColors.purple,
1036
+ shadowOffset: { width: 0, height: 1 },
1037
+ shadowOpacity: 0.28,
1038
+ shadowRadius: 2,
1039
+ elevation: 2,
1083
1040
  }}>
1084
- <EyeIcon color={AppColors.purple} size={15}/>
1085
- </View>
1086
- <View style={{ flex: 1 }}>
1087
- <Text style={{
1041
+ <CheckIcon size={11} color={AppColors.white}/>
1042
+ <Text style={{
1088
1043
  fontFamily: AppFonts.interBold,
1089
- fontSize: 13.5,
1090
- lineHeight: 18,
1091
- color: AppColors.primaryBlack,
1092
- }}>
1093
- {t('settings.general.duplicateLogs')}
1094
- </Text>
1095
- <Text style={{
1096
- fontFamily: AppFonts.interRegular,
1097
1044
  fontSize: 11,
1098
- lineHeight: 15,
1099
- color: AppColors.grayText,
1100
- marginTop: 1,
1045
+ color: AppColors.white,
1101
1046
  }}>
1102
- {t('settings.general.duplicateLogsDescription')}
1103
- </Text>
1104
- </View>
1047
+ Save ({stagedHeight}%)
1048
+ </Text>
1049
+ </TouchableScale>)}
1105
1050
  </View>
1106
1051
 
1107
- <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle show duplicate logs" accessibilityState={{ checked: showDuplicateLogs }} onPress={() => setShowDuplicateLogs(prev => !prev)} style={{
1108
- width: 42,
1109
- height: 24,
1110
- borderRadius: 12,
1111
- backgroundColor: showDuplicateLogs
1112
- ? AppColors.purple
1113
- : AppColors.grayBorderSecondary,
1114
- padding: 2,
1115
- justifyContent: 'center',
1116
- alignItems: showDuplicateLogs
1117
- ? 'flex-end'
1118
- : 'flex-start',
1119
- }}>
1120
- <View style={{
1121
- width: 20,
1122
- height: 20,
1123
- borderRadius: 10,
1124
- backgroundColor: AppColors.white,
1125
- shadowColor: AppColors.black,
1126
- shadowOpacity: 0.18,
1127
- shadowRadius: 2,
1128
- shadowOffset: { width: 0, height: 1 },
1129
- }}/>
1130
- </TouchableScale>
1052
+ <View style={{ marginTop: 6 }}>
1053
+ <Slider value={stagedHeight} onValueChange={setStagedHeight} min={50} max={90} step={5} quickPresets={[50, 60, 70, 80, 90]} formatLabel={val => `${Math.round(val)}%`}/>
1054
+ </View>
1131
1055
  </View>
1132
1056
 
1133
1057
  <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
1134
1058
 
1135
- {/* Console Log Levels */}
1059
+ {/* Modal Animation */}
1136
1060
  <View style={{ gap: 8 }}>
1137
1061
  <View style={{
1138
- flexDirection: 'row',
1139
- alignItems: 'center',
1140
- gap: 10,
1141
- }}>
1062
+ flexDirection: 'row',
1063
+ alignItems: 'center',
1064
+ gap: 10,
1065
+ }}>
1142
1066
  <View style={{
1143
- width: 32,
1144
- height: 32,
1145
- borderRadius: 8,
1146
- backgroundColor: AppColors.purpleShade50,
1147
- alignItems: 'center',
1148
- justifyContent: 'center',
1149
- }}>
1150
- <TerminalIcon color={AppColors.purple} size={15}/>
1067
+ width: 32,
1068
+ height: 32,
1069
+ borderRadius: 8,
1070
+ backgroundColor: AppColors.purpleShade50,
1071
+ alignItems: 'center',
1072
+ justifyContent: 'center',
1073
+ }}>
1074
+ <MotionIcon color={AppColors.purple} size={15}/>
1151
1075
  </View>
1152
1076
  <View style={{ flex: 1 }}>
1153
1077
  <Text style={{
1154
- fontFamily: AppFonts.interBold,
1155
- fontSize: 13.5,
1156
- lineHeight: 18,
1157
- color: AppColors.primaryBlack,
1158
- }}>
1159
- {t('settings.general.activeConsoleLogLevels')}
1078
+ fontFamily: AppFonts.interBold,
1079
+ fontSize: 13.5,
1080
+ lineHeight: 18,
1081
+ color: AppColors.primaryBlack,
1082
+ }}>
1083
+ {t('settings.general.modalAnimation')}
1160
1084
  </Text>
1161
1085
  <Text style={{
1162
- fontFamily: AppFonts.interRegular,
1163
- fontSize: 11,
1164
- lineHeight: 15,
1165
- color: AppColors.grayText,
1166
- marginTop: 1,
1167
- }}>
1168
- {t('settings.general.activeConsoleLogLevelsDesc')}
1086
+ fontFamily: AppFonts.interRegular,
1087
+ fontSize: 11,
1088
+ lineHeight: 15,
1089
+ color: AppColors.grayText,
1090
+ marginTop: 1,
1091
+ }}>
1092
+ {t('settings.general.modalAnimationDescription')}
1169
1093
  </Text>
1170
1094
  </View>
1171
1095
  </View>
1172
1096
 
1173
- <View style={{ flexDirection: 'row', gap: 8, marginTop: 4 }}>
1097
+ <View style={{
1098
+ flexDirection: 'row',
1099
+ backgroundColor: AppColors.grayBackground,
1100
+ borderRadius: 10,
1101
+ padding: 3,
1102
+ borderWidth: 1,
1103
+ borderColor: AppColors.dividerColor,
1104
+ }}>
1174
1105
  {[
1175
- {
1176
- key: 'info',
1177
- label: 'Info',
1178
- color: AppColors.blue500,
1179
- },
1180
- {
1181
- key: 'warn',
1182
- label: 'Warning',
1183
- color: AppColors.amber500,
1184
- },
1185
- {
1186
- key: 'error',
1187
- label: 'Error',
1188
- color: AppColors.errorColor,
1189
- },
1190
- ].map(level => {
1191
- const isActive = showConsoleLevels[level.key];
1192
- return (<TouchableScale key={level.key} accessible={true} accessibilityRole="checkbox" accessibilityLabel={`Toggle ${level.label} logs`} accessibilityState={{ checked: isActive }} onPress={() => setShowConsoleLevels(prev => ({
1193
- ...prev,
1194
- [level.key]: !prev[level.key],
1195
- }))} style={{
1196
- flex: 1,
1197
- flexDirection: 'row',
1198
- alignItems: 'center',
1199
- justifyContent: 'center',
1200
- gap: 6,
1201
- paddingVertical: 8,
1202
- borderRadius: 9,
1203
- borderWidth: 1.5,
1204
- borderColor: isActive
1205
- ? level.color
1206
- : AppColors.grayBorderSecondary,
1207
- backgroundColor: isActive
1208
- ? `${level.color}0D`
1209
- : AppColors.primaryLight,
1210
- }}>
1211
- <View style={{
1212
- width: 8,
1213
- height: 8,
1214
- borderRadius: 4,
1215
- backgroundColor: isActive
1216
- ? level.color
1217
- : AppColors.grayBorderSecondary,
1218
- }}/>
1106
+ { key: 'slide', label: 'Slide Up' },
1107
+ { key: 'fade', label: 'Fade' },
1108
+ { key: 'none', label: 'None' },
1109
+ ].map(opt => {
1110
+ const isActive = modalAnimationType === opt.key;
1111
+ return (<TouchableScale key={opt.key} accessible={true} accessibilityRole="button" accessibilityLabel={`Set transition animation to ${opt.label}`} accessibilityState={{ selected: isActive }} onPress={() => setModalAnimationType(opt.key)} style={{
1112
+ flex: 1,
1113
+ paddingVertical: 7,
1114
+ alignItems: 'center',
1115
+ borderRadius: 8,
1116
+ backgroundColor: isActive
1117
+ ? AppColors.purple
1118
+ : 'transparent',
1119
+ }}>
1219
1120
  <Text style={{
1220
- fontFamily: AppFonts.interBold,
1221
- fontSize: 11.5,
1222
- lineHeight: 15,
1223
- color: isActive
1224
- ? level.color
1225
- : AppColors.grayText,
1226
- }}>
1227
- {level.label}
1121
+ fontFamily: AppFonts.interBold,
1122
+ fontSize: 11.5,
1123
+ lineHeight: 15,
1124
+ color: isActive
1125
+ ? AppColors.white
1126
+ : AppColors.grayText,
1127
+ }}>
1128
+ {opt.label}
1228
1129
  </Text>
1229
1130
  </TouchableScale>);
1230
- })}
1131
+ })}
1231
1132
  </View>
1232
1133
  </View>
1233
1134
  </View>
1234
1135
 
1235
- {/* Section 5: Reset Settings */}
1136
+ {/* Section 3: Default Startup Tab */}
1236
1137
  <View style={{
1237
- backgroundColor: AppColors.primaryLight,
1238
- borderRadius: 14,
1239
- borderWidth: 1,
1240
- borderColor: `${AppColors.errorColor}33`,
1241
- overflow: 'hidden',
1242
- padding: 14,
1243
- flexDirection: 'row',
1244
- alignItems: 'center',
1245
- gap: 12,
1246
- }}>
1138
+ backgroundColor: AppColors.primaryLight,
1139
+ borderRadius: 14,
1140
+ borderWidth: 1,
1141
+ borderColor: AppColors.grayBorderSecondary,
1142
+ overflow: 'hidden',
1143
+ padding: 14,
1144
+ gap: 12,
1145
+ }}>
1146
+ <Text style={{
1147
+ fontFamily: AppFonts.interBold,
1148
+ fontSize: 11,
1149
+ lineHeight: 14,
1150
+ color: AppColors.grayTextWeak,
1151
+ letterSpacing: 0.8,
1152
+ }}>
1153
+ {t('settings.startupDefault')}
1154
+ </Text>
1155
+
1247
1156
  <View style={{
1248
- width: 34,
1249
- height: 34,
1250
- borderRadius: 9,
1251
- backgroundColor: `${AppColors.errorColor}14`,
1252
- alignItems: 'center',
1253
- justifyContent: 'center',
1254
- }}>
1255
- <TrashIcon color={AppColors.errorColor} size={15}/>
1256
- </View>
1257
- <View style={{ flex: 1 }}>
1258
- <Text style={{
1259
- fontFamily: AppFonts.interBold,
1260
- fontSize: 13.5,
1261
- lineHeight: 18,
1262
- color: AppColors.primaryBlack,
1263
- }}>
1264
- {t('settings.resetSettings')}
1265
- </Text>
1266
- <Text style={{
1267
- fontFamily: AppFonts.interRegular,
1268
- fontSize: 11,
1269
- lineHeight: 15,
1270
- color: AppColors.grayText,
1271
- marginTop: 1,
1272
- }}>
1273
- {t('settings.resetSettingsDesc')}
1274
- </Text>
1157
+ flexDirection: 'row',
1158
+ alignItems: 'center',
1159
+ gap: 10,
1160
+ }}>
1161
+ <View style={{
1162
+ width: 32,
1163
+ height: 32,
1164
+ borderRadius: 8,
1165
+ backgroundColor: AppColors.purpleShade50,
1166
+ alignItems: 'center',
1167
+ justifyContent: 'center',
1168
+ }}>
1169
+ <LayersIcon color={AppColors.purple} size={15}/>
1170
+ </View>
1171
+ <View style={{ flex: 1 }}>
1172
+ <Text style={{
1173
+ fontFamily: AppFonts.interBold,
1174
+ fontSize: 13.5,
1175
+ lineHeight: 18,
1176
+ color: AppColors.primaryBlack,
1177
+ }}>
1178
+ {t('settings.general.defaultOpeningTab')}
1179
+ </Text>
1180
+ <Text style={{
1181
+ fontFamily: AppFonts.interRegular,
1182
+ fontSize: 11,
1183
+ lineHeight: 15,
1184
+ color: AppColors.grayText,
1185
+ marginTop: 1,
1186
+ }}>
1187
+ {t('settings.general.defaultOpeningTabDesc')}
1188
+ </Text>
1189
+ </View>
1275
1190
  </View>
1276
1191
 
1277
- <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel="Reset all settings to default" onPress={resetToDefaults} style={{
1278
- paddingHorizontal: 12,
1279
- paddingVertical: 7,
1280
- borderRadius: 8,
1281
- backgroundColor: `${AppColors.errorColor}14`,
1282
- borderWidth: 1,
1283
- borderColor: `${AppColors.errorColor}38`,
1284
- }}>
1285
- <Text style={{
1286
- fontFamily: AppFonts.interBold,
1287
- fontSize: 11.5,
1288
- lineHeight: 15,
1289
- color: AppColors.errorColor,
1290
- }}>
1291
- {t('settings.reset')}
1292
- </Text>
1293
- </TouchableScale>
1294
- </View>
1295
- </View>)}
1296
-
1297
- {/* Storage & Engine Status Card */}
1298
- <View style={{
1299
- backgroundColor: isPersistent
1300
- ? `${AppColors.liveGreen}12`
1301
- : `${AppColors.amber600}12`,
1302
- borderRadius: 12,
1303
- borderWidth: 1,
1304
- borderColor: isPersistent
1305
- ? `${AppColors.liveGreen}33`
1306
- : `${AppColors.amber600}33`,
1307
- padding: 12,
1308
- marginBottom: 10,
1192
+ {/* Sleek Interactive Dropdown Picker */}
1193
+ <View style={{ marginTop: 6, gap: 6 }}>
1194
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={`Default opening tab: ${allModules.find(m => m.key === defaultTab)?.label || 'APIs'}`} onPress={() => setIsDefaultTabDropdownOpen(prev => !prev)} style={{
1195
+ flexDirection: 'row',
1196
+ alignItems: 'center',
1197
+ justifyContent: 'space-between',
1198
+ backgroundColor: AppColors.grayBackground,
1199
+ borderRadius: 10,
1200
+ borderWidth: 1.5,
1201
+ borderColor: isDefaultTabDropdownOpen
1202
+ ? AppColors.purple
1203
+ : AppColors.grayBorderSecondary,
1204
+ paddingHorizontal: 14,
1205
+ paddingVertical: 11,
1206
+ }}>
1207
+ <View style={{
1309
1208
  flexDirection: 'row',
1310
1209
  alignItems: 'center',
1311
1210
  gap: 10,
1211
+ flex: 1,
1312
1212
  }}>
1313
- <View style={{
1213
+ <View style={{
1314
1214
  width: 8,
1315
1215
  height: 8,
1316
1216
  borderRadius: 4,
1317
- backgroundColor: isPersistent
1318
- ? AppColors.green500
1319
- : AppColors.amber600,
1217
+ backgroundColor: AppColors.purple,
1320
1218
  }}/>
1321
- <View style={{ flex: 1 }}>
1322
- <Text style={{
1219
+ <Text style={{
1323
1220
  fontFamily: AppFonts.interBold,
1324
- fontSize: 12,
1325
- lineHeight: 16,
1326
- color: isPersistent
1327
- ? AppColors.green700
1328
- : AppColors.amber800,
1329
- }}>
1330
- {isPersistent
1331
- ? t('settings.general.storageStatusEnabled', {
1332
- type: storage ? 'Custom Storage' : 'iOS NSUserDefaults',
1333
- })
1334
- : t('settings.general.storageStatusDisabled')}
1335
- </Text>
1336
- <Text style={{
1337
- fontFamily: AppFonts.interRegular,
1338
- fontSize: 10.5,
1339
- lineHeight: 14,
1340
- color: isPersistent
1341
- ? AppColors.green800
1342
- : AppColors.amber800,
1343
- marginTop: 2,
1344
- opacity: 0.85,
1345
- }}>
1346
- {isPersistent
1347
- ? t('settings.general.storageStatusEnabledDesc')
1348
- : t('settings.general.storageStatusDisabledDesc')}
1349
- </Text>
1350
- </View>
1351
- </View>
1352
- </ScrollView>
1221
+ fontSize: 13.5,
1222
+ lineHeight: 18,
1223
+ color: AppColors.primaryBlack,
1224
+ }}>
1225
+ {allModules.find(m => m.key === defaultTab)?.label || 'APIs (Network)'}
1226
+ </Text>
1227
+ <View style={{
1228
+ backgroundColor: `${AppColors.purple}14`,
1229
+ paddingHorizontal: 6,
1230
+ paddingVertical: 1.5,
1231
+ borderRadius: 6,
1232
+ }}>
1233
+ <Text style={{
1234
+ fontFamily: AppFonts.interBold,
1235
+ fontSize: 8.5,
1236
+ color: AppColors.purple,
1237
+ }}>
1238
+ DEFAULT
1239
+ </Text>
1240
+ </View>
1241
+ </View>
1353
1242
 
1354
- {/* Bottom Sticky Action Bar for Save Changes */}
1355
- {settingsActiveSubTab === 'module' && (<View style={{
1356
- paddingHorizontal: 16,
1357
- paddingTop: 12,
1358
- paddingBottom: Platform.OS === 'ios' ? 26 : 14,
1243
+ <View style={{
1244
+ transform: [
1245
+ { rotate: isDefaultTabDropdownOpen ? '180deg' : '0deg' },
1246
+ ],
1247
+ }}>
1248
+ <ChevronDownIcon color={AppColors.grayText} size={15}/>
1249
+ </View>
1250
+ </TouchableScale>
1251
+
1252
+ {/* Dropdown Options List */}
1253
+ {isDefaultTabDropdownOpen && (<View style={{
1359
1254
  backgroundColor: AppColors.primaryLight,
1360
- borderTopWidth: 1,
1361
- borderTopColor: AppColors.dividerColor,
1362
- flexDirection: 'row',
1363
- alignItems: 'center',
1364
- gap: 12,
1255
+ borderRadius: 10,
1256
+ borderWidth: 1,
1257
+ borderColor: AppColors.grayBorderSecondary,
1258
+ overflow: 'hidden',
1365
1259
  shadowColor: AppColors.black,
1366
1260
  shadowOpacity: 0.08,
1367
- shadowRadius: 8,
1368
- shadowOffset: { width: 0, height: -3 },
1369
- elevation: 8,
1261
+ shadowRadius: 6,
1262
+ shadowOffset: { width: 0, height: 3 },
1263
+ elevation: 4,
1370
1264
  }}>
1265
+ {allModules
1266
+ .filter(tab => tab.key === 'apis' ||
1267
+ stagedTabVisibility?.[tab.key] ||
1268
+ tabVisibility?.[tab.key])
1269
+ .map((tab, idx, arr) => {
1270
+ const isActive = defaultTab === tab.key;
1271
+ const isLast = idx === arr.length - 1;
1272
+ return (<TouchableScale key={tab.key} accessible={true} accessibilityRole="button" accessibilityLabel={`Select ${tab.label} as default tab`} onPress={() => {
1273
+ setDefaultTab(tab.key);
1274
+ setIsDefaultTabDropdownOpen(false);
1275
+ }} style={{
1276
+ flexDirection: 'row',
1277
+ alignItems: 'center',
1278
+ justifyContent: 'space-between',
1279
+ paddingHorizontal: 14,
1280
+ paddingVertical: 11,
1281
+ backgroundColor: isActive
1282
+ ? `${AppColors.purple}0F`
1283
+ : 'transparent',
1284
+ borderBottomWidth: isLast ? 0 : 1,
1285
+ borderBottomColor: AppColors.dividerColor,
1286
+ }}>
1287
+ <View style={{
1288
+ flexDirection: 'row',
1289
+ alignItems: 'center',
1290
+ gap: 10,
1291
+ }}>
1292
+ <View style={{
1293
+ width: 6,
1294
+ height: 6,
1295
+ borderRadius: 3,
1296
+ backgroundColor: isActive
1297
+ ? AppColors.purple
1298
+ : AppColors.grayTextWeak,
1299
+ }}/>
1300
+ <Text style={{
1301
+ fontFamily: isActive
1302
+ ? AppFonts.interBold
1303
+ : AppFonts.interMedium,
1304
+ fontSize: 13,
1305
+ color: isActive
1306
+ ? AppColors.purple
1307
+ : AppColors.primaryBlack,
1308
+ }}>
1309
+ {tab.label}
1310
+ </Text>
1311
+ </View>
1312
+ {isActive && (<CheckIcon size={14} color={AppColors.purple}/>)}
1313
+ </TouchableScale>);
1314
+ })}
1315
+ </View>)}
1316
+ </View>
1317
+ </View>
1318
+ </View>)}
1319
+ <View style={{ height: 48 }}/>
1320
+ </ScrollView>
1321
+
1322
+ {/* Bottom Sticky Action Bar for Save Changes */}
1323
+ {settingsActiveSubTab === 'module' && (<View style={{
1324
+ paddingHorizontal: 16,
1325
+ paddingTop: 12,
1326
+ paddingBottom: Platform.OS === 'ios' ? 36 : 24,
1327
+ backgroundColor: AppColors.primaryLight,
1328
+ borderTopWidth: 1,
1329
+ borderTopColor: AppColors.dividerColor,
1330
+ flexDirection: 'row',
1331
+ alignItems: 'center',
1332
+ gap: 12,
1333
+ shadowColor: AppColors.black,
1334
+ shadowOpacity: 0.08,
1335
+ shadowRadius: 8,
1336
+ shadowOffset: { width: 0, height: -3 },
1337
+ elevation: 8,
1338
+ }}>
1371
1339
  <View style={{ flex: 1 }}>
1372
1340
  <Text style={{
1373
- fontFamily: AppFonts.interBold,
1374
- fontSize: 13,
1375
- lineHeight: 17,
1376
- color: AppColors.primaryBlack,
1377
- }}>
1341
+ fontFamily: AppFonts.interBold,
1342
+ fontSize: 13,
1343
+ lineHeight: 17,
1344
+ color: AppColors.primaryBlack,
1345
+ }}>
1378
1346
  {stagedActiveCount} of {allModules.length} Modules Active
1379
1347
  </Text>
1380
1348
  <Text style={{
1381
- fontFamily: AppFonts.interRegular,
1382
- fontSize: 11,
1383
- lineHeight: 15,
1384
- color: hasUnsavedChanges
1385
- ? AppColors.purple
1386
- : AppColors.grayText,
1387
- marginTop: 1,
1388
- }}>
1349
+ fontFamily: AppFonts.interRegular,
1350
+ fontSize: 11,
1351
+ lineHeight: 15,
1352
+ color: hasUnsavedChanges
1353
+ ? AppColors.purple
1354
+ : AppColors.grayText,
1355
+ marginTop: 1,
1356
+ }}>
1389
1357
  {hasUnsavedChanges
1390
- ? t('settings.unsavedPending')
1391
- : t('settings.allSynchronized')}
1358
+ ? t('settings.unsavedPending')
1359
+ : t('settings.allSynchronized')}
1392
1360
  </Text>
1393
1361
  </View>
1394
1362
 
1395
1363
  <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel="Save Changes" onPress={handleSaveChanges} style={{
1396
- flexDirection: 'row',
1397
- alignItems: 'center',
1398
- gap: 6,
1399
- backgroundColor: hasUnsavedChanges
1400
- ? AppColors.purple
1401
- : `${AppColors.purple}22`,
1402
- paddingHorizontal: 18,
1403
- paddingVertical: 10,
1404
- borderRadius: 10,
1405
- }}>
1364
+ flexDirection: 'row',
1365
+ alignItems: 'center',
1366
+ gap: 6,
1367
+ backgroundColor: hasUnsavedChanges
1368
+ ? AppColors.purple
1369
+ : `${AppColors.purple}22`,
1370
+ paddingHorizontal: 18,
1371
+ paddingVertical: 10,
1372
+ borderRadius: 10,
1373
+ }}>
1406
1374
  <CheckIcon size={14} color={hasUnsavedChanges ? AppColors.white : AppColors.purple}/>
1407
1375
  <Text style={{
1408
- fontFamily: AppFonts.interBold,
1409
- fontSize: 13,
1410
- lineHeight: 17,
1411
- color: hasUnsavedChanges ? AppColors.white : AppColors.purple,
1412
- }}>
1376
+ fontFamily: AppFonts.interBold,
1377
+ fontSize: 13,
1378
+ lineHeight: 17,
1379
+ color: hasUnsavedChanges ? AppColors.white : AppColors.purple,
1380
+ }}>
1413
1381
  {t('settings.saveChanges')}
1414
1382
  </Text>
1415
1383
  </TouchableScale>
1416
1384
  </View>)}
1417
1385
  </View>);
1418
- }
1419
- const goBackToMain = () => {
1420
- animateNextLayout();
1421
- setSettingsPage('main');
1422
- };
1423
- // Helper: settings row with icon + label + optional description
1424
- const renderSettingRow = (opts) => (<View style={{
1425
- paddingVertical: 12,
1426
- borderBottomWidth: opts.isLast ? 0 : 1,
1427
- borderBottomColor: AppColors.dividerColor,
1428
- }}>
1429
- <TouchableScale disabled={!opts.onPress} onPress={opts.onPress} style={{
1430
- flexDirection: 'row',
1431
- alignItems: 'center',
1432
- gap: 12,
1433
- }}>
1434
- <View style={{
1435
- width: 36,
1436
- height: 36,
1437
- borderRadius: 10,
1438
- backgroundColor: AppColors.purpleShade50,
1439
- borderWidth: 1,
1440
- borderColor: `${AppColors.purple}2E`,
1441
- alignItems: 'center',
1442
- justifyContent: 'center',
1443
- }}>
1444
- {opts.icon}
1445
- </View>
1446
- <View style={{ flex: 1 }}>
1447
- <Text style={{
1448
- fontFamily: AppFonts.interBold,
1449
- fontSize: 14,
1450
- lineHeight: 18,
1451
- color: AppColors.primaryBlack,
1452
- }}>
1453
- {opts.label}
1454
- </Text>
1455
- {opts.description ? (<Text style={{
1456
- fontFamily: AppFonts.interRegular,
1457
- fontSize: 11,
1458
- lineHeight: 15,
1459
- color: AppColors.grayText,
1460
- marginTop: 1,
1461
- }}>
1462
- {opts.description}
1463
- </Text>) : null}
1464
- </View>
1465
- {opts.right || null}
1466
- </TouchableScale>
1467
- {opts.picker && (<View style={{
1468
- flexDirection: 'row',
1469
- backgroundColor: AppColors.grayBackground,
1470
- borderRadius: 8,
1471
- padding: 2.5,
1472
- marginTop: 10,
1473
- borderWidth: 1,
1474
- borderColor: AppColors.dividerColor,
1475
- }}>
1476
- {opts.picker.options.map(opt => {
1477
- const isActive = opts.picker.selectedValue === opt;
1478
- return (<TouchableScale key={String(opt)} onPress={() => opts.picker.onSelect(opt)} style={{
1479
- flex: 1,
1480
- paddingVertical: 6,
1481
- alignItems: 'center',
1482
- borderRadius: 6,
1483
- backgroundColor: isActive
1484
- ? AppColors.purple
1485
- : 'transparent',
1486
- }}>
1487
- <Text style={{
1488
- fontFamily: AppFonts.interBold,
1489
- fontSize: 11,
1490
- lineHeight: 14,
1491
- color: isActive ? AppColors.white : AppColors.grayText,
1492
- }}>
1493
- {opts.picker.formatLabel
1494
- ? opts.picker.formatLabel(opt)
1495
- : opt}
1496
- </Text>
1497
- </TouchableScale>);
1498
- })}
1499
- </View>)}
1500
- </View>);
1501
1386
  let content = null;
1502
1387
  let title = '';
1503
1388
  let icon = null;
@@ -1506,7 +1391,7 @@ const SettingsPanel = () => {
1506
1391
  title = t('settings.apis.title');
1507
1392
  icon = <SignalIcon color={AppColors.white} size={16}/>;
1508
1393
  rightInfo = t('settings.apis.total', { count: logs.length });
1509
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1394
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
1510
1395
  <View style={{
1511
1396
  backgroundColor: AppColors.primaryLight,
1512
1397
  padding: 16,
@@ -1518,10 +1403,12 @@ const SettingsPanel = () => {
1518
1403
  icon: <SignalIcon color={AppColors.purple} size={16}/>,
1519
1404
  label: t('settings.apis.maxRequestLogs'),
1520
1405
  description: t('settings.apis.maxRequestLogsDescription'),
1521
- picker: {
1522
- options: [50, 100, 200, 500],
1523
- selectedValue: maxNetworkLogs,
1524
- onSelect: setMaxNetworkLogs,
1406
+ numericInput: {
1407
+ value: maxNetworkLogs,
1408
+ onChange: setMaxNetworkLogs,
1409
+ min: 10,
1410
+ max: 100,
1411
+ placeholder: 'Enter max requests (10-100)',
1525
1412
  },
1526
1413
  isLast: true,
1527
1414
  })}
@@ -1563,13 +1450,14 @@ const SettingsPanel = () => {
1563
1450
  </View>),
1564
1451
  })}
1565
1452
  </View>
1453
+ <View style={{ height: 48 }}/>
1566
1454
  </ScrollView>);
1567
1455
  }
1568
1456
  else if (settingsPage === 'logs') {
1569
1457
  title = t('settings.logs.title');
1570
1458
  icon = <TerminalIcon color={AppColors.white} size={16}/>;
1571
1459
  rightInfo = t('settings.logs.total', { count: consoleLogs.length });
1572
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1460
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
1573
1461
  <View style={{
1574
1462
  backgroundColor: AppColors.primaryLight,
1575
1463
  padding: 16,
@@ -1582,10 +1470,12 @@ const SettingsPanel = () => {
1582
1470
  icon: <TerminalIcon color={AppColors.purple} size={16}/>,
1583
1471
  label: t('settings.logs.maxConsoleLogs'),
1584
1472
  description: t('settings.logs.maxConsoleLogsDescription'),
1585
- picker: {
1586
- options: [100, 200, 500, 1000],
1587
- selectedValue: maxConsoleLogs,
1588
- onSelect: setMaxConsoleLogs,
1473
+ numericInput: {
1474
+ value: maxConsoleLogs,
1475
+ onChange: setMaxConsoleLogs,
1476
+ min: 10,
1477
+ max: 100,
1478
+ placeholder: 'Enter max logs (10-100)',
1589
1479
  },
1590
1480
  })}
1591
1481
  <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
@@ -1649,6 +1539,96 @@ const SettingsPanel = () => {
1649
1539
  })}
1650
1540
  </View>
1651
1541
 
1542
+ {/* Log Deduplication Card */}
1543
+ <View style={{
1544
+ backgroundColor: AppColors.primaryLight,
1545
+ borderRadius: 14,
1546
+ borderWidth: 1,
1547
+ borderColor: AppColors.grayBorderSecondary,
1548
+ overflow: 'hidden',
1549
+ padding: 16,
1550
+ gap: 14,
1551
+ }}>
1552
+ <Text style={{
1553
+ fontFamily: AppFonts.interBold,
1554
+ fontSize: 11,
1555
+ lineHeight: 14,
1556
+ color: AppColors.grayTextWeak,
1557
+ letterSpacing: 0.8,
1558
+ }}>
1559
+ {t('settings.logFiltersDeduplication')}
1560
+ </Text>
1561
+
1562
+ {/* Show Duplicate Logs */}
1563
+ <View style={{
1564
+ flexDirection: 'row',
1565
+ alignItems: 'center',
1566
+ justifyContent: 'space-between',
1567
+ }}>
1568
+ <View style={{
1569
+ flexDirection: 'row',
1570
+ alignItems: 'center',
1571
+ gap: 10,
1572
+ flex: 1,
1573
+ }}>
1574
+ <View style={{
1575
+ width: 32,
1576
+ height: 32,
1577
+ borderRadius: 8,
1578
+ backgroundColor: AppColors.purpleShade50,
1579
+ alignItems: 'center',
1580
+ justifyContent: 'center',
1581
+ }}>
1582
+ <EyeIcon color={AppColors.purple} size={15}/>
1583
+ </View>
1584
+ <View style={{ flex: 1 }}>
1585
+ <Text style={{
1586
+ fontFamily: AppFonts.interBold,
1587
+ fontSize: 13.5,
1588
+ lineHeight: 18,
1589
+ color: AppColors.primaryBlack,
1590
+ }}>
1591
+ {t('settings.general.duplicateLogs')}
1592
+ </Text>
1593
+ <Text style={{
1594
+ fontFamily: AppFonts.interRegular,
1595
+ fontSize: 11,
1596
+ lineHeight: 15,
1597
+ color: AppColors.grayText,
1598
+ marginTop: 1,
1599
+ }}>
1600
+ {t('settings.general.duplicateLogsDescription')}
1601
+ </Text>
1602
+ </View>
1603
+ </View>
1604
+
1605
+ <TouchableScale accessible={true} accessibilityRole="switch" accessibilityLabel="Toggle show duplicate logs" accessibilityState={{ checked: showDuplicateLogs }} onPress={() => setShowDuplicateLogs(prev => !prev)} style={{
1606
+ width: 42,
1607
+ height: 24,
1608
+ borderRadius: 12,
1609
+ backgroundColor: showDuplicateLogs
1610
+ ? AppColors.purple
1611
+ : AppColors.grayBorderSecondary,
1612
+ padding: 2,
1613
+ justifyContent: 'center',
1614
+ alignItems: showDuplicateLogs
1615
+ ? 'flex-end'
1616
+ : 'flex-start',
1617
+ }}>
1618
+ <View style={{
1619
+ width: 20,
1620
+ height: 20,
1621
+ borderRadius: 10,
1622
+ backgroundColor: AppColors.white,
1623
+ shadowColor: AppColors.black,
1624
+ shadowOpacity: 0.18,
1625
+ shadowRadius: 2,
1626
+ shadowOffset: { width: 0, height: 1 },
1627
+ }}/>
1628
+ </TouchableScale>
1629
+ </View>
1630
+ </View>
1631
+
1652
1632
  <View style={{
1653
1633
  backgroundColor: AppColors.primaryLight,
1654
1634
  borderRadius: 14,
@@ -1684,13 +1664,14 @@ const SettingsPanel = () => {
1684
1664
  </View>),
1685
1665
  })}
1686
1666
  </View>
1667
+ <View style={{ height: 48 }}/>
1687
1668
  </ScrollView>);
1688
1669
  }
1689
1670
  else if (settingsPage === 'analytics') {
1690
1671
  title = t('settings.analytics.title');
1691
1672
  icon = <AnalyticsIcon color={AppColors.white} size={16}/>;
1692
1673
  rightInfo = t('settings.analytics.total', { count: analyticsEvents.length });
1693
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1674
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
1694
1675
  <View style={{
1695
1676
  backgroundColor: AppColors.primaryLight,
1696
1677
  padding: 16,
@@ -1702,6 +1683,13 @@ const SettingsPanel = () => {
1702
1683
  icon: <AnalyticsIcon color={AppColors.purple} size={16}/>,
1703
1684
  label: t('settings.analytics.maxAnalyticsEvents'),
1704
1685
  description: t('settings.analytics.maxAnalyticsEventsDescription', { count: analyticsEvents.length }),
1686
+ numericInput: {
1687
+ value: maxAnalyticsEventsLimit,
1688
+ onChange: setMaxAnalyticsEventsLimit,
1689
+ min: 10,
1690
+ max: 75,
1691
+ placeholder: 'Enter max events (10-75)',
1692
+ },
1705
1693
  isLast: true,
1706
1694
  })}
1707
1695
  </View>
@@ -1741,13 +1729,14 @@ const SettingsPanel = () => {
1741
1729
  </View>),
1742
1730
  })}
1743
1731
  </View>
1732
+ <View style={{ height: 48 }}/>
1744
1733
  </ScrollView>);
1745
1734
  }
1746
1735
  else if (settingsPage === 'redux') {
1747
1736
  title = t('settings.redux.title');
1748
1737
  icon = <ReduxIcon color={AppColors.white} size={16}/>;
1749
1738
  rightInfo = t('settings.redux.reducers', { count: Object.keys(reduxState || {}).length });
1750
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1739
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
1751
1740
  <View style={{
1752
1741
  backgroundColor: AppColors.primaryLight,
1753
1742
  padding: 16,
@@ -1829,13 +1818,14 @@ const SettingsPanel = () => {
1829
1818
  </View>),
1830
1819
  })}
1831
1820
  </View>
1821
+ <View style={{ height: 48 }}/>
1832
1822
  </ScrollView>);
1833
1823
  }
1834
1824
  else if (settingsPage === 'crash') {
1835
1825
  title = t('settings.crash.title');
1836
1826
  icon = <CrashIcon color={AppColors.white} size={16}/>;
1837
1827
  rightInfo = `Total: ${crashRecords?.length || 0}`;
1838
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1828
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
1839
1829
  <View style={{
1840
1830
  backgroundColor: AppColors.primaryLight,
1841
1831
  padding: 16,
@@ -1848,10 +1838,12 @@ const SettingsPanel = () => {
1848
1838
  icon: <LayersIcon color={AppColors.purple} size={16}/>,
1849
1839
  label: t('settings.crash.maxCrashLogs'),
1850
1840
  description: t('settings.crash.maxCrashLogsDesc'),
1851
- picker: {
1852
- options: [25, 50, 100, 200],
1853
- selectedValue: maxCrashLogs || 100,
1854
- onSelect: val => setMaxCrashLogs(val),
1841
+ numericInput: {
1842
+ value: maxCrashLogs,
1843
+ onChange: setMaxCrashLogs,
1844
+ min: 5,
1845
+ max: 50,
1846
+ placeholder: 'Enter max crashes (5-50)',
1855
1847
  },
1856
1848
  })}
1857
1849
  <View style={{ height: 1, backgroundColor: AppColors.dividerColor }}/>
@@ -1913,13 +1905,14 @@ const SettingsPanel = () => {
1913
1905
  </View>),
1914
1906
  })}
1915
1907
  </View>
1908
+ <View style={{ height: 48 }}/>
1916
1909
  </ScrollView>);
1917
1910
  }
1918
1911
  else if (settingsPage === 'bundle') {
1919
1912
  title = t('settings.bundle.title');
1920
1913
  icon = <PackageIcon color={AppColors.white} size={16}/>;
1921
1914
  rightInfo = 'Metro';
1922
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1915
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
1923
1916
  <View style={{
1924
1917
  backgroundColor: AppColors.primaryLight,
1925
1918
  padding: 16,
@@ -1996,13 +1989,14 @@ const SettingsPanel = () => {
1996
1989
  </View>),
1997
1990
  })}
1998
1991
  </View>
1992
+ <View style={{ height: 48 }}/>
1999
1993
  </ScrollView>);
2000
1994
  }
2001
1995
  else if (settingsPage === 'performance') {
2002
1996
  title = t('settings.performance.title');
2003
1997
  icon = <PerformanceIcon color={AppColors.white} size={16}/>;
2004
1998
  rightInfo = '60 FPS Target';
2005
- content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
1999
+ content = (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, paddingBottom: 100, gap: 12 }}>
2006
2000
  <View style={{
2007
2001
  backgroundColor: AppColors.primaryLight,
2008
2002
  padding: 16,
@@ -2079,65 +2073,51 @@ const SettingsPanel = () => {
2079
2073
  </View>),
2080
2074
  })}
2081
2075
  </View>
2076
+ <View style={{ height: 48 }}/>
2082
2077
  </ScrollView>);
2083
2078
  }
2079
+ const subPageAnim = useRef(new Animated.Value(0)).current;
2080
+ useEffect(() => {
2081
+ if (settingsPage !== 'main') {
2082
+ subPageAnim.setValue(0);
2083
+ Animated.spring(subPageAnim, {
2084
+ toValue: 1,
2085
+ friction: 8,
2086
+ tension: 65,
2087
+ useNativeDriver: true,
2088
+ }).start();
2089
+ }
2090
+ }, [settingsPage]);
2084
2091
  return (<View style={{
2085
2092
  flex: 1,
2086
2093
  backgroundColor: AppColors.grayBackground,
2087
2094
  }}>
2088
- <LinearGradient colors={[AppColors.purple, AppColors.brandPurple]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={styles.headerGradient}>
2089
- <View style={{ paddingTop: headerTopPadding, width: '100%' }}>
2090
- <View style={[styles.header, { paddingHorizontal: 16, gap: 12 }]}>
2091
- <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={t('settings.back')} onPress={goBackToMain} hitSlop={12} style={{
2092
- padding: 8,
2093
- borderRadius: 10,
2094
- backgroundColor: `${AppColors.white}26`,
2095
- borderWidth: 1,
2096
- borderColor: `${AppColors.white}14`,
2097
- }}>
2098
- <WhiteBackNavigation color={AppColors.white} size={16}/>
2099
- </TouchableScale>
2100
- {icon && (<View style={{
2101
- width: 30,
2102
- height: 30,
2103
- borderRadius: 8,
2104
- backgroundColor: `${AppColors.white}26`,
2105
- alignItems: 'center',
2106
- justifyContent: 'center',
2107
- }}>
2108
- {icon}
2109
- </View>)}
2110
- <View style={{ flex: 1 }}>
2111
- <Text style={{
2112
- fontFamily: AppFonts.interBold,
2113
- fontSize: 17,
2114
- lineHeight: 22,
2115
- color: AppColors.white,
2116
- }}>
2117
- {title}
2118
- </Text>
2119
- </View>
2120
- {rightInfo ? (<View style={{
2121
- backgroundColor: `${AppColors.white}26`,
2122
- paddingHorizontal: 8,
2123
- paddingVertical: 4,
2124
- borderRadius: 8,
2125
- borderWidth: 1,
2126
- borderColor: `${AppColors.white}1A`,
2127
- }}>
2128
- <Text style={{
2129
- fontFamily: AppFonts.interBold,
2130
- fontSize: 11,
2131
- lineHeight: 14,
2132
- color: AppColors.white,
2133
- }}>
2134
- {rightInfo}
2135
- </Text>
2136
- </View>) : null}
2137
- </View>
2138
- </View>
2139
- </LinearGradient>
2140
- {content}
2095
+ {/* ─── Persistent Main Settings (Retains 100% scroll position) ─── */}
2096
+ <View style={[
2097
+ StyleSheet.absoluteFill,
2098
+ settingsPage !== 'main' && { pointerEvents: 'none' },
2099
+ ]}>
2100
+ {renderMainSettingsContent()}
2101
+ </View>
2102
+
2103
+ {/* ─── Sub-module Settings Overlay (Smooth Animated Slide) ─── */}
2104
+ {settingsPage !== 'main' && (<Animated.View style={[
2105
+ StyleSheet.absoluteFill,
2106
+ {
2107
+ backgroundColor: AppColors.grayBackground,
2108
+ opacity: subPageAnim,
2109
+ transform: [
2110
+ {
2111
+ translateX: subPageAnim.interpolate({
2112
+ inputRange: [0, 1],
2113
+ outputRange: [30, 0],
2114
+ }),
2115
+ },
2116
+ ],
2117
+ },
2118
+ ]}>
2119
+ {content}
2120
+ </Animated.View>)}
2141
2121
  </View>);
2142
2122
  };
2143
2123
  export default SettingsPanel;