@umituz/react-native-settings 4.23.84 → 4.23.85

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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/domains/about/presentation/hooks/useAboutInfo.ts +0 -2
  3. package/src/domains/appearance/presentation/components/ColorPicker.tsx +0 -1
  4. package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +0 -1
  5. package/src/domains/appearance/presentation/components/ThemeOption.tsx +0 -1
  6. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +0 -1
  7. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +12 -12
  8. package/src/domains/feedback/presentation/components/SupportSection.tsx +0 -1
  9. package/src/domains/localization/infrastructure/components/useLanguageSwitcher.ts +0 -1
  10. package/src/domains/localization/infrastructure/hooks/useLanguageSelection.ts +0 -4
  11. package/src/domains/localization/infrastructure/hooks/useTranslation.ts +0 -1
  12. package/src/domains/localization/infrastructure/storage/LanguageInitializer.ts +0 -1
  13. package/src/domains/localization/infrastructure/storage/LanguageSwitcher.ts +0 -4
  14. package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +0 -5
  15. package/src/domains/localization/presentation/components/LanguageItem.tsx +0 -1
  16. package/src/domains/localization/presentation/providers/LocalizationManager.tsx +0 -1
  17. package/src/domains/localization/presentation/screens/LanguageSelectionScreen.tsx +0 -3
  18. package/src/domains/notifications/infrastructure/services/NotificationService.ts +0 -1
  19. package/src/domains/notifications/infrastructure/utils/dev.ts +0 -3
  20. package/src/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.ts +0 -1
  21. package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +0 -6
  22. package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +0 -5
  23. package/src/domains/rating/application/services/RatingService.ts +13 -63
  24. package/src/domains/rating/infrastructure/storage/RatingStorage.ts +14 -36
  25. package/src/domains/rating/presentation/hooks/useAppRating.tsx +0 -1
  26. package/src/infrastructure/utils/dateUtils.ts +61 -0
  27. package/src/infrastructure/utils/memoComparisonUtils.ts +68 -0
  28. package/src/infrastructure/utils/sanitizers.ts +49 -0
  29. package/src/infrastructure/utils/translationHelpers.ts +81 -0
  30. package/src/infrastructure/utils/validators.ts +59 -0
  31. package/src/presentation/components/SettingsItemCard.tsx +129 -172
  32. package/src/presentation/components/settings/SettingsItemCardContent.tsx +70 -0
  33. package/src/presentation/components/settings/SettingsItemCardRightElement.tsx +42 -0
  34. package/src/presentation/components/settings/SettingsItemCardSection.tsx +29 -0
  35. package/src/presentation/hooks/useSettingsScreenConfig.ts +19 -54
  36. package/src/presentation/navigation/hooks/useSettingsScreens.ts +75 -89
  37. package/src/presentation/screens/components/GamificationSettingsItem.tsx +20 -17
  38. package/src/presentation/screens/components/SettingsContent.tsx +29 -0
  39. package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +13 -4
  40. package/src/presentation/screens/components/VideoTutorialSettingsItem.tsx +47 -0
  41. package/src/presentation/screens/components/WalletSettingsItem.tsx +13 -4
  42. package/src/presentation/screens/components/sections/CustomSettingsList.tsx +12 -5
  43. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +12 -11
  44. package/src/presentation/screens/components/sections/IdentitySettingsSection.tsx +4 -1
  45. package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +16 -10
  46. package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +9 -4
  47. package/src/presentation/screens/hooks/useFeatureDetection.ts +2 -0
  48. package/src/presentation/screens/types/SettingsConfig.ts +8 -1
  49. package/src/presentation/screens/types/UserFeatureConfig.ts +20 -0
  50. package/src/presentation/screens/types/index.ts +1 -0
  51. package/src/presentation/screens/utils/normalizeConfig.ts +7 -1
  52. package/src/presentation/utils/accountConfigUtils.ts +67 -0
  53. package/src/presentation/utils/screenFactory.ts +87 -0
  54. package/src/presentation/utils/userProfileUtils.ts +51 -0
