@umituz/react-native-settings 4.23.81 → 4.23.83

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 (30) hide show
  1. package/package.json +1 -1
  2. package/src/domains/about/presentation/screens/AboutScreen.tsx +49 -3
  3. package/src/domains/about/presentation/screens/AboutScreenContent.tsx +2 -4
  4. package/src/domains/disclaimer/presentation/screens/DisclaimerScreen.tsx +15 -1
  5. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +25 -23
  6. package/src/domains/feedback/presentation/components/FeedbackForm.styles.ts +51 -0
  7. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +3 -49
  8. package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +54 -57
  9. package/src/domains/legal/presentation/screens/LegalContentScreen.tsx +8 -8
  10. package/src/domains/legal/presentation/screens/LegalScreen.tsx +14 -3
  11. package/src/domains/localization/index.ts +2 -2
  12. package/src/domains/localization/presentation/providers/{LocalizationProvider.tsx → LocalizationManager.tsx} +1 -1
  13. package/src/domains/localization/presentation/screens/LanguageSelectionScreen.tsx +1 -1
  14. package/src/domains/notifications/index.ts +1 -1
  15. package/src/domains/notifications/presentation/screens/NotificationsScreen.tsx +53 -26
  16. package/src/domains/notifications/reminders/presentation/components/ReminderForm.constants.ts +35 -0
  17. package/src/domains/notifications/reminders/presentation/components/ReminderForm.styles.ts +22 -0
  18. package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +13 -57
  19. package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +25 -10
  20. package/src/domains/video-tutorials/presentation/screens/VideoTutorialsScreen.tsx +17 -6
  21. package/src/presentation/components/SettingsFooter.tsx +3 -3
  22. package/src/presentation/navigation/SettingsStackNavigator.tsx +32 -174
  23. package/src/presentation/navigation/hooks/index.ts +1 -0
  24. package/src/presentation/navigation/hooks/useSettingsScreens.ts +163 -0
  25. package/src/presentation/screens/SettingsScreen.tsx +38 -58
  26. package/src/presentation/screens/components/SettingsHeader.tsx +29 -43
  27. package/src/presentation/screens/hooks/useSettingsScreen.ts +64 -0
  28. package/src/utils/appUtils.ts +43 -0
  29. package/src/utils/index.ts +1 -0
  30. package/src/domains/about/presentation/screens/AboutScreenContainer.tsx +0 -109
