@umituz/react-native-settings 4.20.5 → 4.20.7
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/domains/about/utils/index.ts +2 -8
- package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +2 -8
- package/src/domains/appearance/presentation/components/ColorPicker.tsx +3 -6
- package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +4 -8
- package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +2 -4
- package/src/domains/faqs/presentation/components/FAQSection.tsx +4 -4
- package/src/domains/feedback/presentation/components/SupportSection.tsx +2 -15
- package/src/domains/legal/domain/services/ContentValidationService.ts +26 -34
- package/src/domains/legal/domain/services/UrlHandlerService.ts +2 -16
- package/src/domains/legal/presentation/components/LegalDocumentsList.tsx +2 -8
- package/src/domains/legal/presentation/components/LegalLinks.tsx +4 -8
- package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +2 -8
- package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +2 -8
- package/src/domains/video-tutorials/presentation/components/VideoTutorialCard.tsx +2 -2
- package/src/domains/video-tutorials/presentation/screens/VideoTutorialsScreen.tsx +1 -8
- package/src/presentation/components/SettingItem.tsx +1 -1
- package/src/presentation/components/SettingsErrorBoundary.tsx +4 -4
- package/src/presentation/screens/components/SettingsContent.tsx +7 -0
- package/src/presentation/screens/components/sections/SubscriptionSettingsSection.tsx +49 -0
- package/src/presentation/screens/hooks/useFeatureDetection.ts +2 -0
- package/src/presentation/screens/types/FeatureConfig.ts +19 -0
- package/src/presentation/screens/types/SettingsConfig.ts +7 -0
- package/src/presentation/screens/utils/normalizeConfig.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.20.
|
|
3
|
+
"version": "4.20.7",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, FAQs, and rating",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -125,10 +125,7 @@ export const openUrl = async (url: string): Promise<boolean> => {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
return false;
|
|
128
|
-
} catch
|
|
129
|
-
if (__DEV__) {
|
|
130
|
-
console.error('Failed to open URL:', error);
|
|
131
|
-
}
|
|
128
|
+
} catch {
|
|
132
129
|
return false;
|
|
133
130
|
}
|
|
134
131
|
};
|
|
@@ -151,10 +148,7 @@ export const sendEmail = async (email: string, subject?: string): Promise<boolea
|
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
return false;
|
|
154
|
-
} catch
|
|
155
|
-
if (__DEV__) {
|
|
156
|
-
console.error('Failed to send email:', error);
|
|
157
|
-
}
|
|
151
|
+
} catch {
|
|
158
152
|
return false;
|
|
159
153
|
}
|
|
160
154
|
};
|
|
@@ -29,10 +29,7 @@ export const getSystemTheme = (): ThemeMode | null => {
|
|
|
29
29
|
// On native platforms, use Appearance API
|
|
30
30
|
const colorScheme = Appearance.getColorScheme();
|
|
31
31
|
return colorScheme === 'dark' ? 'dark' : 'light';
|
|
32
|
-
} catch
|
|
33
|
-
if (__DEV__) {
|
|
34
|
-
console.warn('[getSystemTheme] Failed to detect system theme:', error);
|
|
35
|
-
}
|
|
32
|
+
} catch {
|
|
36
33
|
return null;
|
|
37
34
|
}
|
|
38
35
|
};
|
|
@@ -70,10 +67,7 @@ export const addSystemThemeListener = (
|
|
|
70
67
|
return () => {
|
|
71
68
|
subscription?.remove();
|
|
72
69
|
};
|
|
73
|
-
} catch
|
|
74
|
-
if (__DEV__) {
|
|
75
|
-
console.warn('[addSystemThemeListener] Failed to add listener:', error);
|
|
76
|
-
}
|
|
70
|
+
} catch {
|
|
77
71
|
return () => { }; // Return empty cleanup function
|
|
78
72
|
}
|
|
79
73
|
};
|
|
@@ -39,12 +39,9 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|
|
39
39
|
try {
|
|
40
40
|
// Prevent unnecessary updates if color hasn't changed
|
|
41
41
|
if (value === color) return;
|
|
42
|
-
|
|
43
42
|
onValueChange(color);
|
|
44
|
-
} catch
|
|
45
|
-
|
|
46
|
-
console.error("[ColorPicker] Failed to change color:", error);
|
|
47
|
-
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Silent error handling
|
|
48
45
|
}
|
|
49
46
|
}, [value, onValueChange]);
|
|
50
47
|
|
|
@@ -65,7 +62,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|
|
65
62
|
activeOpacity={0.8} // Performance optimization
|
|
66
63
|
>
|
|
67
64
|
{isSelected && (
|
|
68
|
-
<AtomicIcon name="checkmark" size="sm" customColor=
|
|
65
|
+
<AtomicIcon name="checkmark" size="sm" customColor={tokens.colors.textInverse} />
|
|
69
66
|
)}
|
|
70
67
|
</TouchableOpacity>
|
|
71
68
|
);
|
|
@@ -94,10 +94,8 @@ export const CustomColorsSection: React.FC<CustomColorsSectionProps> = ({
|
|
|
94
94
|
const handleColorChange = useCallback((key: keyof CustomThemeColors, color: string) => {
|
|
95
95
|
try {
|
|
96
96
|
onColorChange(key, color);
|
|
97
|
-
} catch
|
|
98
|
-
|
|
99
|
-
console.error("[CustomColorsSection] Failed to change color:", error);
|
|
100
|
-
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Silent error handling
|
|
101
99
|
}
|
|
102
100
|
}, [onColorChange]);
|
|
103
101
|
|
|
@@ -105,10 +103,8 @@ export const CustomColorsSection: React.FC<CustomColorsSectionProps> = ({
|
|
|
105
103
|
const handleResetColors = useCallback(() => {
|
|
106
104
|
try {
|
|
107
105
|
onResetColors();
|
|
108
|
-
} catch
|
|
109
|
-
|
|
110
|
-
console.error("[CustomColorsSection] Failed to reset colors:", error);
|
|
111
|
-
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Silent error handling
|
|
112
108
|
}
|
|
113
109
|
}, [onResetColors]);
|
|
114
110
|
|
|
@@ -42,10 +42,8 @@ export const ThemeModeSection: React.FC<ThemeModeSectionProps> = ({
|
|
|
42
42
|
const handleThemeSelect = useCallback((mode: ThemeMode) => {
|
|
43
43
|
try {
|
|
44
44
|
onThemeSelect(mode);
|
|
45
|
-
} catch
|
|
46
|
-
|
|
47
|
-
console.error("[ThemeModeSection] Failed to select theme:", error);
|
|
48
|
-
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Silent error handling
|
|
49
47
|
}
|
|
50
48
|
}, [onThemeSelect]);
|
|
51
49
|
|
|
@@ -32,16 +32,16 @@ export const FAQSection: React.FC<FAQSectionProps> = ({
|
|
|
32
32
|
renderSection,
|
|
33
33
|
renderItem,
|
|
34
34
|
}) => {
|
|
35
|
-
if (!config.enabled) return null;
|
|
35
|
+
if (!config.enabled || !config.title || !config.description) return null;
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
38
|
<>
|
|
39
39
|
{renderSection({
|
|
40
|
-
title: config.title
|
|
40
|
+
title: config.title,
|
|
41
41
|
children: renderItem({
|
|
42
|
-
title: config.description
|
|
42
|
+
title: config.description,
|
|
43
43
|
icon: 'help-circle',
|
|
44
|
-
onPress: config.onPress || (() =>
|
|
44
|
+
onPress: config.onPress || (() => {}),
|
|
45
45
|
isLast: true,
|
|
46
46
|
}),
|
|
47
47
|
})}
|
|
@@ -65,17 +65,12 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
|
|
|
65
65
|
try {
|
|
66
66
|
await feedbackConfig.config.onSubmit(data);
|
|
67
67
|
setModalVisible(false);
|
|
68
|
-
} catch
|
|
69
|
-
|
|
70
|
-
console.error("Feedback submission error:", error);
|
|
71
|
-
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Silent error handling
|
|
72
70
|
} finally {
|
|
73
71
|
setIsSubmitting(false);
|
|
74
72
|
}
|
|
75
73
|
} else {
|
|
76
|
-
if (__DEV__) {
|
|
77
|
-
console.warn("No onSubmit handler provided for Feedback");
|
|
78
|
-
}
|
|
79
74
|
setModalVisible(false);
|
|
80
75
|
}
|
|
81
76
|
};
|
|
@@ -91,14 +86,6 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
|
|
|
91
86
|
const supported = await Linking.canOpenURL(config.storeUrl);
|
|
92
87
|
if (supported) {
|
|
93
88
|
await Linking.openURL(config.storeUrl);
|
|
94
|
-
} else {
|
|
95
|
-
if (__DEV__) {
|
|
96
|
-
console.warn("Cannot open store URL:", config.storeUrl);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
if (__DEV__) {
|
|
101
|
-
console.warn("No storeUrl or onRate provided for Rating");
|
|
102
89
|
}
|
|
103
90
|
}
|
|
104
91
|
}, [ratingConfig.config]);
|
|
@@ -19,48 +19,40 @@ export class ContentValidationService {
|
|
|
19
19
|
* Validate screen content requirements
|
|
20
20
|
*/
|
|
21
21
|
static validateScreenContent(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
_content: string | undefined,
|
|
23
|
+
_url: string | undefined,
|
|
24
|
+
_title: string | undefined,
|
|
25
|
+
_viewOnlineText: string | undefined,
|
|
26
|
+
_openText: string | undefined,
|
|
27
|
+
_screenName: string
|
|
28
28
|
): void {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (url && !viewOnlineText) {
|
|
37
|
-
console.warn(`${screenName}: viewOnlineText is required when url is provided`);
|
|
38
|
-
}
|
|
39
|
-
if (url && !openText) {
|
|
40
|
-
console.warn(`${screenName}: openText is required when url is provided`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
29
|
+
// Silent validation - no console output
|
|
30
|
+
void _content;
|
|
31
|
+
void _url;
|
|
32
|
+
void _title;
|
|
33
|
+
void _viewOnlineText;
|
|
34
|
+
void _openText;
|
|
35
|
+
void _screenName;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
/**
|
|
46
39
|
* Validate legal links requirements
|
|
47
40
|
*/
|
|
48
41
|
static validateLegalLinks(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
_privacyPolicyUrl: string | undefined,
|
|
43
|
+
_termsOfServiceUrl: string | undefined,
|
|
44
|
+
_privacyText: string | undefined,
|
|
45
|
+
_termsText: string | undefined,
|
|
46
|
+
_onPrivacyPress: (() => void) | undefined,
|
|
47
|
+
_onTermsPress: (() => void) | undefined
|
|
55
48
|
): void {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
49
|
+
// Silent validation - no console output
|
|
50
|
+
void _privacyPolicyUrl;
|
|
51
|
+
void _termsOfServiceUrl;
|
|
52
|
+
void _privacyText;
|
|
53
|
+
void _termsText;
|
|
54
|
+
void _onPrivacyPress;
|
|
55
|
+
void _onTermsPress;
|
|
64
56
|
}
|
|
65
57
|
|
|
66
58
|
/**
|
|
@@ -51,14 +51,7 @@ export class UrlHandlerService {
|
|
|
51
51
|
* Open URL in external browser with performance optimizations
|
|
52
52
|
*/
|
|
53
53
|
static async openUrl(url: string): Promise<void> {
|
|
54
|
-
if (__DEV__) {
|
|
55
|
-
console.log('UrlHandlerService: Opening URL', { url });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
54
|
if (!this.isValidUrl(url)) {
|
|
59
|
-
if (__DEV__) {
|
|
60
|
-
console.warn('UrlHandlerService: Invalid URL provided', { url });
|
|
61
|
-
}
|
|
62
55
|
return;
|
|
63
56
|
}
|
|
64
57
|
|
|
@@ -77,16 +70,9 @@ export class UrlHandlerService {
|
|
|
77
70
|
|
|
78
71
|
if (canOpen) {
|
|
79
72
|
await Linking.openURL(url);
|
|
80
|
-
} else {
|
|
81
|
-
if (__DEV__) {
|
|
82
|
-
console.warn('UrlHandlerService: Cannot open URL', { url });
|
|
83
|
-
}
|
|
84
73
|
}
|
|
85
|
-
} catch
|
|
86
|
-
|
|
87
|
-
console.error('UrlHandlerService: Error opening URL', { url, error });
|
|
88
|
-
}
|
|
89
|
-
// Don't throw error to prevent app crashes, just log it
|
|
74
|
+
} catch {
|
|
75
|
+
// Silent error handling to prevent app crashes
|
|
90
76
|
}
|
|
91
77
|
}
|
|
92
78
|
|
|
@@ -40,19 +40,13 @@ export const LegalDocumentsList: React.FC<LegalDocumentsListProps> = React.memo(
|
|
|
40
40
|
const tokens = useAppDesignTokens();
|
|
41
41
|
|
|
42
42
|
const handleEulaPress = React.useCallback(async () => {
|
|
43
|
-
if (__DEV__) {
|
|
44
|
-
console.log('LegalDocumentsList: EULA pressed', { eulaUrl });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
43
|
if (onEulaPress) {
|
|
48
44
|
onEulaPress();
|
|
49
45
|
} else if (eulaUrl) {
|
|
50
46
|
try {
|
|
51
47
|
await UrlHandlerService.openUrl(eulaUrl);
|
|
52
|
-
} catch
|
|
53
|
-
|
|
54
|
-
console.error('LegalDocumentsList: Error opening EULA URL', error);
|
|
55
|
-
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Silent error handling
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
}, [onEulaPress, eulaUrl]);
|
|
@@ -70,10 +70,8 @@ export const LegalLinks: React.FC<LegalLinksProps> = React.memo(
|
|
|
70
70
|
} else if (privacyPolicyUrl) {
|
|
71
71
|
try {
|
|
72
72
|
await UrlHandlerService.openUrl(privacyPolicyUrl);
|
|
73
|
-
} catch
|
|
74
|
-
|
|
75
|
-
console.error('LegalLinks: Error opening privacy policy URL', error);
|
|
76
|
-
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Silent error handling
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
}, [onPrivacyPress, privacyPolicyUrl]);
|
|
@@ -84,10 +82,8 @@ export const LegalLinks: React.FC<LegalLinksProps> = React.memo(
|
|
|
84
82
|
} else if (termsOfServiceUrl) {
|
|
85
83
|
try {
|
|
86
84
|
await UrlHandlerService.openUrl(termsOfServiceUrl);
|
|
87
|
-
} catch
|
|
88
|
-
|
|
89
|
-
console.error('LegalLinks: Error opening terms of service URL', error);
|
|
90
|
-
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Silent error handling
|
|
91
87
|
}
|
|
92
88
|
}
|
|
93
89
|
}, [onTermsPress, termsOfServiceUrl]);
|
|
@@ -81,19 +81,13 @@ export const PrivacyPolicyScreen: React.FC<PrivacyPolicyScreenProps> = React.mem
|
|
|
81
81
|
|
|
82
82
|
// Memoize URL press handler to prevent child re-renders
|
|
83
83
|
const handleUrlPress = React.useCallback(async () => {
|
|
84
|
-
if (__DEV__) {
|
|
85
|
-
console.log('PrivacyPolicyScreen: URL pressed', { url });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
84
|
if (onUrlPress) {
|
|
89
85
|
onUrlPress();
|
|
90
86
|
} else if (url) {
|
|
91
87
|
try {
|
|
92
88
|
await UrlHandlerService.openUrl(url);
|
|
93
|
-
} catch
|
|
94
|
-
|
|
95
|
-
console.error('PrivacyPolicyScreen: Error opening URL', error);
|
|
96
|
-
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Silent error handling
|
|
97
91
|
}
|
|
98
92
|
}
|
|
99
93
|
}, [onUrlPress, url]);
|
|
@@ -81,19 +81,13 @@ export const TermsOfServiceScreen: React.FC<TermsOfServiceScreenProps> = React.m
|
|
|
81
81
|
|
|
82
82
|
// Memoize URL press handler to prevent child re-renders
|
|
83
83
|
const handleUrlPress = React.useCallback(async () => {
|
|
84
|
-
if (__DEV__) {
|
|
85
|
-
console.log('TermsOfServiceScreen: URL pressed', { url });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
84
|
if (onUrlPress) {
|
|
89
85
|
onUrlPress();
|
|
90
86
|
} else if (url) {
|
|
91
87
|
try {
|
|
92
88
|
await UrlHandlerService.openUrl(url);
|
|
93
|
-
} catch
|
|
94
|
-
|
|
95
|
-
console.error('TermsOfServiceScreen: Error opening URL', error);
|
|
96
|
-
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Silent error handling
|
|
97
91
|
}
|
|
98
92
|
}
|
|
99
93
|
}, [onUrlPress, url]);
|
|
@@ -61,7 +61,7 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
|
|
|
61
61
|
{ backgroundColor: tokens.colors.primary },
|
|
62
62
|
]}
|
|
63
63
|
>
|
|
64
|
-
<Text style={[styles.featuredText, { color:
|
|
64
|
+
<Text style={[styles.featuredText, { color: tokens.colors.onPrimary }]}>
|
|
65
65
|
Featured
|
|
66
66
|
</Text>
|
|
67
67
|
</View>
|
|
@@ -140,7 +140,7 @@ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
|
140
140
|
borderRadius: 4,
|
|
141
141
|
},
|
|
142
142
|
durationText: {
|
|
143
|
-
color:
|
|
143
|
+
color: tokens.colors.textInverse,
|
|
144
144
|
fontSize: 12,
|
|
145
145
|
fontWeight: "500",
|
|
146
146
|
},
|
|
@@ -85,14 +85,7 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
|
|
|
85
85
|
|
|
86
86
|
const handleTutorialPress = React.useCallback(
|
|
87
87
|
(tutorialId: string) => {
|
|
88
|
-
|
|
89
|
-
onTutorialPress(tutorialId);
|
|
90
|
-
} else if (__DEV__) {
|
|
91
|
-
// eslint-disable-next-line no-console
|
|
92
|
-
console.log("VideoTutorialsScreen: No onTutorialPress handler", {
|
|
93
|
-
tutorialId,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
88
|
+
onTutorialPress?.(tutorialId);
|
|
96
89
|
},
|
|
97
90
|
[onTutorialPress],
|
|
98
91
|
);
|
|
@@ -96,7 +96,7 @@ export const SettingItem: React.FC<SettingItemProps> = ({
|
|
|
96
96
|
false: tokens.colors.surfaceVariant,
|
|
97
97
|
true: tokens.colors.primary
|
|
98
98
|
}}
|
|
99
|
-
thumbColor=
|
|
99
|
+
thumbColor={tokens.colors.surface}
|
|
100
100
|
ios_backgroundColor={tokens.colors.surfaceVariant}
|
|
101
101
|
disabled={disabled}
|
|
102
102
|
/>
|
|
@@ -31,10 +31,10 @@ export class SettingsErrorBoundary extends Component<Props, State> {
|
|
|
31
31
|
return { hasError: true, error };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
override componentDidCatch(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
override componentDidCatch(_error: Error, _errorInfo: React.ErrorInfo) {
|
|
35
|
+
// Silent error handling - error already captured in state
|
|
36
|
+
void _error;
|
|
37
|
+
void _errorInfo;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
override render() {
|
|
@@ -11,6 +11,7 @@ import { ProfileSectionLoader } from "./sections/ProfileSectionLoader";
|
|
|
11
11
|
import { FeatureSettingsSection } from "./sections/FeatureSettingsSection";
|
|
12
12
|
import { IdentitySettingsSection } from "./sections/IdentitySettingsSection";
|
|
13
13
|
import { SupportSettingsSection } from "./sections/SupportSettingsSection";
|
|
14
|
+
import { SubscriptionSettingsSection } from "./sections/SubscriptionSettingsSection";
|
|
14
15
|
import { CustomSettingsList } from "./sections/CustomSettingsList";
|
|
15
16
|
import type { NormalizedConfig } from "../utils/normalizeConfig";
|
|
16
17
|
import type { CustomSettingsSection } from "../types";
|
|
@@ -29,6 +30,7 @@ interface SettingsContentProps {
|
|
|
29
30
|
feedback: boolean;
|
|
30
31
|
rating: boolean;
|
|
31
32
|
faqs: boolean;
|
|
33
|
+
subscription: boolean;
|
|
32
34
|
};
|
|
33
35
|
showUserProfile?: boolean;
|
|
34
36
|
userProfile?: any;
|
|
@@ -67,6 +69,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
67
69
|
features.feedback ||
|
|
68
70
|
features.rating ||
|
|
69
71
|
features.faqs ||
|
|
72
|
+
features.subscription ||
|
|
70
73
|
customSections.length > 0,
|
|
71
74
|
[features, customSections.length]
|
|
72
75
|
);
|
|
@@ -88,6 +91,10 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
88
91
|
|
|
89
92
|
<CustomSettingsList customSections={customSections} />
|
|
90
93
|
|
|
94
|
+
{features.subscription && (
|
|
95
|
+
<SubscriptionSettingsSection config={normalizedConfig.subscription.config} />
|
|
96
|
+
)}
|
|
97
|
+
|
|
91
98
|
<FeatureSettingsSection normalizedConfig={normalizedConfig} features={features} />
|
|
92
99
|
|
|
93
100
|
<IdentitySettingsSection normalizedConfig={normalizedConfig} features={features} />
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
ListItem,
|
|
5
|
+
AtomicBadge,
|
|
6
|
+
useAppDesignTokens,
|
|
7
|
+
} from "@umituz/react-native-design-system";
|
|
8
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
9
|
+
import { SettingsSection } from "../../../components/SettingsSection";
|
|
10
|
+
import type { SubscriptionConfig } from "../../types";
|
|
11
|
+
|
|
12
|
+
interface SubscriptionSettingsSectionProps {
|
|
13
|
+
config?: SubscriptionConfig;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SubscriptionSettingsSection: React.FC<SubscriptionSettingsSectionProps> = ({
|
|
17
|
+
config,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useLocalization();
|
|
20
|
+
const tokens = useAppDesignTokens();
|
|
21
|
+
|
|
22
|
+
if (!config) return null;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<SettingsSection title={config.sectionTitle || t("settings.sections.subscription")}>
|
|
26
|
+
<ListItem
|
|
27
|
+
title={config.title || t("settings.subscription.title")}
|
|
28
|
+
description={config.description || t("settings.subscription.description")}
|
|
29
|
+
leftIcon={config.icon || "star"}
|
|
30
|
+
onPress={config.onPress}
|
|
31
|
+
rightElement={
|
|
32
|
+
config.isPremium ? (
|
|
33
|
+
<AtomicBadge
|
|
34
|
+
label={t("common.premium")}
|
|
35
|
+
variant="success"
|
|
36
|
+
size="small"
|
|
37
|
+
/>
|
|
38
|
+
) : (
|
|
39
|
+
<AtomicBadge
|
|
40
|
+
label={t("common.free")}
|
|
41
|
+
variant="warning"
|
|
42
|
+
size="small"
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
/>
|
|
47
|
+
</SettingsSection>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -58,6 +58,7 @@ export function useFeatureDetection(
|
|
|
58
58
|
feedback,
|
|
59
59
|
rating,
|
|
60
60
|
faqs,
|
|
61
|
+
subscription,
|
|
61
62
|
} = normalizedConfig;
|
|
62
63
|
|
|
63
64
|
const notificationServiceAvailable = !!options?.notificationServiceAvailable;
|
|
@@ -110,6 +111,7 @@ export function useFeatureDetection(
|
|
|
110
111
|
feedback: feedback.enabled,
|
|
111
112
|
rating: rating.enabled,
|
|
112
113
|
faqs: faqs.enabled,
|
|
114
|
+
subscription: subscription.enabled,
|
|
113
115
|
};
|
|
114
116
|
}, [normalizedConfig, navigation, options]);
|
|
115
117
|
}
|
|
@@ -218,3 +218,22 @@ export interface CloudSyncConfig {
|
|
|
218
218
|
/** Firestore collection name */
|
|
219
219
|
collectionName?: string;
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Subscription Settings Configuration
|
|
223
|
+
*/
|
|
224
|
+
export interface SubscriptionConfig {
|
|
225
|
+
/** Show subscription section */
|
|
226
|
+
enabled?: FeatureVisibility;
|
|
227
|
+
/** Custom title for the subscription section */
|
|
228
|
+
title?: string;
|
|
229
|
+
/** Custom label for the subscription item */
|
|
230
|
+
description?: string;
|
|
231
|
+
/** Custom icon name (Ionicons) */
|
|
232
|
+
icon?: string;
|
|
233
|
+
/** Custom section title for grouping */
|
|
234
|
+
sectionTitle?: string;
|
|
235
|
+
/** Handler to open subscription screen */
|
|
236
|
+
onPress?: () => void;
|
|
237
|
+
/** Whether user is premium (to show active status) */
|
|
238
|
+
isPremium?: boolean;
|
|
239
|
+
}
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
RatingConfig,
|
|
17
17
|
FAQConfig,
|
|
18
18
|
CloudSyncConfig,
|
|
19
|
+
SubscriptionConfig,
|
|
19
20
|
} from "./FeatureConfig";
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -113,6 +114,12 @@ export interface SettingsConfig {
|
|
|
113
114
|
* @default false
|
|
114
115
|
*/
|
|
115
116
|
cloudSync?: FeatureVisibility | CloudSyncConfig;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Subscription settings configuration
|
|
120
|
+
* @default false
|
|
121
|
+
*/
|
|
122
|
+
subscription?: FeatureVisibility | SubscriptionConfig;
|
|
116
123
|
|
|
117
124
|
/**
|
|
118
125
|
* Custom empty state text when no settings are available
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
FeedbackConfig,
|
|
16
16
|
RatingConfig,
|
|
17
17
|
FAQConfig,
|
|
18
|
+
SubscriptionConfig,
|
|
18
19
|
SettingsConfig,
|
|
19
20
|
} from "../types";
|
|
20
21
|
|
|
@@ -59,6 +60,10 @@ export interface NormalizedConfig {
|
|
|
59
60
|
enabled: boolean;
|
|
60
61
|
config?: FAQConfig;
|
|
61
62
|
};
|
|
63
|
+
subscription: {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
config?: SubscriptionConfig;
|
|
66
|
+
};
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
/**
|
|
@@ -103,5 +108,6 @@ export function normalizeSettingsConfig(
|
|
|
103
108
|
feedback: normalizeConfigValue(config?.feedback, false),
|
|
104
109
|
rating: normalizeConfigValue(config?.rating, false),
|
|
105
110
|
faqs: normalizeConfigValue(config?.faqs, false),
|
|
111
|
+
subscription: normalizeConfigValue(config?.subscription, false),
|
|
106
112
|
};
|
|
107
113
|
}
|