@@ -0,0 +1,70 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { useAppDesignTokens, AtomicIcon, AtomicText, type IconName } from "@umituz/react-native-design-system";
4
+
5
+ export interface SettingsItemCardContentProps {
6
+ icon: IconName;
7
+ title: string;
8
+ description?: string;
9
+ iconBgColor: string;
10
+ iconColor: string;
11
+ disabled?: boolean;
12
+ rightElement: React.ReactElement;
13
+ }
14
+
15
+ export const SettingsItemCardContent: React.FC<SettingsItemCardContentProps> = React.memo(({
16
+ icon,
17
+ title,
18
+ description,
19
+ iconBgColor,
20
+ iconColor,
21
+ disabled,
22
+ rightElement,
23
+ }) => {
24
+ const tokens = useAppDesignTokens();
25
+
26
+ return (
27
+ <View style={styles.content}>
28
+ <View style={[styles.iconContainer, { backgroundColor: iconBgColor, borderRadius: tokens.borders.radius.md }]}>
29
+ <AtomicIcon name={icon} size="lg" customColor={iconColor} />
30
+ </View>
31
+ <View style={styles.textContainer}>
32
+ <AtomicText
33
+ type="bodyLarge"
34
+ color={disabled ? "onSurfaceVariant" : "onSurface"}
35
+ numberOfLines={1}
36
+ style={{ marginBottom: description ? tokens.spacing.xs : 0, opacity: disabled ? 0.6 : 1 }}
37
+ >
38
+ {title}
39
+ </AtomicText>
40
+ {!!description && (
41
+ <AtomicText type="bodyMedium" color="textSecondary" numberOfLines={2}>
42
+ {description}
43
+ </AtomicText>
44
+ )}
45
+ </View>
46
+ {rightElement}
47
+ </View>
48
+ );
49
+ });
50
+
51
+ SettingsItemCardContent.displayName = "SettingsItemCardContent";
52
+
53
+ const styles = {
54
+ content: {
55
+ flex: 1,
56
+ flexDirection: "row",
57
+ alignItems: "center",
58
+ },
59
+ iconContainer: {
60
+ width: 48,
61
+ height: 48,
62
+ justifyContent: "center",
63
+ alignItems: "center",
64
+ marginRight: 16,
65
+ },
66
+ textContainer: {
67
+ flex: 1,
68
+ marginRight: 8,
69
+ },
70
+ } as const;
@@ -0,0 +1,42 @@
1
+ import React from "react";
2
+ import { AtomicIcon, AtomicSwitch, type IconName } from "@umituz/react-native-design-system";
3
+
4
+ export interface SettingsItemCardRightElementProps {
5
+ showSwitch: boolean;
6
+ switchValue?: boolean;
7
+ onSwitchChange?: (value: boolean) => void;
8
+ disabled?: boolean;
9
+ shouldShowChevron: boolean;
10
+ rightIcon: IconName;
11
+ }
12
+
13
+ export const SettingsItemCardRightElement: React.FC<SettingsItemCardRightElementProps> = React.memo(({
14
+ showSwitch,
15
+ switchValue,
16
+ onSwitchChange,
17
+ disabled,
18
+ shouldShowChevron,
19
+ rightIcon,
20
+ }) => {
21
+ const handleSwitchChange = React.useCallback((value: boolean) => {
22
+ if (onSwitchChange) {
23
+ onSwitchChange(value);
24
+ }
25
+ }, [onSwitchChange]);
26
+
27
+ if (showSwitch) {
28
+ return (
29
+ <AtomicSwitch
30
+ value={!!switchValue}
31
+ onValueChange={handleSwitchChange}
32
+ disabled={disabled}
33
+ />
34
+ );
35
+ }
36
+ if (shouldShowChevron) {
37
+ return <AtomicIcon name={rightIcon} size="sm" color="textSecondary" />;
38
+ }
39
+ return null;
40
+ });
41
+
42
+ SettingsItemCardRightElement.displayName = "SettingsItemCardRightElement";
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { AtomicText } from "@umituz/react-native-design-system";
4
+
5
+ export interface SettingsItemCardSectionProps {
6
+ sectionTitle?: string;
7
+ }
8
+
9
+ export const SettingsItemCardSection: React.FC<SettingsItemCardSectionProps> = React.memo(({ sectionTitle }) => {
10
+ if (!sectionTitle) return null;
11
+
12
+ return (
13
+ <View style={styles.headerContainer}>
14
+ <AtomicText type="labelMedium" color="textSecondary" style={{ textTransform: "uppercase" }}>
15
+ {sectionTitle}
16
+ </AtomicText>
17
+ </View>
18
+ );
19
+ });
20
+
21
+ SettingsItemCardSection.displayName = "SettingsItemCardSection";
22
+
23
+ const styles = {
24
+ headerContainer: {
25
+ paddingHorizontal: 16,
26
+ paddingTop: 16,
27
+ paddingBottom: 8,
28
+ },
29
+ } as const;
@@ -27,6 +27,8 @@ import {
27
27
  createFAQConfig,
28
28
  createSubscriptionConfig,
29
29
  } from "../utils/config-creators";