@@ -0,0 +1,163 @@
1
+ import React, { useMemo } from 'react';
2
+ import type { StackScreen } from "@umituz/react-native-design-system";
3
+ import { LanguageSelectionScreen } from "../../../domains/localization";
4
+ import { NotificationSettingsScreen } from "../../../domains/notifications";
5
+ import { AccountScreen } from "@umituz/react-native-auth";
6
+ import { SettingsScreen } from "../../screens/SettingsScreen";
7
+ import { AppearanceScreen } from "../../screens/AppearanceScreen";
8
+ import { FAQScreen } from "../../../domains/faqs";
9
+ import { AboutScreen } from "../../../domains/about";
10
+ import { LegalScreen } from "../../../domains/legal";
11
+ import { GamificationScreen } from "../../../domains/gamification";
12
+ import type { SettingsStackNavigatorProps, AdditionalScreen } from "../types";
13
+
14
+ export interface UseSettingsScreensProps extends SettingsStackNavigatorProps {
15
+ aboutConfig: any;
16
+ legalProps: any;
17
+ notificationTranslations: any;
18
+ quietHoursTranslations: any;
19
+ t: (key: string) => string;
20
+ }
21
+
22
+ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[] => {
23
+ const {
24
+ appInfo,
25
+ config,
26
+ showUserProfile,
27
+ userProfile,
28
+ devSettings,
29
+ customSections,
30
+ showHeader,
31
+ showCloseButton,
32
+ onClose,
33
+ aboutConfig,
34
+ legalProps,
35
+ notificationTranslations,
36
+ quietHoursTranslations,
37
+ faqData,
38
+ additionalScreens,
39
+ gamificationConfig,
40
+ accountConfig,
41
+ t,
42
+ } = props;
43
+
44
+ return useMemo(() => {
45
+ const baseScreens: StackScreen[] = [
46
+ {
47
+ name: "SettingsMain",
48
+ options: { headerShown: false },
49
+ children: () => React.createElement(SettingsScreen, {
50
+ config,
51
+ appVersion: appInfo.version,
52
+ showUserProfile,
53
+ userProfile,
54
+ devSettings,
55
+ customSections,
56
+ showHeader,
57
+ showCloseButton,
58
+ onClose,
59
+ }),
60
+ },
61
+ {
62
+ name: "Appearance",
63
+ component: AppearanceScreen as any,
64
+ options: { headerShown: false },
65
+ },
66
+ {
67
+ name: "About",
68
+ options: { headerShown: false },
69
+ children: () => React.createElement(AboutScreen, { config: aboutConfig }),
70
+ },
71
+ {
72
+ name: "Legal",
73
+ options: { headerShown: false },
74
+ children: () => React.createElement(LegalScreen, legalProps),
75
+ },
76
+ {
77
+ name: "Notifications",
78
+ options: { headerShown: false },
79
+ children: () => React.createElement(NotificationSettingsScreen, {
80
+ translations: notificationTranslations,
81
+ quietHoursTranslations,
82
+ }),
83
+ },
84
+ ];
85
+
86
+ const faqScreen: StackScreen | null = (faqData && faqData.categories?.length > 0)
87
+ ? {
88
+ name: "FAQ",
89
+ options: { headerShown: false },
90
+ children: () => React.createElement(FAQScreen, {
91
+ categories: faqData.categories,
92
+ searchPlaceholder: t("settings.faqs.searchPlaceholder"),
93
+ emptySearchTitle: t("settings.faqs.emptySearchTitle"),
94
+ emptySearchMessage: t("settings.faqs.emptySearchMessage"),
95
+ headerTitle: t("settings.faqs.headerTitle"),
96
+ }),
97
+ }
98
+ : null;
99
+
100
+ const additionalStackScreens: StackScreen[] = (additionalScreens || []).map((screen: AdditionalScreen): StackScreen => {
101
+ const stackScreen: any = { name: screen.name };
102
+ if (screen.component) stackScreen.component = screen.component;
103
+ if (screen.children) stackScreen.children = screen.children;
104
+ if (screen.options) stackScreen.options = screen.options;
105
+ return stackScreen as StackScreen;
106
+ });
107
+
108
+ const gamificationScreen: StackScreen | null = gamificationConfig?.enabled
109
+ ? {
110
+ name: "Gamification",
111
+ options: { headerShown: false },
112
+ children: () => React.createElement(GamificationScreen, { config: gamificationConfig }),
113
+ }
114
+ : null;
115
+
116
+ const languageScreen: StackScreen = {
117
+ name: "LanguageSelection",
118
+ options: { headerShown: false },
119
+ children: () => React.createElement(LanguageSelectionScreen, {
120
+ headerTitle: t("settings.language.title"),
121
+ searchPlaceholder: t("settings.languageSelection.searchPlaceholder"),
122
+ }),
123
+ };
124
+
125
+ const accountScreen: StackScreen | null = accountConfig
126
+ ? {
127
+ name: "Account",
128
+ options: { headerShown: false },
129
+ children: () => React.createElement(AccountScreen, { config: accountConfig }),
130
+ }
131
+ : null;
132
+
133
+ const allScreens: StackScreen[] = [
134
+ ...baseScreens,
135
+ ...(faqScreen ? [faqScreen] : []),
136
+ ...additionalStackScreens,
137
+ ...(gamificationScreen ? [gamificationScreen] : []),
138
+ languageScreen,
139
+ ...(accountScreen ? [accountScreen] : []),
140
+ ];
141
+
142
+ return allScreens;
143
+ }, [
144
+ t,
145
+ showHeader,
146
+ showCloseButton,
147
+ onClose,
148
+ config,
149
+ appInfo.version,
150
+ showUserProfile,
151
+ userProfile,
152
+ devSettings,
153
+ customSections,
154
+ aboutConfig,
155
+ legalProps,
156
+ notificationTranslations,
157
+ quietHoursTranslations,
158
+ faqData,
159
+ additionalScreens,
160
+ gamificationConfig,
161
+ accountConfig,
162
+ ]);
163
+ };
@@ -6,12 +6,10 @@
6
6
  import React from "react";
7
7
  import {
8
8
  ScreenLayout,
9
- useAppNavigation,
10
9
  } from "@umituz/react-native-design-system";
10
+ import { useSettingsScreen } from "./hooks/useSettingsScreen";
11
11
  import { SettingsHeader } from "./components/SettingsHeader";
12
12
  import { SettingsContent } from "./components/SettingsContent";
13
- import { normalizeSettingsConfig } from "./utils/normalizeConfig";
14
- import { useFeatureDetection } from "./hooks/useFeatureDetection";
15
13
  import type { SettingsConfig, CustomSettingsSection } from "./types";
