@umituz/react-native-settings 1.6.2 → 1.8.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/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/presentation/screens/SettingsScreen.tsx +42 -148
- package/src/presentation/screens/components/AboutLegalSection.tsx +66 -0
- package/src/presentation/screens/components/AppearanceSection.tsx +39 -0
- package/src/presentation/screens/components/NotificationsSection.tsx +92 -0
- package/src/presentation/screens/components/index.ts +9 -0
- package/src/presentation/screens/hooks/useFeatureDetection.ts +100 -0
- package/src/presentation/screens/types.ts +221 -11
- package/src/presentation/screens/utils/normalizeConfig.ts +89 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { LanguageSelectionScreen } from './presentation/screens/LanguageSelectio
|
|
|
41
41
|
// PRESENTATION LAYER - Types
|
|
42
42
|
// =============================================================================
|
|
43
43
|
|
|
44
|
-
export type { SettingsConfig } from './presentation/screens/types';
|
|
44
|
+
export type { SettingsConfig, CustomSettingsSection } from './presentation/screens/types';
|
|
45
45
|
|
|
46
46
|
// =============================================================================
|
|
47
47
|
// PRESENTATION LAYER - Components
|
|
@@ -1,82 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings Screen
|
|
3
|
-
*
|
|
3
|
+
* Presentation layer - Composition only, no business logic
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React
|
|
7
|
-
import {
|
|
8
|
-
View,
|
|
9
|
-
ScrollView,
|
|
10
|
-
StatusBar,
|
|
11
|
-
StyleSheet,
|
|
12
|
-
Alert,
|
|
13
|
-
DeviceEventEmitter,
|
|
14
|
-
} from "react-native";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, ScrollView, StatusBar, StyleSheet } from "react-native";
|
|
15
8
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
16
|
-
import { useNavigation
|
|
9
|
+
import { useNavigation } from "@react-navigation/native";
|
|
17
10
|
import {
|
|
18
11
|
useDesignSystemTheme,
|
|
19
12
|
useAppDesignTokens,
|
|
20
13
|
} from "@umituz/react-native-design-system-theme";
|
|
21
|
-
import { Palette, Bell, Info, FileText } from "lucide-react-native";
|
|
22
14
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
23
|
-
import { SettingItem } from "../components/SettingItem";
|
|
24
|
-
import { SettingsSection } from "../components/SettingsSection";
|
|
25
15
|
import { SettingsFooter } from "../components/SettingsFooter";
|
|
26
16
|
import { UserProfileHeader } from "../components/UserProfileHeader";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.notificationService;
|
|
35
|
-
} catch {
|
|
36
|
-
// Package not available
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Optional onboarding store
|
|
40
|
-
let useOnboardingStore: any = null;
|
|
41
|
-
try {
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
-
const onboardingPackage = require("@umituz/react-native-onboarding");
|
|
44
|
-
useOnboardingStore = onboardingPackage.useOnboardingStore;
|
|
45
|
-
} catch {
|
|
46
|
-
// Package not available
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Check if navigation screen exists
|
|
51
|
-
*/
|
|
52
|
-
const hasNavigationScreen = (
|
|
53
|
-
navigation: any,
|
|
54
|
-
screenName: string,
|
|
55
|
-
): boolean => {
|
|
56
|
-
try {
|
|
57
|
-
const state = navigation.getState();
|
|
58
|
-
if (!state) return false;
|
|
59
|
-
|
|
60
|
-
const checkRoutes = (routes: any[]): boolean => {
|
|
61
|
-
if (!routes || !Array.isArray(routes)) return false;
|
|
62
|
-
|
|
63
|
-
for (const route of routes) {
|
|
64
|
-
if (route.name === screenName) return true;
|
|
65
|
-
if (route.state?.routes && checkRoutes(route.state.routes)) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
return checkRoutes(state.routes || []);
|
|
73
|
-
} catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
17
|
+
import { SettingsSection } from "../components/SettingsSection";
|
|
18
|
+
import { AppearanceSection } from "./components/AppearanceSection";
|
|
19
|
+
import { NotificationsSection } from "./components/NotificationsSection";
|
|
20
|
+
import { AboutLegalSection } from "./components/AboutLegalSection";
|
|
21
|
+
import { normalizeSettingsConfig } from "./utils/normalizeConfig";
|
|
22
|
+
import { useFeatureDetection } from "./hooks/useFeatureDetection";
|
|
23
|
+
import type { CustomSettingsSection } from "./types";
|
|
77
24
|
|
|
78
25
|
export interface SettingsScreenProps {
|
|
79
|
-
config?:
|
|
26
|
+
config?: any;
|
|
80
27
|
/** Show user profile header */
|
|
81
28
|
showUserProfile?: boolean;
|
|
82
29
|
/** User profile props */
|
|
@@ -92,6 +39,8 @@ export interface SettingsScreenProps {
|
|
|
92
39
|
showFooter?: boolean;
|
|
93
40
|
/** Custom footer text */
|
|
94
41
|
footerText?: string;
|
|
42
|
+
/** Custom sections to render */
|
|
43
|
+
customSections?: CustomSettingsSection[];
|
|
95
44
|
}
|
|
96
45
|
|
|
97
46
|
export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
|
@@ -100,67 +49,26 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
|
|
100
49
|
userProfile,
|
|
101
50
|
showFooter = true,
|
|
102
51
|
footerText,
|
|
52
|
+
customSections = [],
|
|
103
53
|
}) => {
|
|
104
54
|
const navigation = useNavigation();
|
|
105
55
|
const { themeMode } = useDesignSystemTheme();
|
|
106
56
|
const tokens = useAppDesignTokens();
|
|
107
57
|
const insets = useSafeAreaInsets();
|
|
108
58
|
const { t } = useLocalization();
|
|
109
|
-
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
|
110
59
|
|
|
111
60
|
const isDark = themeMode === "dark";
|
|
112
61
|
const colors = tokens.colors;
|
|
113
62
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const notificationsConfig = config?.notifications ?? "auto";
|
|
117
|
-
const aboutConfig = config?.about ?? "auto";
|
|
118
|
-
const legalConfig = config?.legal ?? "auto";
|
|
119
|
-
|
|
120
|
-
return {
|
|
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")),
|
|
136
|
-
};
|
|
137
|
-
}, [config, navigation]);
|
|
138
|
-
|
|
139
|
-
const handleNotificationsToggle = async (value: boolean) => {
|
|
140
|
-
if (notificationService && !value) {
|
|
141
|
-
const hasPermissions = await notificationService.hasPermissions();
|
|
142
|
-
if (!hasPermissions) {
|
|
143
|
-
await notificationService.requestPermissions();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
setNotificationsEnabled(value);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const handleNotificationsPress = async () => {
|
|
150
|
-
if (notificationService) {
|
|
151
|
-
const hasPermissions = await notificationService.hasPermissions();
|
|
152
|
-
if (!hasPermissions) {
|
|
153
|
-
await notificationService.requestPermissions();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
navigation.navigate("Notifications" as never);
|
|
157
|
-
};
|
|
63
|
+
const normalizedConfig = normalizeSettingsConfig(config);
|
|
64
|
+
const features = useFeatureDetection(normalizedConfig, navigation);
|
|
158
65
|
|
|
159
66
|
const hasAnyFeatures =
|
|
160
67
|
features.appearance ||
|
|
161
68
|
features.notifications ||
|
|
162
69
|
features.about ||
|
|
163
|
-
features.legal
|
|
70
|
+
features.legal ||
|
|
71
|
+
customSections.length > 0;
|
|
164
72
|
|
|
165
73
|
return (
|
|
166
74
|
<View style={[styles.container, { backgroundColor: colors.backgroundPrimary }]}>
|
|
@@ -189,49 +97,35 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
|
|
189
97
|
)}
|
|
190
98
|
|
|
191
99
|
{features.appearance && (
|
|
192
|
-
<
|
|
193
|
-
<SettingItem
|
|
194
|
-
icon={Palette}
|
|
195
|
-
title={t("settings.appearance.title")}
|
|
196
|
-
value={t("settings.appearance.description")}
|
|
197
|
-
onPress={() => navigation.navigate("Appearance" as never)}
|
|
198
|
-
/>
|
|
199
|
-
</SettingsSection>
|
|
100
|
+
<AppearanceSection config={normalizedConfig.appearance.config} />
|
|
200
101
|
)}
|
|
201
102
|
|
|
202
103
|
{features.notifications && (
|
|
203
|
-
<
|
|
204
|
-
<SettingItem
|
|
205
|
-
icon={Bell}
|
|
206
|
-
title={t("settings.notifications.title")}
|
|
207
|
-
showSwitch={true}
|
|
208
|
-
switchValue={notificationsEnabled}
|
|
209
|
-
onSwitchChange={handleNotificationsToggle}
|
|
210
|
-
isLast={true}
|
|
211
|
-
/>
|
|
212
|
-
</SettingsSection>
|
|
104
|
+
<NotificationsSection config={normalizedConfig.notifications.config} />
|
|
213
105
|
)}
|
|
214
106
|
|
|
215
107
|
{(features.about || features.legal) && (
|
|
216
|
-
<
|
|
217
|
-
{features.about
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
108
|
+
<AboutLegalSection
|
|
109
|
+
showAbout={features.about}
|
|
110
|
+
showLegal={features.legal}
|
|
111
|
+
aboutConfig={normalizedConfig.about.config}
|
|
112
|
+
legalConfig={normalizedConfig.legal.config}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{customSections && customSections.length > 0 && (
|
|
117
|
+
<>
|
|
118
|
+
{customSections
|
|
119
|
+
.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
|
|
120
|
+
.map((section, index) => (
|
|
121
|
+
<SettingsSection
|
|
122
|
+
key={section.id || `custom-${index}`}
|
|
123
|
+
title={section.title}
|
|
124
|
+
>
|
|
125
|
+
{section.content}
|
|
126
|
+
</SettingsSection>
|
|
127
|
+
))}
|
|
128
|
+
</>
|
|
235
129
|
)}
|
|
236
130
|
|
|
237
131
|
{!hasAnyFeatures && (
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* About & Legal Section Component
|
|
3
|
+
* Single Responsibility: Render about and legal settings section
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Info, FileText } from "lucide-react-native";
|
|
8
|
+
import { useNavigation } from "@react-navigation/native";
|
|
9
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
|
+
import { SettingItem } from "../../components/SettingItem";
|
|
11
|
+
import { SettingsSection } from "../../components/SettingsSection";
|
|
12
|
+
import type { AboutConfig, LegalConfig } from "../types";
|
|
13
|
+
|
|
14
|
+
interface AboutLegalSectionProps {
|
|
15
|
+
showAbout: boolean;
|
|
16
|
+
showLegal: boolean;
|
|
17
|
+
aboutConfig?: AboutConfig;
|
|
18
|
+
legalConfig?: LegalConfig;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const AboutLegalSection: React.FC<AboutLegalSectionProps> = ({
|
|
22
|
+
showAbout,
|
|
23
|
+
showLegal,
|
|
24
|
+
aboutConfig,
|
|
25
|
+
legalConfig,
|
|
26
|
+
}) => {
|
|
27
|
+
const navigation = useNavigation();
|
|
28
|
+
const { t } = useLocalization();
|
|
29
|
+
|
|
30
|
+
if (!showAbout && !showLegal) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const aboutRoute = aboutConfig?.route || "About";
|
|
35
|
+
const aboutTitle = aboutConfig?.title || t("settings.about.title");
|
|
36
|
+
const aboutDescription =
|
|
37
|
+
aboutConfig?.description || t("settings.about.description");
|
|
38
|
+
|
|
39
|
+
const legalRoute = legalConfig?.route || "Legal";
|
|
40
|
+
const legalTitle = legalConfig?.title || t("settings.legal.title");
|
|
41
|
+
const legalDescription =
|
|
42
|
+
legalConfig?.description || t("settings.legal.description");
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<SettingsSection title={t("settings.sections.about")}>
|
|
46
|
+
{showAbout && (
|
|
47
|
+
<SettingItem
|
|
48
|
+
icon={Info}
|
|
49
|
+
title={aboutTitle}
|
|
50
|
+
value={aboutDescription}
|
|
51
|
+
onPress={() => navigation.navigate(aboutRoute as never)}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
{showLegal && (
|
|
55
|
+
<SettingItem
|
|
56
|
+
icon={FileText}
|
|
57
|
+
title={legalTitle}
|
|
58
|
+
value={legalDescription}
|
|
59
|
+
onPress={() => navigation.navigate(legalRoute as never)}
|
|
60
|
+
isLast={true}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
</SettingsSection>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appearance Section Component
|
|
3
|
+
* Single Responsibility: Render appearance settings section
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Palette } from "lucide-react-native";
|
|
8
|
+
import { useNavigation } from "@react-navigation/native";
|
|
9
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
|
+
import { SettingItem } from "../../components/SettingItem";
|
|
11
|
+
import { SettingsSection } from "../../components/SettingsSection";
|
|
12
|
+
import type { AppearanceConfig } from "../types";
|
|
13
|
+
|
|
14
|
+
interface AppearanceSectionProps {
|
|
15
|
+
config?: AppearanceConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
|
|
19
|
+
config,
|
|
20
|
+
}) => {
|
|
21
|
+
const navigation = useNavigation();
|
|
22
|
+
const { t } = useLocalization();
|
|
23
|
+
|
|
24
|
+
const route = config?.route || "Appearance";
|
|
25
|
+
const title = config?.title || t("settings.appearance.title");
|
|
26
|
+
const description = config?.description || t("settings.appearance.description");
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<SettingsSection title={t("settings.sections.app.title")}>
|
|
30
|
+
<SettingItem
|
|
31
|
+
icon={Palette}
|
|
32
|
+
title={title}
|
|
33
|
+
value={description}
|
|
34
|
+
onPress={() => navigation.navigate(route as never)}
|
|
35
|
+
/>
|
|
36
|
+
</SettingsSection>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications Section Component
|
|
3
|
+
* Single Responsibility: Render notifications settings section
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useEffect } from "react";
|
|
7
|
+
import { Bell } from "lucide-react-native";
|
|
8
|
+
import { useNavigation } from "@react-navigation/native";
|
|
9
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
|
+
import { SettingItem } from "../../components/SettingItem";
|
|
11
|
+
import { SettingsSection } from "../../components/SettingsSection";
|
|
12
|
+
import type { NotificationsConfig } from "../types";
|
|
13
|
+
|
|
14
|
+
// Optional notification service
|
|
15
|
+
let notificationService: any = null;
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
notificationService = require("@umituz/react-native-notifications")
|
|
19
|
+
.notificationService;
|
|
20
|
+
} catch {
|
|
21
|
+
// Package not available
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface NotificationsSectionProps {
|
|
25
|
+
config?: NotificationsConfig;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const NotificationsSection: React.FC<NotificationsSectionProps> = ({
|
|
29
|
+
config,
|
|
30
|
+
}) => {
|
|
31
|
+
const navigation = useNavigation();
|
|
32
|
+
const { t } = useLocalization();
|
|
33
|
+
const [notificationsEnabled, setNotificationsEnabled] = useState(
|
|
34
|
+
config?.initialValue ?? true,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (config?.initialValue !== undefined) {
|
|
39
|
+
setNotificationsEnabled(config.initialValue);
|
|
40
|
+
}
|
|
41
|
+
}, [config?.initialValue]);
|
|
42
|
+
|
|
43
|
+
const handleToggle = async (value: boolean) => {
|
|
44
|
+
if (notificationService && !value) {
|
|
45
|
+
const hasPermissions = await notificationService.hasPermissions();
|
|
46
|
+
if (!hasPermissions) {
|
|
47
|
+
await notificationService.requestPermissions();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setNotificationsEnabled(value);
|
|
52
|
+
config?.onToggleChange?.(value);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handlePress = async () => {
|
|
56
|
+
if (notificationService) {
|
|
57
|
+
const hasPermissions = await notificationService.hasPermissions();
|
|
58
|
+
if (!hasPermissions) {
|
|
59
|
+
await notificationService.requestPermissions();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
navigation.navigate((config?.route || "Notifications") as never);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const title = config?.title || t("settings.notifications.title");
|
|
66
|
+
const description = config?.description || t("settings.notifications.description");
|
|
67
|
+
const showToggle = config?.showToggle ?? true;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<SettingsSection title={t("settings.sections.general")}>
|
|
71
|
+
{showToggle ? (
|
|
72
|
+
<SettingItem
|
|
73
|
+
icon={Bell}
|
|
74
|
+
title={title}
|
|
75
|
+
showSwitch={true}
|
|
76
|
+
switchValue={notificationsEnabled}
|
|
77
|
+
onSwitchChange={handleToggle}
|
|
78
|
+
isLast={true}
|
|
79
|
+
/>
|
|
80
|
+
) : (
|
|
81
|
+
<SettingItem
|
|
82
|
+
icon={Bell}
|
|
83
|
+
title={title}
|
|
84
|
+
value={description}
|
|
85
|
+
onPress={handlePress}
|
|
86
|
+
isLast={true}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
</SettingsSection>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Screen Components
|
|
3
|
+
* Barrel export for all section components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { AppearanceSection } from "./AppearanceSection";
|
|
7
|
+
export { NotificationsSection } from "./NotificationsSection";
|
|
8
|
+
export { AboutLegalSection } from "./AboutLegalSection";
|
|
9
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Detection Hook
|
|
3
|
+
* Single Responsibility: Detect which features should be shown
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import type { NormalizedConfig } from "../utils/normalizeConfig";
|
|
8
|
+
|
|
9
|
+
// Optional notification service
|
|
10
|
+
let notificationService: any = null;
|
|
11
|
+
try {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
|
+
notificationService = require("@umituz/react-native-notifications")
|
|
14
|
+
.notificationService;
|
|
15
|
+
} catch {
|
|
16
|
+
// Package not available
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if navigation screen exists
|
|
21
|
+
*/
|
|
22
|
+
function hasNavigationScreen(
|
|
23
|
+
navigation: any,
|
|
24
|
+
screenName: string,
|
|
25
|
+
): boolean {
|
|
26
|
+
try {
|
|
27
|
+
const state = navigation.getState();
|
|
28
|
+
if (!state) return false;
|
|
29
|
+
|
|
30
|
+
const checkRoutes = (routes: any[]): boolean => {
|
|
31
|
+
if (!routes || !Array.isArray(routes)) return false;
|
|
32
|
+
|
|
33
|
+
for (const route of routes) {
|
|
34
|
+
if (route.name === screenName) return true;
|
|
35
|
+
if (route.state?.routes && checkRoutes(route.state.routes)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return checkRoutes(state.routes || []);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hook to detect which features should be shown
|
|
50
|
+
*/
|
|
51
|
+
export function useFeatureDetection(
|
|
52
|
+
normalizedConfig: NormalizedConfig,
|
|
53
|
+
navigation: any,
|
|
54
|
+
) {
|
|
55
|
+
return useMemo(() => {
|
|
56
|
+
const { appearance, notifications, about, legal, account, support, developer } =
|
|
57
|
+
normalizedConfig;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
appearance:
|
|
61
|
+
appearance.enabled &&
|
|
62
|
+
(appearance.config?.enabled === true ||
|
|
63
|
+
(appearance.config?.enabled !== false &&
|
|
64
|
+
hasNavigationScreen(
|
|
65
|
+
navigation,
|
|
66
|
+
appearance.config?.route || "Appearance",
|
|
67
|
+
))),
|
|
68
|
+
notifications:
|
|
69
|
+
notifications.enabled &&
|
|
70
|
+
(notifications.config?.enabled === true ||
|
|
71
|
+
(notifications.config?.enabled !== false &&
|
|
72
|
+
notificationService !== null &&
|
|
73
|
+
hasNavigationScreen(
|
|
74
|
+
navigation,
|
|
75
|
+
notifications.config?.route || "Notifications",
|
|
76
|
+
))),
|
|
77
|
+
about:
|
|
78
|
+
about.enabled &&
|
|
79
|
+
(about.config?.enabled === true ||
|
|
80
|
+
(about.config?.enabled !== false &&
|
|
81
|
+
hasNavigationScreen(navigation, about.config?.route || "About"))),
|
|
82
|
+
legal:
|
|
83
|
+
legal.enabled &&
|
|
84
|
+
(legal.config?.enabled === true ||
|
|
85
|
+
(legal.config?.enabled !== false &&
|
|
86
|
+
hasNavigationScreen(navigation, legal.config?.route || "Legal"))),
|
|
87
|
+
account:
|
|
88
|
+
account.enabled &&
|
|
89
|
+
(account.config?.enabled === true ||
|
|
90
|
+
(account.config?.enabled !== false &&
|
|
91
|
+
hasNavigationScreen(
|
|
92
|
+
navigation,
|
|
93
|
+
account.config?.route || "AccountSettings",
|
|
94
|
+
))),
|
|
95
|
+
support: support.enabled,
|
|
96
|
+
developer: developer.enabled && __DEV__,
|
|
97
|
+
};
|
|
98
|
+
}, [normalizedConfig, navigation]);
|
|
99
|
+
}
|
|
100
|
+
|
|
@@ -1,35 +1,245 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Settings Configuration
|
|
2
|
+
* Settings Configuration Types
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Comprehensive configuration system for SettingsScreen
|
|
5
|
+
* Supports built-in features and custom sections
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ComponentType, ReactNode } from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Feature visibility configuration
|
|
6
12
|
* - true: Always show (if navigation screen exists)
|
|
7
13
|
* - false: Never show
|
|
8
14
|
* - 'auto': Automatically detect (check if navigation screen exists and package is available)
|
|
9
15
|
*/
|
|
16
|
+
export type FeatureVisibility = boolean | "auto";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Appearance Settings Configuration
|
|
20
|
+
*/
|
|
21
|
+
export interface AppearanceConfig {
|
|
22
|
+
/** Show appearance section */
|
|
23
|
+
enabled?: FeatureVisibility;
|
|
24
|
+
/** Custom navigation route for appearance screen */
|
|
25
|
+
route?: string;
|
|
26
|
+
/** Show language selection */
|
|
27
|
+
showLanguage?: boolean;
|
|
28
|
+
/** Show theme toggle */
|
|
29
|
+
showTheme?: boolean;
|
|
30
|
+
/** Custom appearance title */
|
|
31
|
+
title?: string;
|
|
32
|
+
/** Custom appearance description */
|
|
33
|
+
description?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Notifications Settings Configuration
|
|
38
|
+
*/
|
|
39
|
+
export interface NotificationsConfig {
|
|
40
|
+
/** Show notifications section */
|
|
41
|
+
enabled?: FeatureVisibility;
|
|
42
|
+
/** Custom navigation route for notifications screen */
|
|
43
|
+
route?: string;
|
|
44
|
+
/** Show notifications toggle switch */
|
|
45
|
+
showToggle?: boolean;
|
|
46
|
+
/** Initial notifications state */
|
|
47
|
+
initialValue?: boolean;
|
|
48
|
+
/** Custom toggle handler */
|
|
49
|
+
onToggleChange?: (value: boolean) => void;
|
|
50
|
+
/** Custom notifications title */
|
|
51
|
+
title?: string;
|
|
52
|
+
/** Custom notifications description */
|
|
53
|
+
description?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* About Settings Configuration
|
|
58
|
+
*/
|
|
59
|
+
export interface AboutConfig {
|
|
60
|
+
/** Show about section */
|
|
61
|
+
enabled?: FeatureVisibility;
|
|
62
|
+
/** Custom navigation route for about screen */
|
|
63
|
+
route?: string;
|
|
64
|
+
/** Custom about title */
|
|
65
|
+
title?: string;
|
|
66
|
+
/** Custom about description */
|
|
67
|
+
description?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Legal Settings Configuration
|
|
72
|
+
*/
|
|
73
|
+
export interface LegalConfig {
|
|
74
|
+
/** Show legal section */
|
|
75
|
+
enabled?: FeatureVisibility;
|
|
76
|
+
/** Custom navigation route for legal screen */
|
|
77
|
+
route?: string;
|
|
78
|
+
/** Custom legal title */
|
|
79
|
+
title?: string;
|
|
80
|
+
/** Custom legal description */
|
|
81
|
+
description?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Account Settings Configuration
|
|
86
|
+
*/
|
|
87
|
+
export interface AccountConfig {
|
|
88
|
+
/** Show account section */
|
|
89
|
+
enabled?: FeatureVisibility;
|
|
90
|
+
/** Custom navigation route for account screen */
|
|
91
|
+
route?: string;
|
|
92
|
+
/** Custom account title */
|
|
93
|
+
title?: string;
|
|
94
|
+
/** Custom account description */
|
|
95
|
+
description?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Support Settings Configuration
|
|
100
|
+
*/
|
|
101
|
+
export interface SupportConfig {
|
|
102
|
+
/** Show support section */
|
|
103
|
+
enabled?: FeatureVisibility;
|
|
104
|
+
/** Support items configuration */
|
|
105
|
+
items?: {
|
|
106
|
+
/** Live support configuration */
|
|
107
|
+
liveSupport?: {
|
|
108
|
+
enabled?: boolean;
|
|
109
|
+
route?: string;
|
|
110
|
+
title?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
};
|
|
113
|
+
/** Help support configuration */
|
|
114
|
+
helpSupport?: {
|
|
115
|
+
enabled?: boolean;
|
|
116
|
+
route?: string;
|
|
117
|
+
title?: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/** Custom support section title */
|
|
122
|
+
title?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Developer Settings Configuration
|
|
127
|
+
*/
|
|
128
|
+
export interface DeveloperConfig {
|
|
129
|
+
/** Show developer section (only in __DEV__ mode) */
|
|
130
|
+
enabled?: boolean;
|
|
131
|
+
/** Developer settings items */
|
|
132
|
+
items?: Array<{
|
|
133
|
+
title: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
route?: string;
|
|
136
|
+
onPress?: () => void;
|
|
137
|
+
icon?: ComponentType<{ size?: number; color?: string }>;
|
|
138
|
+
iconColor?: string;
|
|
139
|
+
titleColor?: string;
|
|
140
|
+
}>;
|
|
141
|
+
/** Custom developer section title */
|
|
142
|
+
title?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Main Settings Configuration
|
|
147
|
+
*
|
|
148
|
+
* Controls which settings features are visible in the SettingsScreen.
|
|
149
|
+
* Each feature can be configured with:
|
|
150
|
+
* - Simple: boolean | 'auto' (quick setup)
|
|
151
|
+
* - Advanced: Detailed config object (full control)
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* // Simple configuration
|
|
155
|
+
* const config: SettingsConfig = {
|
|
156
|
+
* appearance: true,
|
|
157
|
+
* notifications: 'auto',
|
|
158
|
+
* about: false,
|
|
159
|
+
* };
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* // Advanced configuration
|
|
163
|
+
* const config: SettingsConfig = {
|
|
164
|
+
* appearance: {
|
|
165
|
+
* enabled: true,
|
|
166
|
+
* route: 'CustomAppearance',
|
|
167
|
+
* showLanguage: true,
|
|
168
|
+
* showTheme: true,
|
|
169
|
+
* },
|
|
170
|
+
* notifications: {
|
|
171
|
+
* enabled: 'auto',
|
|
172
|
+
* showToggle: true,
|
|
173
|
+
* initialValue: false,
|
|
174
|
+
* onToggleChange: (value) => console.log(value),
|
|
175
|
+
* },
|
|
176
|
+
* support: {
|
|
177
|
+
* enabled: true,
|
|
178
|
+
* items: {
|
|
179
|
+
* liveSupport: {
|
|
180
|
+
* enabled: true,
|
|
181
|
+
* route: 'ChatSupport',
|
|
182
|
+
* title: 'Live Chat',
|
|
183
|
+
* },
|
|
184
|
+
* },
|
|
185
|
+
* },
|
|
186
|
+
* };
|
|
187
|
+
*/
|
|
10
188
|
export interface SettingsConfig {
|
|
11
189
|
/**
|
|
12
|
-
*
|
|
190
|
+
* Appearance settings (Theme & Language)
|
|
13
191
|
* @default 'auto'
|
|
14
192
|
*/
|
|
15
|
-
appearance?:
|
|
193
|
+
appearance?: FeatureVisibility | AppearanceConfig;
|
|
16
194
|
|
|
17
195
|
/**
|
|
18
|
-
*
|
|
196
|
+
* Notifications settings
|
|
19
197
|
* @default 'auto'
|
|
20
198
|
*/
|
|
21
|
-
notifications?:
|
|
199
|
+
notifications?: FeatureVisibility | NotificationsConfig;
|
|
22
200
|
|
|
23
201
|
/**
|
|
24
|
-
*
|
|
202
|
+
* About settings
|
|
25
203
|
* @default 'auto'
|
|
26
204
|
*/
|
|
27
|
-
about?:
|
|
205
|
+
about?: FeatureVisibility | AboutConfig;
|
|
28
206
|
|
|
29
207
|
/**
|
|
30
|
-
*
|
|
208
|
+
* Legal settings (Terms, Privacy Policy)
|
|
31
209
|
* @default 'auto'
|
|
32
210
|
*/
|
|
33
|
-
legal?:
|
|
211
|
+
legal?: FeatureVisibility | LegalConfig;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Account settings
|
|
215
|
+
* @default false
|
|
216
|
+
*/
|
|
217
|
+
account?: FeatureVisibility | AccountConfig;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Support settings
|
|
221
|
+
* @default false
|
|
222
|
+
*/
|
|
223
|
+
support?: FeatureVisibility | SupportConfig;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Developer settings (only shown in __DEV__ mode)
|
|
227
|
+
* @default false
|
|
228
|
+
*/
|
|
229
|
+
developer?: boolean | DeveloperConfig;
|
|
34
230
|
}
|
|
35
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Custom Settings Section
|
|
234
|
+
* Allows apps to add custom sections to the settings screen
|
|
235
|
+
*/
|
|
236
|
+
export interface CustomSettingsSection {
|
|
237
|
+
/** Section title */
|
|
238
|
+
title: string;
|
|
239
|
+
/** Section content (React nodes) */
|
|
240
|
+
content: ReactNode;
|
|
241
|
+
/** Section order (lower = higher in list) */
|
|
242
|
+
order?: number;
|
|
243
|
+
/** Section ID for identification */
|
|
244
|
+
id?: string;
|
|
245
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Normalization Utilities
|
|
3
|
+
* Single Responsibility: Normalize config values to consistent format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
FeatureVisibility,
|
|
8
|
+
AppearanceConfig,
|
|
9
|
+
NotificationsConfig,
|
|
10
|
+
AboutConfig,
|
|
11
|
+
LegalConfig,
|
|
12
|
+
AccountConfig,
|
|
13
|
+
SupportConfig,
|
|
14
|
+
DeveloperConfig,
|
|
15
|
+
} from "../types";
|
|
16
|
+
|
|
17
|
+
export interface NormalizedConfig {
|
|
18
|
+
appearance: {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
config?: AppearanceConfig;
|
|
21
|
+
};
|
|
22
|
+
notifications: {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
config?: NotificationsConfig;
|
|
25
|
+
};
|
|
26
|
+
about: {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
config?: AboutConfig;
|
|
29
|
+
};
|
|
30
|
+
legal: {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
config?: LegalConfig;
|
|
33
|
+
};
|
|
34
|
+
account: {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
config?: AccountConfig;
|
|
37
|
+
};
|
|
38
|
+
support: {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
config?: SupportConfig;
|
|
41
|
+
};
|
|
42
|
+
developer: {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
config?: DeveloperConfig;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalize a config value to enabled boolean and optional config object
|
|
50
|
+
*/
|
|
51
|
+
function normalizeConfigValue<T>(
|
|
52
|
+
value: FeatureVisibility | T | undefined,
|
|
53
|
+
defaultValue: FeatureVisibility,
|
|
54
|
+
): { enabled: boolean; config?: T } {
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
return { enabled: defaultValue === true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof value === "boolean" || value === "auto") {
|
|
60
|
+
return { enabled: value === true };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// It's a config object
|
|
64
|
+
const config = value as T;
|
|
65
|
+
const enabled =
|
|
66
|
+
(config as { enabled?: FeatureVisibility })?.enabled ?? defaultValue;
|
|
67
|
+
return {
|
|
68
|
+
enabled: enabled === true,
|
|
69
|
+
config,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Normalize entire SettingsConfig to consistent format
|
|
75
|
+
*/
|
|
76
|
+
export function normalizeSettingsConfig(
|
|
77
|
+
config: any,
|
|
78
|
+
): NormalizedConfig {
|
|
79
|
+
return {
|
|
80
|
+
appearance: normalizeConfigValue(config?.appearance, "auto"),
|
|
81
|
+
notifications: normalizeConfigValue(config?.notifications, "auto"),
|
|
82
|
+
about: normalizeConfigValue(config?.about, "auto"),
|
|
83
|
+
legal: normalizeConfigValue(config?.legal, "auto"),
|
|
84
|
+
account: normalizeConfigValue(config?.account, false),
|
|
85
|
+
support: normalizeConfigValue(config?.support, false),
|
|
86
|
+
developer: normalizeConfigValue(config?.developer, false),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|