30
+ import { createUserProfileDisplay } from "../utils/userProfileUtils";
31
+ import { createAccountConfig } from "../utils/accountConfigUtils";
30
32
  import type { SettingsConfig } from "../screens/types";
31
33
  import type { FeedbackFormData } from "../utils/config-creators";
32
34
  import type { AppInfo, FAQData, UserProfileDisplay, AdditionalScreen } from "../navigation/types";
@@ -127,60 +129,23 @@ export const useSettingsScreenConfig = (
127
129
  showAbout, showLegal, showFaqs, showRating,
128
130
  ]);
129
131
 
130
- const userProfile = useMemo((): UserProfileDisplay => {
131
- const isAnonymous = userProfileData?.isAnonymous ?? true;
132
-
133
- // Ensure t function is available before using it
134
- const anonymousName = typeof t === 'function'
135
- ? t("settings.profile.anonymousName")
136
- : 'Anonymous';
137
-
138
- return {
139
- displayName: userProfileData?.displayName || anonymousName,
140
- userId: userProfileData?.userId ?? undefined,
141
- isAnonymous,
142
- avatarUrl: userProfileData?.avatarUrl ?? undefined,
143
- onPress: isAnonymous ? handleSignIn : undefined,
144
- accountSettingsRoute: isAnonymous ? undefined : "Account",
145
- };
146
- }, [userProfileData, t, handleSignIn]);
147
-
148
- const accountConfig = useMemo((): AccountScreenConfig => {
149
- const isAnonymous = user?.isAnonymous ?? true;
150
-
151
- // Ensure t function is available before using it
152
- const getTranslation = (key: string, fallback: string) =>
153
- typeof t === 'function' ? t(key) : fallback;
154
-
155
- return {
156
- profile: {
157
- displayName: userProfileData?.displayName || user?.displayName || getTranslation("settings.profile.anonymousName", "Anonymous"),
158
- userId: userProfileData?.userId ?? user?.uid ?? undefined,
159
- isAnonymous,
160
- avatarUrl: userProfileData?.avatarUrl ?? user?.photoURL ?? undefined,
161
- benefits: isAnonymous ? [
162
- getTranslation("settings.profile.benefits.saveHistory", "Save history"),
163
- getTranslation("settings.profile.benefits.syncDevices", "Sync devices"),
164
- getTranslation("settings.profile.benefits.cloudSync", "Cloud sync"),
165
- getTranslation("settings.profile.benefits.secureBackup", "Secure backup"),
166
- ] : undefined,
167
- },
168
- isAnonymous,
169
- editProfileText: getTranslation("settings.account.editProfile", "Edit Profile"),
170
- onSignIn: handleSignIn,
171
- accountActions: {
172
- onLogout: handleSignOut,
173
- onDeleteAccount: handleDeleteAccount,
174
- logoutText: getTranslation("settings.account.logout", "Log Out"),
175
- logoutConfirmTitle: getTranslation("settings.account.logoutConfirmTitle", "Log Out"),
176
- logoutConfirmMessage: getTranslation("settings.account.logoutConfirmMessage", "Are you sure you want to log out?"),
177
- cancelText: getTranslation("common.cancel", "Cancel"),
178
- deleteAccountText: getTranslation("settings.account.deleteAccount", "Delete Account"),
179
- deleteConfirmTitle: getTranslation("settings.account.deleteConfirmTitle", "Delete Account"),
180
- deleteConfirmMessage: getTranslation("settings.account.deleteConfirmMessage", "Are you sure you want to delete your account? This action cannot be undone."),
181
- },
182
- };
183
- }, [user, userProfileData, handleSignIn, handleSignOut, handleDeleteAccount, t]);
132
+ const userProfile = useMemo(() => createUserProfileDisplay({
133
+ profileData: userProfileData,
134
+ t,
135
+ onSignIn: handleSignIn,
136
+ }), [userProfileData, t, handleSignIn]);
137
+
138
+ const accountConfig = useMemo(() => createAccountConfig({
139
+ displayName: userProfileData?.displayName || user?.displayName || undefined,
140
+ userId: userProfileData?.userId ?? user?.uid ?? undefined,
141
+ photoURL: user?.photoURL ?? undefined,
142
+ isAnonymous: user?.isAnonymous,
143
+ avatarUrl: userProfileData?.avatarUrl ?? undefined,
144
+ onSignIn: handleSignIn,
145
+ onLogout: handleSignOut,
146
+ onDeleteAccount: handleDeleteAccount,
147
+ t,
148
+ }), [user, userProfileData, handleSignIn, handleSignOut, handleDeleteAccount, t]);
184
149
 
