@umituz/react-native-settings 1.4.1 → 1.6.0
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.
- package/README.md +133 -17
- package/package.json +2 -1
- package/src/index.ts +11 -0
- package/src/presentation/components/DisclaimerSetting.tsx +1 -1
- package/src/presentation/components/SettingItem.tsx +135 -227
- package/src/presentation/components/SettingsFooter.tsx +46 -0
- package/src/presentation/components/SettingsSection.tsx +59 -0
- package/src/presentation/components/UserProfileHeader.tsx +129 -0
- package/src/presentation/screens/AppearanceScreen.tsx +25 -16
- package/src/presentation/screens/LanguageSelectionScreen.tsx +3 -2
- package/src/presentation/screens/SettingsScreen.tsx +182 -285
|
@@ -1,369 +1,266 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings Screen
|
|
3
|
-
*
|
|
4
|
-
* Modern settings with Paper List.Section pattern:
|
|
5
|
-
* - React Native Paper List.Section + List.Subheader
|
|
6
|
-
* - Organized sections (Appearance, General, About & Legal)
|
|
7
|
-
* - Paper Divider for visual separation
|
|
8
|
-
* - Material Design 3 compliance
|
|
9
|
-
* - OFFLINE MODE: No account, premium, feedback, or donation
|
|
10
|
-
* - Optimized spacing for better visual density
|
|
11
|
-
* - Configurable features via SettingsConfig prop
|
|
3
|
+
* Modern settings screen with user profile header and organized sections
|
|
12
4
|
*/
|
|
13
5
|
|
|
14
|
-
import React, { useMemo } from
|
|
15
|
-
import {
|
|
16
|
-
|
|
6
|
+
import React, { useMemo, useState } from "react";
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
ScrollView,
|
|
10
|
+
StatusBar,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
Alert,
|
|
13
|
+
DeviceEventEmitter,
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
16
|
+
import { useNavigation, CommonActions } from "@react-navigation/native";
|
|
17
|
+
import {
|
|
18
|
+
useDesignSystemTheme,
|
|
19
|
+
useAppDesignTokens,
|
|
20
|
+
} from "@umituz/react-native-design-system-theme";
|
|
21
|
+
import { Palette, Bell, Info, FileText } from "lucide-react-native";
|
|
22
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
23
|
+
import { SettingItem } from "../components/SettingItem";
|
|
24
|
+
import { SettingsSection } from "../components/SettingsSection";
|
|
25
|
+
import { SettingsFooter } from "../components/SettingsFooter";
|
|
26
|
+
import { UserProfileHeader } from "../components/UserProfileHeader";
|
|
27
|
+
import { SettingsConfig } from "./types";
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
import { useDesignSystemTheme, useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
20
|
-
import { ScreenLayout, AtomicIcon, AtomicText, SectionHeader, SectionContainer, AtomicDivider } from '@umituz/react-native-design-system';
|
|
21
|
-
import { SettingItem } from '../components/SettingItem';
|
|
22
|
-
import { getLanguageByCode, useLocalization } from '@umituz/react-native-localization';
|
|
23
|
-
import { SettingsConfig } from './types';
|
|
24
|
-
|
|
25
|
-
// Optional notification service - only import if package is available
|
|
29
|
+
// Optional notification service
|
|
26
30
|
let notificationService: any = null;
|
|
27
31
|
try {
|
|
28
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
-
notificationService = require(
|
|
33
|
+
notificationService = require("@umituz/react-native-notifications")
|
|
34
|
+
.notificationService;
|
|
30
35
|
} catch {
|
|
31
|
-
// Package not available
|
|
36
|
+
// Package not available
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
// Optional onboarding store
|
|
39
|
+
// Optional onboarding store
|
|
35
40
|
let useOnboardingStore: any = null;
|
|
36
41
|
try {
|
|
37
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
|
-
const onboardingPackage = require(
|
|
43
|
+
const onboardingPackage = require("@umituz/react-native-onboarding");
|
|
39
44
|
useOnboardingStore = onboardingPackage.useOnboardingStore;
|
|
40
45
|
} catch {
|
|
41
|
-
// Package not available
|
|
46
|
+
// Package not available
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
|
-
* Check if
|
|
50
|
+
* Check if navigation screen exists
|
|
46
51
|
*/
|
|
47
|
-
const hasNavigationScreen = (
|
|
52
|
+
const hasNavigationScreen = (
|
|
53
|
+
navigation: any,
|
|
54
|
+
screenName: string,
|
|
55
|
+
): boolean => {
|
|
48
56
|
try {
|
|
49
57
|
const state = navigation.getState();
|
|
50
58
|
if (!state) return false;
|
|
51
59
|
|
|
52
|
-
// Recursively check all routes in navigation state
|
|
53
60
|
const checkRoutes = (routes: any[]): boolean => {
|
|
54
61
|
if (!routes || !Array.isArray(routes)) return false;
|
|
55
|
-
|
|
62
|
+
|
|
56
63
|
for (const route of routes) {
|
|
57
|
-
if (route.name === screenName)
|
|
64
|
+
if (route.name === screenName) return true;
|
|
65
|
+
if (route.state?.routes && checkRoutes(route.state.routes)) {
|
|
58
66
|
return true;
|
|
59
67
|
}
|
|
60
|
-
// Check nested navigators
|
|
61
|
-
if (route.state?.routes) {
|
|
62
|
-
if (checkRoutes(route.state.routes)) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
68
|
}
|
|
67
69
|
return false;
|
|
68
70
|
};
|
|
69
71
|
|
|
70
72
|
return checkRoutes(state.routes || []);
|
|
71
73
|
} catch {
|
|
72
|
-
// If we can't check navigation state, assume it's not available
|
|
73
74
|
return false;
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
export interface SettingsScreenProps {
|
|
78
|
-
/**
|
|
79
|
-
* Configuration for which settings features to show
|
|
80
|
-
* @default { appearance: 'auto', notifications: 'auto', about: 'auto', legal: 'auto' }
|
|
81
|
-
*/
|
|
82
79
|
config?: SettingsConfig;
|
|
80
|
+
/** Show user profile header */
|
|
81
|
+
showUserProfile?: boolean;
|
|
82
|
+
/** User profile props */
|
|
83
|
+
userProfile?: {
|
|
84
|
+
displayName?: string;
|
|
85
|
+
userId?: string;
|
|
86
|
+
isGuest?: boolean;
|
|
87
|
+
avatarUrl?: string;
|
|
88
|
+
accountSettingsRoute?: string;
|
|
89
|
+
onPress?: () => void;
|
|
90
|
+
};
|
|
91
|
+
/** Show footer with version */
|
|
92
|
+
showFooter?: boolean;
|
|
93
|
+
/** Custom footer text */
|
|
94
|
+
footerText?: string;
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
|
86
|
-
config = {}
|
|
97
|
+
export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
|
98
|
+
config = {},
|
|
99
|
+
showUserProfile = false,
|
|
100
|
+
userProfile,
|
|
101
|
+
showFooter = true,
|
|
102
|
+
footerText,
|
|
87
103
|
}) => {
|
|
88
104
|
const navigation = useNavigation();
|
|
89
|
-
// Only read themeMode, no theme logic here - theme logic belongs in theme package
|
|
90
105
|
const { themeMode } = useDesignSystemTheme();
|
|
91
106
|
const tokens = useAppDesignTokens();
|
|
92
107
|
const insets = useSafeAreaInsets();
|
|
93
|
-
const {
|
|
108
|
+
const { t } = useLocalization();
|
|
109
|
+
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
|
94
110
|
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const themeDisplay = themeMode === 'dark' ? t('settings.darkMode') : t('settings.lightMode');
|
|
111
|
+
const isDark = themeMode === "dark";
|
|
112
|
+
const colors = tokens.colors;
|
|
98
113
|
|
|
99
|
-
// Determine which features should be shown
|
|
100
114
|
const features = useMemo(() => {
|
|
101
|
-
const appearanceConfig = config
|
|
102
|
-
const notificationsConfig = config
|
|
103
|
-
const aboutConfig = config
|
|
104
|
-
const legalConfig = config
|
|
115
|
+
const appearanceConfig = config?.appearance ?? "auto";
|
|
116
|
+
const notificationsConfig = config?.notifications ?? "auto";
|
|
117
|
+
const aboutConfig = config?.about ?? "auto";
|
|
118
|
+
const legalConfig = config?.legal ?? "auto";
|
|
105
119
|
|
|
106
120
|
return {
|
|
107
|
-
appearance:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
appearance:
|
|
122
|
+
appearanceConfig === true ||
|
|
123
|
+
(appearanceConfig === "auto" &&
|
|
124
|
+
hasNavigationScreen(navigation, "Appearance")),
|
|
125
|
+
notifications:
|
|
126
|
+
notificationsConfig === true ||
|
|
127
|
+
(notificationsConfig === "auto" &&
|
|
128
|
+
notificationService !== null &&
|
|
129
|
+
hasNavigationScreen(navigation, "Notifications")),
|
|
130
|
+
about:
|
|
131
|
+
aboutConfig === true ||
|
|
132
|
+
(aboutConfig === "auto" && hasNavigationScreen(navigation, "About")),
|
|
133
|
+
legal:
|
|
134
|
+
legalConfig === true ||
|
|
135
|
+
(legalConfig === "auto" && hasNavigationScreen(navigation, "Legal")),
|
|
117
136
|
};
|
|
118
137
|
}, [config, navigation]);
|
|
119
138
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const handleAboutPress = () => {
|
|
125
|
-
navigation.navigate('About' as never);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const handleLegalPress = () => {
|
|
129
|
-
navigation.navigate('Legal' as never);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const handleNotificationsPress = async () => {
|
|
133
|
-
if (notificationService) {
|
|
139
|
+
const handleNotificationsToggle = async (value: boolean) => {
|
|
140
|
+
if (notificationService && !value) {
|
|
134
141
|
const hasPermissions = await notificationService.hasPermissions();
|
|
135
142
|
if (!hasPermissions) {
|
|
136
143
|
await notificationService.requestPermissions();
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
|
-
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const handleClose = () => {
|
|
143
|
-
// Try to go back in current navigator first
|
|
144
|
-
if (navigation.canGoBack()) {
|
|
145
|
-
navigation.goBack();
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// If we can't go back in current navigator, try to find parent navigator
|
|
150
|
-
// This handles the case where Settings is the root screen of a stack
|
|
151
|
-
let parent = navigation.getParent();
|
|
152
|
-
let depth = 0;
|
|
153
|
-
const maxDepth = 5; // Safety limit to prevent infinite loops
|
|
154
|
-
|
|
155
|
-
// Traverse up the navigation tree to find a navigator that can go back
|
|
156
|
-
while (parent && depth < maxDepth) {
|
|
157
|
-
if (parent.canGoBack()) {
|
|
158
|
-
parent.goBack();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
parent = parent.getParent();
|
|
162
|
-
depth++;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// If no parent can go back, try using CommonActions to go back
|
|
166
|
-
// This is a fallback for edge cases
|
|
167
|
-
try {
|
|
168
|
-
navigation.dispatch(CommonActions.goBack());
|
|
169
|
-
} catch (error) {
|
|
170
|
-
// If all else fails, silently fail (close button just won't work)
|
|
171
|
-
/* eslint-disable-next-line no-console */
|
|
172
|
-
if (__DEV__) {
|
|
173
|
-
console.warn('[SettingsScreen] Could not navigate back:', error);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
146
|
+
setNotificationsEnabled(value);
|
|
176
147
|
};
|
|
177
148
|
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const onboardingStore = useOnboardingStore.getState();
|
|
186
|
-
// Reset onboarding state
|
|
187
|
-
await onboardingStore.reset();
|
|
188
|
-
// Emit event to trigger navigation to onboarding
|
|
189
|
-
DeviceEventEmitter.emit('reset-onboarding');
|
|
190
|
-
// Close settings first - try parent navigator if current can't go back
|
|
191
|
-
if (navigation.canGoBack()) {
|
|
192
|
-
navigation.goBack();
|
|
193
|
-
} else {
|
|
194
|
-
// Try to find parent navigator that can go back
|
|
195
|
-
let parent = navigation.getParent();
|
|
196
|
-
let depth = 0;
|
|
197
|
-
const maxDepth = 5;
|
|
198
|
-
|
|
199
|
-
while (parent && depth < maxDepth) {
|
|
200
|
-
if (parent.canGoBack()) {
|
|
201
|
-
parent.goBack();
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
parent = parent.getParent();
|
|
205
|
-
depth++;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Fallback to CommonActions
|
|
209
|
-
if (!parent || depth >= maxDepth) {
|
|
210
|
-
try {
|
|
211
|
-
navigation.dispatch(CommonActions.goBack());
|
|
212
|
-
} catch (error) {
|
|
213
|
-
/* eslint-disable-next-line no-console */
|
|
214
|
-
if (__DEV__) {
|
|
215
|
-
console.warn('[SettingsScreen] Could not navigate back:', error);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
149
|
+
const handleNotificationsPress = async () => {
|
|
150
|
+
if (notificationService) {
|
|
151
|
+
const hasPermissions = await notificationService.hasPermissions();
|
|
152
|
+
if (!hasPermissions) {
|
|
153
|
+
await notificationService.requestPermissions();
|
|
219
154
|
}
|
|
220
|
-
// Small delay to ensure navigation completes
|
|
221
|
-
setTimeout(() => {
|
|
222
|
-
DeviceEventEmitter.emit('show-onboarding');
|
|
223
|
-
}, 100);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
Alert.alert(
|
|
226
|
-
'Error',
|
|
227
|
-
'Failed to show onboarding. Please try again.',
|
|
228
|
-
[{ text: 'OK' }],
|
|
229
|
-
);
|
|
230
155
|
}
|
|
156
|
+
navigation.navigate("Notifications" as never);
|
|
231
157
|
};
|
|
232
158
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.log('[SettingsScreen] Navigation state:', navigation.getState());
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Check if any features are enabled
|
|
242
|
-
const hasAnyFeatures = features.appearance || features.notifications || features.about || features.legal;
|
|
159
|
+
const hasAnyFeatures =
|
|
160
|
+
features.appearance ||
|
|
161
|
+
features.notifications ||
|
|
162
|
+
features.about ||
|
|
163
|
+
features.legal;
|
|
243
164
|
|
|
244
165
|
return (
|
|
245
|
-
<
|
|
246
|
-
{
|
|
247
|
-
<View style={[
|
|
248
|
-
styles.header,
|
|
249
|
-
{
|
|
250
|
-
borderBottomColor: tokens.colors.borderLight,
|
|
251
|
-
backgroundColor: tokens.colors.surface,
|
|
252
|
-
paddingTop: insets.top,
|
|
253
|
-
}
|
|
254
|
-
]}>
|
|
255
|
-
<AtomicText type="headlineLarge" style={{ color: tokens.colors.textPrimary, flex: 1 }}>
|
|
256
|
-
{t('navigation.settings') || 'Settings'}
|
|
257
|
-
</AtomicText>
|
|
258
|
-
<TouchableOpacity
|
|
259
|
-
onPress={handleClose}
|
|
260
|
-
style={styles.closeButton}
|
|
261
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
262
|
-
testID="close-settings-button"
|
|
263
|
-
>
|
|
264
|
-
<AtomicIcon name="X" size="lg" color="primary" />
|
|
265
|
-
</TouchableOpacity>
|
|
266
|
-
</View>
|
|
166
|
+
<View style={[styles.container, { backgroundColor: colors.backgroundPrimary }]}>
|
|
167
|
+
<StatusBar barStyle={isDark ? "light-content" : "dark-content"} />
|
|
267
168
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
169
|
+
<ScrollView
|
|
170
|
+
style={styles.scrollView}
|
|
171
|
+
contentContainerStyle={[
|
|
172
|
+
styles.scrollContent,
|
|
173
|
+
{
|
|
174
|
+
paddingTop: insets.top + 16,
|
|
175
|
+
paddingBottom: 100,
|
|
176
|
+
},
|
|
177
|
+
]}
|
|
178
|
+
showsVerticalScrollIndicator={false}
|
|
179
|
+
>
|
|
180
|
+
{showUserProfile && (
|
|
181
|
+
<UserProfileHeader
|
|
182
|
+
displayName={userProfile?.displayName}
|
|
183
|
+
userId={userProfile?.userId}
|
|
184
|
+
isGuest={userProfile?.isGuest}
|
|
185
|
+
avatarUrl={userProfile?.avatarUrl}
|
|
186
|
+
accountSettingsRoute={userProfile?.accountSettingsRoute}
|
|
187
|
+
onPress={userProfile?.onPress}
|
|
279
188
|
/>
|
|
280
|
-
|
|
281
|
-
)}
|
|
189
|
+
)}
|
|
282
190
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
<SectionContainer>
|
|
286
|
-
<SectionHeader>{t('settings.sections.general')}</SectionHeader>
|
|
287
|
-
<SettingItem
|
|
288
|
-
icon="Bell"
|
|
289
|
-
iconGradient={((tokens.colors as any).settingGradients?.notifications as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
|
|
290
|
-
title={t('settings.notifications.title')}
|
|
291
|
-
description={t('settings.notifications.description')}
|
|
292
|
-
onPress={handleNotificationsPress}
|
|
293
|
-
testID="notifications-button"
|
|
294
|
-
/>
|
|
295
|
-
</SectionContainer>
|
|
296
|
-
)}
|
|
297
|
-
|
|
298
|
-
{/* Development/Test: Show Onboarding */}
|
|
299
|
-
{__DEV__ && useOnboardingStore && (
|
|
300
|
-
<SectionContainer>
|
|
301
|
-
<SectionHeader>Development</SectionHeader>
|
|
302
|
-
<SettingItem
|
|
303
|
-
icon="Play"
|
|
304
|
-
iconGradient={((tokens.colors as any).settingGradients?.info as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
|
|
305
|
-
title="Show Onboarding (Dev)"
|
|
306
|
-
description="Navigate to onboarding screen"
|
|
307
|
-
onPress={handleShowOnboarding}
|
|
308
|
-
testID="show-onboarding-button"
|
|
309
|
-
/>
|
|
310
|
-
</SectionContainer>
|
|
311
|
-
)}
|
|
312
|
-
|
|
313
|
-
{/* About & Legal Section */}
|
|
314
|
-
{(features.about || features.legal) && (
|
|
315
|
-
<SectionContainer>
|
|
316
|
-
<SectionHeader>{t('settings.sections.about')}</SectionHeader>
|
|
317
|
-
{features.about && (
|
|
191
|
+
{features.appearance && (
|
|
192
|
+
<SettingsSection title={t("settings.sections.app.title")}>
|
|
318
193
|
<SettingItem
|
|
319
|
-
icon=
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
onPress={handleAboutPress}
|
|
324
|
-
testID="about-button"
|
|
194
|
+
icon={Palette}
|
|
195
|
+
title={t("settings.appearance.title")}
|
|
196
|
+
value={t("settings.appearance.description")}
|
|
197
|
+
onPress={() => navigation.navigate("Appearance" as never)}
|
|
325
198
|
/>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
199
|
+
</SettingsSection>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
{features.notifications && (
|
|
203
|
+
<SettingsSection title={t("settings.sections.general")}>
|
|
329
204
|
<SettingItem
|
|
330
|
-
icon=
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
205
|
+
icon={Bell}
|
|
206
|
+
title={t("settings.notifications.title")}
|
|
207
|
+
showSwitch={true}
|
|
208
|
+
switchValue={notificationsEnabled}
|
|
209
|
+
onSwitchChange={handleNotificationsToggle}
|
|
210
|
+
isLast={true}
|
|
336
211
|
/>
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
)}
|
|
212
|
+
</SettingsSection>
|
|
213
|
+
)}
|
|
340
214
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
215
|
+
{(features.about || features.legal) && (
|
|
216
|
+
<SettingsSection title={t("settings.sections.about")}>
|
|
217
|
+
{features.about && (
|
|
218
|
+
<SettingItem
|
|
219
|
+
icon={Info}
|
|
220
|
+
title={t("settings.about.title")}
|
|
221
|
+
value={t("settings.about.description")}
|
|
222
|
+
onPress={() => navigation.navigate("About" as never)}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
225
|
+
{features.legal && (
|
|
226
|
+
<SettingItem
|
|
227
|
+
icon={FileText}
|
|
228
|
+
title={t("settings.legal.title")}
|
|
229
|
+
value={t("settings.legal.description")}
|
|
230
|
+
onPress={() => navigation.navigate("Legal" as never)}
|
|
231
|
+
isLast={true}
|
|
232
|
+
/>
|
|
233
|
+
)}
|
|
234
|
+
</SettingsSection>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{!hasAnyFeatures && (
|
|
238
|
+
<View style={styles.emptyContainer}>
|
|
239
|
+
<SettingsSection
|
|
240
|
+
title={t("settings.noOptionsAvailable") || "No settings available"}
|
|
241
|
+
>
|
|
242
|
+
<View />
|
|
243
|
+
</SettingsSection>
|
|
244
|
+
</View>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{showFooter && <SettingsFooter versionText={footerText} />}
|
|
248
|
+
</ScrollView>
|
|
249
|
+
</View>
|
|
350
250
|
);
|
|
351
251
|
};
|
|
352
252
|
|
|
353
253
|
const styles = StyleSheet.create({
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
paddingBottom: 12,
|
|
360
|
-
paddingTop: 12,
|
|
361
|
-
borderBottomWidth: 1,
|
|
362
|
-
zIndex: 1000,
|
|
254
|
+
container: {
|
|
255
|
+
flex: 1,
|
|
256
|
+
},
|
|
257
|
+
scrollView: {
|
|
258
|
+
flex: 1,
|
|
363
259
|
},
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
260
|
+
scrollContent: {
|
|
261
|
+
flexGrow: 1,
|
|
262
|
+
},
|
|
263
|
+
emptyContainer: {
|
|
264
|
+
paddingVertical: 24,
|
|
367
265
|
},
|
|
368
266
|
});
|
|
369
|
-
|