16
14
  import type { DevSettingsProps } from "../../domains/dev";
17
15
 
@@ -55,63 +53,44 @@ export interface SettingsScreenProps {
55
53
  showHeader?: boolean;
56
54
  }
57
55
 
58
- export const SettingsScreen: React.FC<SettingsScreenProps> = ({
59
- children,
60
- config = {},
61
- showUserProfile,
62
- userProfile,
63
- showFooter = true,
64
- footerText,
65
- appVersion,
66
- customSections = [],
67
- showCloseButton = false,
68
- showHeader = true,
69
- onClose,
70
- featureOptions,
71
- devSettings,
72
- gamificationConfig,
73
- }) => {
74
- const navigation = useAppNavigation();
56
+ export const SettingsScreen: React.FC<SettingsScreenProps> = (props) => {
57
+ const {
58
+ children,
59
+ config = {},
60
+ showUserProfile,
61
+ userProfile,
62
+ showFooter = true,
63
+ footerText,
64
+ appVersion: providedVersion,
65
+ customSections = [],
66
+ showCloseButton = false,
67
+ showHeader = true,
68
+ onClose,
69
+ featureOptions,
70
+ devSettings,
71
+ gamificationConfig,
72
+ } = props;
75
73
 
76
- // Memoize config normalization to prevent unnecessary recalculations
77
- const normalizedConfig = React.useMemo(
78
- () => normalizeSettingsConfig(config),
79
- [config]
80
- );
81
-
82
- // Feature detection hook (must be called at top level)
83
- const detectedFeatures = useFeatureDetection(normalizedConfig, navigation, featureOptions);
84
-
85
- // Memoize features to prevent unnecessary recalculations
86
- const features = React.useMemo(
87
- () => detectedFeatures,
88
- [detectedFeatures]
89
- );
90
-
91
- // Determine if user profile should be shown (explicit prop takes priority, then config)
92
- const shouldShowUserProfile = showUserProfile ?? features.userProfile;
74
+ const {
75
+ normalizedConfig,
76
+ features,
77
+ shouldShowUserProfile,
78
+ appVersion,
79
+ handleClose,
80
+ } = useSettingsScreen({
81
+ config,
82
+ showUserProfile,
83
+ featureOptions,
84
+ appVersion: providedVersion,
85
+ onClose,
86
+ });
93
87
 
94
- // Workaround: Use conditional rendering with type assertion
95
- if (showHeader) {
96
- return <ScreenLayout header={<SettingsHeader showCloseButton={showCloseButton} onClose={onClose} />}>
97
- {children ?? (
98
- <SettingsContent
99
- normalizedConfig={normalizedConfig}
100
- features={features}
101
- showUserProfile={shouldShowUserProfile}
102
- userProfile={userProfile}
103
- showFooter={showFooter}
104
- footerText={footerText}
105
- appVersion={appVersion}
106
- customSections={customSections}
107
- devSettings={devSettings}
108
- gamificationConfig={gamificationConfig}
109
- />
110
- )}
111
- </ScreenLayout>;
112
- }
88
+ const header = showHeader ? (
89
+ <SettingsHeader showCloseButton={showCloseButton} onClose={handleClose} />
90
+ ) : undefined;
113
91
 
114
- return <ScreenLayout>
92
+ return (
93
+ <ScreenLayout header={header}>
115
94
  {children ?? (
116
95
  <SettingsContent
117
96
  normalizedConfig={normalizedConfig}
@@ -126,5 +105,6 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
126
105
  gamificationConfig={gamificationConfig}
127
106
  />
128
107
  )}
129
- </ScreenLayout>;
108
+ </ScreenLayout>
109
+ );
130
110
  };
@@ -1,11 +1,6 @@
1
- /**
2
- * Settings Header Component
3
- * Handles close button functionality
4
- */
5
-
6
1
  import React from "react";
7
- import { View, Pressable, StyleSheet } from "react-native";
8
- import { useAppDesignTokens, AtomicIcon, AtomicText, useAppNavigation } from "@umituz/react-native-design-system";
2
+ import { Pressable } from "react-native";
3
+ import { useAppDesignTokens, AtomicIcon, useAppNavigation, NavigationHeader } from "@umituz/react-native-design-system";
9
4
  import { useLocalization } from "../../../domains/localization";
10
5
 
11
6
  interface SettingsHeaderProps {
@@ -29,41 +24,32 @@ export const SettingsHeader: React.FC<SettingsHeaderProps> = ({
29
24
  }
30
25
  };
31
26
 
27
+ const rightElement = showCloseButton ? (
28
+ <Pressable
29
+ onPress={handleClose}
30
+ style={({ pressed }) => [
31
+ {
32
+ width: 44,
33
+ height: 44,
34
+ justifyContent: "center",
35
+ alignItems: "center",
36
+ backgroundColor: pressed ? tokens.colors.surfaceVariant : tokens.colors.surface,
37
+ borderRadius: tokens.borders.radius.full,
38
+ },
39
+ ]}
40
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
41
+ >
42
+ <AtomicIcon name="close-outline" size="lg" color="textPrimary" />
43
+ </Pressable>
44
+ ) : undefined;
45
+
32
46
  return (
33
- <View style={[styles.container, { padding: tokens.spacing.lg }]}>
34
- <AtomicText type="headlineLarge">
35
- {t('settings.title')}
36
- </AtomicText>
37
-
38
- {showCloseButton && (
39
- <Pressable
40
- onPress={handleClose}
41
- style={({ pressed }) => [
42
- styles.closeButton,
43
- {
44
- backgroundColor: pressed ? tokens.colors.surfaceVariant : tokens.colors.surface,
45
- borderRadius: tokens.borders.radius.full,
46
- },
47
- ]}
48
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
49
- >
50
- <AtomicIcon name="close-outline" size="lg" color="textPrimary" />
51
- </Pressable>
52
- )}
53
- </View>
47
+ <NavigationHeader
48
+ title={t('settings.title')}
49
+ rightElement={rightElement}
50
+ // If NOT showing close button, we might want a back button?
51
+ // But usually Settings is a root screen in a modal or stack.
52
+ onBackPress={!showCloseButton ? handleClose : undefined}
53
+ />
54
54
  );
55
- };
56
-
57
- const styles = StyleSheet.create({
58
- container: {
59
- flexDirection: 'row',
60
- justifyContent: 'space-between',
61
- alignItems: 'center',
62
- },
63
- closeButton: {
64
- width: 44,
65
- height: 44,
66
- justifyContent: "center",
67
- alignItems: "center",
68
- },
69
- });
55
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * useSettingsScreen Hook
3
+ * Refactored: Extracted business logic from SettingsScreen component
4
+ */
5
+
6
+ import { useMemo, useCallback } from "react";
7
+ import { useAppNavigation } from "@umituz/react-native-design-system";
8
+ import { normalizeSettingsConfig } from "../utils/normalizeConfig";
9
+ import { useFeatureDetection } from "./useFeatureDetection";
10
+ import { getAppVersion } from "../../../utils/appUtils";
11
+ import type { SettingsConfig } from "../types";
12
+
13
+ export interface UseSettingsScreenParams {
14
+ config?: SettingsConfig;
15
+ showUserProfile?: boolean;
16
+ featureOptions?: {
17
+ notificationServiceAvailable?: boolean;
18
+ };
19
+ appVersion?: string;
20
+ onClose?: () => void;
21
+ }
22
+
23
+ export function useSettingsScreen({
24
+ config = {},
25
+ showUserProfile,
26
+ featureOptions,
27
+ appVersion: providedVersion,
28
+ onClose,
29
+ }: UseSettingsScreenParams) {
30
+ const navigation = useAppNavigation();
31
+
32
+ // Normalize config
33
+ const normalizedConfig = useMemo(
34
+ () => normalizeSettingsConfig(config),
35
+ [config]
36
+ );
37
+
38
+ // Feature detection
39
+ const features = useFeatureDetection(
40
+ normalizedConfig,
41
+ navigation,
42
+ featureOptions
43
+ );
44
+
45
+ const shouldShowUserProfile = showUserProfile ?? features.userProfile;
46
+
47
+ const appVersion = providedVersion || getAppVersion();
48
+
49
+ const handleClose = useCallback(() => {
50
+ if (onClose) {
51
+ onClose();
52
+ } else {
53
+ navigation.goBack();
54
+ }
55
+ }, [onClose, navigation]);
56
+
57
+ return {
58
+ normalizedConfig,
59
+ features,
60
+ shouldShowUserProfile,
61
+ appVersion,
62
+ handleClose,
63
+ };
64
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * App and Platform Utilities
3
+ */
4
+ import { Platform } from "react-native";
5
+ import Constants from "expo-constants";
6
+
7
+ /**
8
+ * Gets the current app version from Expo constants
9
+ */
10
+ export function getAppVersion(): string {
11
+ const version = Constants.expoConfig?.version ?? Constants.manifest2?.extra?.expoClient?.version;
12
+ if (!version) {
13
+ // Return a default if not found
14
+ return "1.0.0";
15
+ }
16
+ return version;
17
+ }
18
+
19
+ /**
20
+ * Gets the current build number from Expo constants
21
+ */
22
+ export function getBuildNumber(): string | undefined {
23
+ return Constants.expoConfig?.ios?.buildNumber ?? Constants.expoConfig?.android?.versionCode?.toString();
24
+ }
25
+
26
+ /**
27
+ * Validates if the current platform is supported
28
+ */
29
+ export function validatePlatform(): "ios" | "android" {
30
+ const platform = Platform.OS;
31
+ if (platform !== "ios" && platform !== "android") {
32
+ // Default to android for consistency if something goes wrong in detection
33
+ return "android";
34
+ }
35
+ return platform;
36
+ }
37
+
38
+ /**
39
+ * Checks if the app is currently in development mode
40
+ */
41
+ export function isDev(): boolean {
42
+ return __DEV__;
43
+ }
@@ -0,0 +1 @@
1
+ export * from "./appUtils";
@@ -1,109 +0,0 @@
1
- /**
2
- * AboutScreen Container Component
3
- * Handles business logic and state management for About screen
4
- */
5
- import React from 'react';
6
- import { View, StyleSheet } from 'react-native';
7
- import { AboutScreenContent } from './AboutScreenContent';
8
- import { useAboutInfo } from '../hooks/useAboutInfo';
9
- import { useAppDesignTokens, AtomicText, type DesignTokens } from '@umituz/react-native-design-system';
10
- import type { AboutScreenProps } from './AboutScreen';
11
-
12
- export const AboutScreenContainer: React.FC<AboutScreenProps> = ({
13
- config,
14
- containerStyle,
15
- headerStyle,
16
- titleStyle,
17
- versionStyle,
18
- showHeader = true,
19
- headerComponent,
20
- footerComponent,
21
- testID,
22
- }) => {
23
- const tokens = useAppDesignTokens();
24
- const styles = getStyles(tokens);
25
-
26
- const { appInfo, loading, error } = useAboutInfo({
27
- autoInit: true,
28
- initialConfig: config,
29
- });
30
-
31
- const containerStyles = React.useMemo(() => {
32
- return [
33
- styles.container,
34
- { backgroundColor: tokens.colors.backgroundPrimary },
35
- containerStyle
36
- ];
37
- }, [containerStyle, tokens.colors.backgroundPrimary, styles]);
38
-
39
- const texts = config.texts || {};
40
-
41
- if (loading) {
42
- return (
43
- <View style={containerStyles} testID={testID}>
44
- <AtomicText
45
- type="bodyMedium"
46
- color="textSecondary"
47
- style={styles.statusText}
48
- >
49
- {texts.loading}
50
- </AtomicText>
51
- </View>
52
- );
53
- }
54
-
55
- if (error) {
56
- return (
57
- <View style={containerStyles} testID={testID}>
58
- <AtomicText
59
- type="bodyMedium"
60
- color="error"
61
- style={styles.statusText}
62
- >
63
- {texts.errorPrefix} {error}
64
- </AtomicText>
65
- </View>
66
- );
67
- }
68
-
69
- if (!appInfo) {
70
- return (
71
- <View style={containerStyles} testID={testID}>
72
- <AtomicText
73
- type="bodyMedium"
74
- color="textSecondary"
75
- style={styles.statusText}
76
- >
77
- {texts.noInfo}
78
- </AtomicText>
79
- </View>
80
- );
81
- }
82
-
83
- return (
84
- <AboutScreenContent
85
- appInfo={appInfo}
86
- config={config}
87
- containerStyle={containerStyle}
88
- headerStyle={headerStyle}
89
- titleStyle={titleStyle}
90
- versionStyle={versionStyle}
91
- showHeader={showHeader}
92
- headerComponent={headerComponent}
93
- footerComponent={footerComponent}
94
- testID={testID}
95
- _tokens={tokens}
96
- />
97
- );
98
- };
99
-
100
- const getStyles = (tokens: DesignTokens) => StyleSheet.create({
101
- container: {
102
- flex: 1,
103
- },
104
- statusText: {
105
- textAlign: 'center',
106
- fontSize: tokens.typography.bodyMedium.responsiveFontSize,
107
- marginTop: 20,
108
- },
109
- });