185
150
  const translatedFaqData = useMemo((): FAQData | undefined => {
186
151
  if (!faqData) return undefined;
@@ -9,7 +9,13 @@ import { FAQScreen } from "../../../domains/faqs";
9
9
  import { AboutScreen } from "../../../domains/about";
10
10
  import { LegalScreen } from "../../../domains/legal";
11
11
  import { GamificationScreen } from "../../../domains/gamification";
12
- import type { SettingsStackNavigatorProps, AdditionalScreen } from "../types";
12
+ import {
13
+ createScreenWithProps,
14
+ convertAdditionalScreen,
15
+ createConditionalScreen,
16
+ combineScreens,
17
+ } from "../../utils/screenFactory";
18
+ import type { SettingsStackNavigatorProps } from "../types";
13
19
 
14
20
  export interface UseSettingsScreensProps extends SettingsStackNavigatorProps {
15
21
  aboutConfig: any;
@@ -42,104 +48,84 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
42
48
  } = props;
43
49
 
44
50
  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
- },
51
+ const settingsMainScreen = createScreenWithProps(
52
+ "SettingsMain",
53
+ SettingsScreen,
66
54
  {
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
- },
55
+ config,
56
+ appVersion: appInfo.version,
57
+ showUserProfile,
58
+ userProfile,
59
+ devSettings,
60
+ customSections,
61
+ showHeader,
62
+ showCloseButton,
63
+ onClose,
64
+ }
65
+ );
66
+
67
+ const appearanceScreen = {
68
+ name: "Appearance",
69
+ component: AppearanceScreen as any,
70
+ options: { headerShown: false },
71
+ };
72
+
73
+ const aboutScreen = createScreenWithProps("About", AboutScreen, { config: aboutConfig });
74
+ const legalScreen = createScreenWithProps("Legal", LegalScreen, legalProps);
75
+
76
+ const notificationScreen = createScreenWithProps(
77
+ "Notifications",
78
+ NotificationSettingsScreen,
76
79
  {
77
- name: "Notifications",
78
- options: { headerShown: false },
79
- children: () => React.createElement(NotificationSettingsScreen, {
80
- translations: notificationTranslations,
81
- quietHoursTranslations,
82
- }),
83
- },
80
+ translations: notificationTranslations,
81
+ quietHoursTranslations,
82
+ }
83
+ );
84
+
85
+ const baseScreens: StackScreen[] = [
86
+ settingsMainScreen,
87
+ appearanceScreen,
88
+ aboutScreen,
89
+ legalScreen,
90
+ notificationScreen,
84
91
  ];
85
92
 
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;
93
+ const faqScreen = createConditionalScreen(
94
+ !!(faqData && faqData.categories?.length > 0),
95
+ () => createScreenWithProps("FAQ", FAQScreen, {
96
+ categories: faqData!.categories,
97
+ searchPlaceholder: t("settings.faqs.searchPlaceholder"),
98
+ emptySearchTitle: t("settings.faqs.emptySearchTitle"),
99
+ emptySearchMessage: t("settings.faqs.emptySearchMessage"),
100
+ headerTitle: t("settings.faqs.headerTitle"),
101
+ })
102
+ );
99
103
 
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
- });
104
+ const additionalStackScreens: StackScreen[] = (additionalScreens || []).map(convertAdditionalScreen);
107
105
 
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;
106
+ const gamificationScreen = createConditionalScreen(
107
+ !!(gamificationConfig?.enabled),
108
+ () => createScreenWithProps("Gamification", GamificationScreen as any, { config: gamificationConfig })
109
+ );
115
110
 
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
- };
111
+ const languageScreen = createScreenWithProps("LanguageSelection", LanguageSelectionScreen, {
112
+ headerTitle: t("settings.language.title"),
113
+ searchPlaceholder: t("settings.languageSelection.searchPlaceholder"),
114
+ });
124
115
 
125
- const accountScreen: StackScreen | null = accountConfig
126
- ? {
127
- name: "Account",
128
- options: { headerShown: false },
129
- children: () => React.createElement(AccountScreen, { config: accountConfig }),
130
- }
131
- : null;
116
+ const accountScreen = createConditionalScreen(
117
+ !!accountConfig,
118
+ () => createScreenWithProps("Account", AccountScreen as any, { config: accountConfig })
119
+ );
132
120
 
133
- const allScreens: StackScreen[] = [
134
- ...baseScreens,
135
- ...(faqScreen ? [faqScreen] : []),
136
- ...additionalStackScreens,
137
- ...(gamificationScreen ? [gamificationScreen] : []),
121
+ return combineScreens(
122
+ baseScreens,
123
+ faqScreen,
124
+ additionalStackScreens,
125
+ gamificationScreen,
138
126
  languageScreen,
139
- ...(accountScreen ? [accountScreen] : []),
140
- ];
141
-
142
- return allScreens;
127
+ accountScreen
128
+ );
143
129
  }, [
144
130
  t,
145
131
  showHeader,
@@ -3,8 +3,9 @@ import { useAppNavigation } from "@umituz/react-native-design-system";
3
3
  import type { IconName } from "@umituz/react-native-design-system";
4
4
  import { SettingsItemCard } from "../../components/SettingsItemCard";
5
5
  import { useGamification } from "../../../domains/gamification";
6
- import type { GamificationItemConfig } from "../../screens/types/UserFeatureConfig";
6
+ import type { GamificationItemConfig } from "../types/UserFeatureConfig";
7
7
  import type { GamificationSettingsConfig } from "../../../domains/gamification";
8
+ import { compareGamificationProps } from "../../../infrastructure/utils/memoComparisonUtils";
8
9
 
9
10
  export interface GamificationSettingsItemProps {
10
11
  config: GamificationItemConfig;
@@ -12,27 +13,23 @@ export interface GamificationSettingsItemProps {
12
13
  t: (key: string) => string;
13
14
  }
14
15
 
15
- export const GamificationSettingsItem: React.FC<GamificationSettingsItemProps> = ({
16
- config,
17
- gamificationConfig,
18
- t
16
+ const GamificationSettingsItemComponent: React.FC<GamificationSettingsItemProps> = ({
17
+ config,
18
+ gamificationConfig,
19
+ t
19
20
  }) => {
20
21
  const navigation = useAppNavigation();
21
22
  const { level } = useGamification(gamificationConfig);
22
-
23
- const handlePress = () => {
24
- if (config.route) {
25
- navigation.navigate(config.route as never);
26
- } else {
27
- navigation.navigate("Gamification" as never);
28
- }
29
- };
30
-
23
+
24
+ const handlePress = React.useCallback(() => {
25
+ const route = config.route || "Gamification";
26
+ navigation.navigate(route as never);
27
+ }, [navigation, config.route]);
28
+
31
29
  const icon = (config.icon || "trophy-outline") as IconName;
32
30
  const title = config.title || t("settings.gamification.title");
33
-
34
- // Dynamic description showing current level and points
35
- const description = config.description ||
31
+
32
+ const description = config.description ||
36
33
  t("settings.gamification.description")
37
34
  .replace("{level}", level.currentLevel.toString())
38
35
  .replace("{points}", level.currentPoints.toString());
@@ -47,3 +44,9 @@ export const GamificationSettingsItem: React.FC<GamificationSettingsItemProps> =
47
44
  />
48
45
  );
49
46
  };
47
+
48
+ export const GamificationSettingsItem = React.memo(
49
+ GamificationSettingsItemComponent,
50
+ compareGamificationProps
51
+ );
52
+ GamificationSettingsItem.displayName = "GamificationSettingsItem";
@@ -17,6 +17,7 @@ import type { CustomSettingsSection } from "../types";
17
17
  import { SubscriptionSettingsItem } from "./SubscriptionSettingsItem";
18
18
  import { WalletSettingsItem } from "./WalletSettingsItem";
19
19
  import { GamificationSettingsItem } from "./GamificationSettingsItem";
20
+ import { VideoTutorialSettingsItem } from "./VideoTutorialSettingsItem";
20
21
 
21
22
  interface SettingsContentProps {
22
23
  normalizedConfig: NormalizedConfig;
@@ -34,6 +35,7 @@ interface SettingsContentProps {
34
35
  subscription: boolean;
35
36
  wallet: boolean;
36
37
  gamification: boolean;
38
+ videoTutorial: boolean;
37
39
  };
38
40
  showUserProfile?: boolean;
39
41
  userProfile?: {
@@ -84,6 +86,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
84
86
  features.subscription ||
85
87
  features.wallet ||
86
88
  features.gamification ||
89
+ features.videoTutorial ||
87
90
  customSections.length > 0,
88
91
  [
89
92
  features.appearance,
@@ -98,6 +101,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
98
101
  features.subscription,
99
102
  features.wallet,
100
103
  features.gamification,
104
+ features.videoTutorial,
101
105
  customSections.length,
102
106
  ]
103
107
  );
@@ -127,6 +131,13 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
127
131
  />
128
132
  )}
129
133
 
134
+ {features.videoTutorial && (
135
+ <VideoTutorialSettingsItem
136
+ config={normalizedConfig.videoTutorial.config || {}}
137
+ t={t}
138
+ />
139
+ )}
140
+
130
141
  <FeatureSettingsSection normalizedConfig={normalizedConfig} features={features} />
131
142
 
132
143
  <IdentitySettingsSection normalizedConfig={normalizedConfig} features={features} />
@@ -156,6 +167,24 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
156
167
  );
157
168
  };
158
169
 
170
+ export const MemoizedSettingsContent = React.memo(SettingsContent, (prevProps, nextProps) => {
171
+ return (
172
+ prevProps.normalizedConfig === nextProps.normalizedConfig &&
173
+ prevProps.features === nextProps.features &&
174
+ prevProps.showUserProfile === nextProps.showUserProfile &&
175
+ prevProps.userProfile === nextProps.userProfile &&
176
+ prevProps.showFooter === nextProps.showFooter &&
177
+ prevProps.footerText === nextProps.footerText &&
178
+ prevProps.appVersion === nextProps.appVersion &&
179
+ prevProps.customSections === nextProps.customSections &&
180
+ prevProps.emptyStateText === nextProps.emptyStateText &&
181
+ prevProps.devSettings === nextProps.devSettings &&
182
+ prevProps.gamificationConfig === nextProps.gamificationConfig
183
+ );
184
+ });
185
+
186
+ MemoizedSettingsContent.displayName = "MemoizedSettingsContent";
187
+
159
188
  const styles = StyleSheet.create({
160
189
  container: {
161
190
  flex: 1,
@@ -2,22 +2,25 @@ import React from "react";
2
2
  import { useAppNavigation } from "@umituz/react-native-design-system";
3
3
  import { SettingsItemCard } from "../../components/SettingsItemCard";
4
4
  import type { IconName } from "@umituz/react-native-design-system";
5
- import type { SubscriptionConfig } from "../../screens/types/UserFeatureConfig";
5
+ import type { SubscriptionConfig } from "../types/UserFeatureConfig";
6
+ import { compareConfigAndTranslate } from "../../../infrastructure/utils/memoComparisonUtils";
6
7
 
7
8
  export interface SubscriptionSettingsItemProps {
8
9
  config: SubscriptionConfig;
9
10
  t: (key: string) => string;
10
11
  }
11
12
 
12
- export const SubscriptionSettingsItem: React.FC<SubscriptionSettingsItemProps> = ({ config, t }) => {
13
+ const SubscriptionSettingsItemComponent: React.FC<SubscriptionSettingsItemProps> = ({ config, t }) => {
13
14
  const navigation = useAppNavigation();
14
- const handlePress = () => {
15
+
16
+ const handlePress = React.useCallback(() => {
15
17
  if (config.route) {
16
18
  navigation.navigate(config.route as never);
17
19
  } else if (config.onPress) {
18
20
  config.onPress();
19
21
  }
20
- };
22
+ }, [navigation, config.route, config.onPress]);
23
+
21
24
  return (
22
25
  <SettingsItemCard
23
26
  title={config.title || t("settings.subscription.title")}
@@ -28,3 +31,9 @@ export const SubscriptionSettingsItem: React.FC<SubscriptionSettingsItemProps> =
28
31
  />
29
32
  );
30
33
  };
34
+
35
+ export const SubscriptionSettingsItem = React.memo(
36
+ SubscriptionSettingsItemComponent,
37
+ compareConfigAndTranslate
38
+ );
39
+ SubscriptionSettingsItem.displayName = "SubscriptionSettingsItem";
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { useAppNavigation } from "@umituz/react-native-design-system";
3
+ import type { IconName } from "@umituz/react-native-design-system";
4
+ import { SettingsItemCard } from "../../components/SettingsItemCard";
5
+ import type { VideoTutorialConfig } from "../types/UserFeatureConfig";
6
+ import { compareConfigAndTranslate } from "../../../infrastructure/utils/memoComparisonUtils";
7
+
8
+ export interface VideoTutorialSettingsItemProps {
9
+ config: VideoTutorialConfig;
10
+ t: (key: string) => string;
11
+ }
12
+
13
+ const VideoTutorialSettingsItemComponent: React.FC<VideoTutorialSettingsItemProps> = ({
14
+ config,
15
+ t
16
+ }) => {
17
+ const navigation = useAppNavigation();
18
+
19
+ const handlePress = React.useCallback(() => {
20
+ if (config.onPress) {
21
+ config.onPress();
22
+ } else {
23
+ const route = config.route || "VideoTutorial";
24
+ navigation.navigate(route as never);
25
+ }
26
+ }, [navigation, config.onPress, config.route]);
27
+
28
+ const icon = (config.icon || "play-circle-outline") as IconName;
29
+ const title = config.title || t("settings.videoTutorial.title");
30
+ const description = config.description || t("settings.videoTutorial.description");
31
+
32
+ return (
33
+ <SettingsItemCard
34
+ title={title}
35
+ description={description}
36
+ icon={icon}
37
+ onPress={handlePress}
38
+ sectionTitle={config.sectionTitle}
39
+ />
40
+ );
41
+ };
42
+
43
+ export const VideoTutorialSettingsItem = React.memo(
44
+ VideoTutorialSettingsItemComponent,
45
+ compareConfigAndTranslate
46
+ );
47
+ VideoTutorialSettingsItem.displayName = "VideoTutorialSettingsItem";
@@ -2,20 +2,23 @@ import React from "react";
2
2
  import { useAppNavigation } from "@umituz/react-native-design-system";
3
3
  import { SettingsItemCard } from "../../components/SettingsItemCard";
4
4
  import type { IconName } from "@umituz/react-native-design-system";
5
- import type { WalletConfig } from "../../screens/types/UserFeatureConfig";
5
+ import type { WalletConfig } from "../types/UserFeatureConfig";
6
+ import { compareConfigAndTranslate } from "../../../infrastructure/utils/memoComparisonUtils";
6
7
 
7
8
  export interface WalletSettingsItemProps {
8
9
  config: WalletConfig;
9
10
  t: (key: string) => string;
10
11
  }
11
12
 
12
- export const WalletSettingsItem: React.FC<WalletSettingsItemProps> = ({ config, t }) => {
13
+ const WalletSettingsItemComponent: React.FC<WalletSettingsItemProps> = ({ config, t }) => {
13
14
  const navigation = useAppNavigation();
14
- const handlePress = () => {
15
+
16
+ const handlePress = React.useCallback(() => {
15
17
  if (config.route) {
16
18
  navigation.navigate(config.route as never);
17
19
  }
18
- };
20
+ }, [navigation, config.route]);
21
+
19
22
  return (
20
23
  <SettingsItemCard
21
24
  title={config.title || t("wallet.title")}
@@ -26,3 +29,9 @@ export const WalletSettingsItem: React.FC<WalletSettingsItemProps> = ({ config,
26
29
  />
27
30
  );
28
31
  };
32
+
33
+ export const WalletSettingsItem = React.memo(
34
+ WalletSettingsItemComponent,
35
+ compareConfigAndTranslate
36
+ );
37
+ WalletSettingsItem.displayName = "WalletSettingsItem";