@umituz/react-native-settings 4.23.84 → 4.23.86

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 (59) hide show
  1. package/package.json +3 -3
  2. package/src/domains/about/presentation/hooks/useAboutInfo.ts +1 -3
  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/faqs/presentation/screens/FAQScreen.tsx +1 -1
  7. package/src/domains/feedback/presentation/components/FeedbackForm.styles.ts +1 -1
  8. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +1 -2
  9. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +12 -12
  10. package/src/domains/feedback/presentation/components/SupportSection.tsx +0 -1
  11. package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +1 -6
  12. package/src/domains/localization/infrastructure/components/useLanguageSwitcher.ts +0 -1
  13. package/src/domains/localization/infrastructure/hooks/useLanguageSelection.ts +0 -4
  14. package/src/domains/localization/infrastructure/hooks/useTranslation.ts +0 -1
  15. package/src/domains/localization/infrastructure/storage/LanguageInitializer.ts +0 -1
  16. package/src/domains/localization/infrastructure/storage/LanguageSwitcher.ts +0 -4
  17. package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +0 -5
  18. package/src/domains/localization/presentation/components/LanguageItem.tsx +0 -1
  19. package/src/domains/localization/presentation/providers/LocalizationManager.tsx +0 -1
  20. package/src/domains/localization/presentation/screens/LanguageSelectionScreen.tsx +0 -3
  21. package/src/domains/notifications/infrastructure/services/NotificationService.ts +0 -1
  22. package/src/domains/notifications/infrastructure/utils/dev.ts +3 -6
  23. package/src/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.ts +0 -1
  24. package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +0 -6
  25. package/src/domains/notifications/reminders/presentation/components/ReminderForm.constants.ts +1 -1
  26. package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +1 -6
  27. package/src/domains/rating/application/services/RatingService.ts +13 -63
  28. package/src/domains/rating/infrastructure/storage/RatingStorage.ts +14 -36
  29. package/src/domains/rating/presentation/hooks/useAppRating.tsx +0 -1
  30. package/src/infrastructure/utils/dateUtils.ts +61 -0
  31. package/src/infrastructure/utils/memoComparisonUtils.ts +66 -0
  32. package/src/infrastructure/utils/sanitizers.ts +49 -0
  33. package/src/infrastructure/utils/translationHelpers.ts +81 -0
  34. package/src/infrastructure/utils/validators.ts +59 -0
  35. package/src/presentation/components/SettingsItemCard.tsx +129 -172
  36. package/src/presentation/components/settings/SettingsItemCardContent.tsx +70 -0
  37. package/src/presentation/components/settings/SettingsItemCardRightElement.tsx +42 -0
  38. package/src/presentation/components/settings/SettingsItemCardSection.tsx +29 -0
  39. package/src/presentation/hooks/useSettingsScreenConfig.ts +19 -54
  40. package/src/presentation/navigation/SettingsStackNavigator.tsx +1 -24
  41. package/src/presentation/navigation/hooks/useSettingsScreens.ts +76 -90
  42. package/src/presentation/screens/components/GamificationSettingsItem.tsx +20 -17
  43. package/src/presentation/screens/components/SettingsContent.tsx +29 -0
  44. package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +13 -4
  45. package/src/presentation/screens/components/VideoTutorialSettingsItem.tsx +47 -0
  46. package/src/presentation/screens/components/WalletSettingsItem.tsx +13 -4
  47. package/src/presentation/screens/components/sections/CustomSettingsList.tsx +12 -5
  48. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +12 -11
  49. package/src/presentation/screens/components/sections/IdentitySettingsSection.tsx +4 -1
  50. package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +16 -10
  51. package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +9 -4
  52. package/src/presentation/screens/hooks/useFeatureDetection.ts +2 -0
  53. package/src/presentation/screens/types/SettingsConfig.ts +8 -1
  54. package/src/presentation/screens/types/UserFeatureConfig.ts +20 -0
  55. package/src/presentation/screens/types/index.ts +1 -0
  56. package/src/presentation/screens/utils/normalizeConfig.ts +7 -1
  57. package/src/presentation/utils/accountConfigUtils.ts +67 -0
  58. package/src/presentation/utils/screenFactory.ts +87 -0
  59. package/src/presentation/utils/userProfileUtils.ts +51 -0
@@ -1,4 +1,4 @@
1
- import React, { useMemo } from 'react';
1
+ import { useMemo } from 'react';
2
2
  import type { StackScreen } from "@umituz/react-native-design-system";
3
3
  import { LanguageSelectionScreen } from "../../../domains/localization";
4
4
  import { NotificationSettingsScreen } from "../../../domains/notifications";
@@ -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";
@@ -2,14 +2,13 @@ import React, { useMemo } from "react";
2
2
  import { SettingsSection } from "../../../components/SettingsSection";
3
3
  import { SettingsItemCard } from "../../../components/SettingsItemCard";
4
4
  import type { CustomSettingsSection } from "../../types";
5
+ import { createSinglePropComparator } from "../../../../infrastructure/utils/memoComparisonUtils";
5
6
 
6
7
  interface CustomSettingsListProps {
7
8
  customSections?: CustomSettingsSection[];
8
9
  }
9
10
 
10
- export const CustomSettingsList: React.FC<CustomSettingsListProps> = ({
11
- customSections = [],
12
- }) => {
11
+ export const CustomSettingsList: React.FC<CustomSettingsListProps> = ({ customSections = [] }) => {
13
12
  const sortedSections = useMemo(() => {
14
13
  return Array.from(customSections)
15
14
  .sort((a: CustomSettingsSection, b: CustomSettingsSection) => (a.order ?? 999) - (b.order ?? 999));
@@ -25,9 +24,9 @@ export const CustomSettingsList: React.FC<CustomSettingsListProps> = ({
25
24
  title={section.title}
26
25
  >
27
26
  {section.content}
28
- {!section.content && section.items && section.items.length > 0 && section.items.map((item) => (
27
+ {!section.content && section.items && section.items.length > 0 && section.items.map((item, itemIndex) => (
29
28
  <SettingsItemCard
30
- key={item.id || `item-${index}`}
29
+ key={item.id || `item-${itemIndex}`}
31
30
  title={item.title}
32
31
  description={item.subtitle}
33
32
  icon={item.icon}
@@ -42,3 +41,11 @@ export const CustomSettingsList: React.FC<CustomSettingsListProps> = ({
42
41
  </>
43
42
  );
44
43
  };
44
+
45
+ CustomSettingsList.displayName = "CustomSettingsList";
46
+
47
+ export const MemoizedCustomSettingsList = React.memo(
48
+ CustomSettingsList,
49
+ createSinglePropComparator("customSections")
50
+ );
51
+ MemoizedCustomSettingsList.displayName = "MemoizedCustomSettingsList";
@@ -5,8 +5,8 @@ import { NotificationsSection } from "../../../../domains/notifications";
5
5
  import { useLocalization, getLanguageByCode } from "../../../../domains/localization";
6
6
  import { SettingsItemCard } from "../../../components/SettingsItemCard";
7
7
  import type { NormalizedConfig } from "../../utils/normalizeConfig";
8
-
9
8
  import { SettingsSection } from "../../../components/SettingsSection";
9
+ import { compareConfigAndFeatures } from "../../../../infrastructure/utils/memoComparisonUtils";
10
10
 
11
11
  interface FeatureSettingsSectionProps {
12
12
  normalizedConfig: NormalizedConfig;
@@ -24,23 +24,22 @@ export const FeatureSettingsSection: React.FC<FeatureSettingsSectionProps> = ({
24
24
  const { t, currentLanguage } = useLocalization();
25
25
  const navigation = useAppNavigation();
26
26
 
27
- const handleLanguagePress = () => {
27
+ const handleLanguagePress = React.useCallback(() => {
28
28
  if (normalizedConfig.language.config?.onPress) {
29
29
  normalizedConfig.language.config.onPress();
30
30
  } else {
31
- const route =
32
- normalizedConfig.language.config?.route || "LanguageSelection";
31
+ const route = normalizedConfig.language.config?.route || "LanguageSelection";
33
32
  navigation.navigate(route as never);
34
33
  }
35
- };
34
+ }, [navigation, normalizedConfig.language.config]);
36
35
 
37
- const currentLanguageData = getLanguageByCode(currentLanguage);
38
- const languageDisplayName = currentLanguageData
39
- ? `${currentLanguageData.flag} ${currentLanguageData.nativeName}`
40
- : currentLanguage;
36
+ const currentLanguageData = React.useMemo(() => getLanguageByCode(currentLanguage), [currentLanguage]);
37
+ const languageDisplayName = React.useMemo(() => {
38
+ if (!currentLanguageData) return currentLanguage;
39
+ return `${currentLanguageData.flag} ${currentLanguageData.nativeName}`;
40
+ }, [currentLanguageData, currentLanguage]);
41
41
 
42
- if (!features.appearance && !features.language && !features.notifications)
43
- return null;
42
+ if (!features.appearance && !features.language && !features.notifications) return null;
44
43
 
45
44
  return (
46
45
  <SettingsSection
@@ -85,3 +84,5 @@ export const FeatureSettingsSection: React.FC<FeatureSettingsSectionProps> = ({
85
84
  );
86
85
  };
87
86
 
87
+ export const MemoizedFeatureSettingsSection = React.memo(FeatureSettingsSection, compareConfigAndFeatures);
88
+ MemoizedFeatureSettingsSection.displayName = "MemoizedFeatureSettingsSection";
@@ -3,8 +3,8 @@ import { AboutSection } from "../../../../domains/about/presentation/components/
3
3
  import { LegalSection } from "../../../../domains/legal/presentation/components/LegalSection";
4
4
  import { useLocalization } from "../../../../domains/localization";
5
5
  import type { NormalizedConfig } from "../../utils/normalizeConfig";
6
-
7
6
  import { SettingsSection } from "../../../components/SettingsSection";
7
+ import { compareConfigAndFeatures } from "../../../../infrastructure/utils/memoComparisonUtils";
8
8
 
9
9
  interface IdentitySettingsSectionProps {
10
10
  normalizedConfig: NormalizedConfig;
@@ -48,4 +48,7 @@ export const IdentitySettingsSection: React.FC<IdentitySettingsSectionProps> = (
48
48
  );
49
49
  };
50
50
 
51
+ IdentitySettingsSection.displayName = "IdentitySettingsSection";
51
52
 
53
+ export const MemoizedIdentitySettingsSection = React.memo(IdentitySettingsSection, compareConfigAndFeatures);
54
+ MemoizedIdentitySettingsSection.displayName = "MemoizedIdentitySettingsSection";
@@ -3,6 +3,7 @@ import { View, StyleSheet } from "react-native";
3
3
  import { ProfileSection } from "@umituz/react-native-auth";
4
4
  import { useLocalization } from "../../../../domains/localization";
5
5
  import { useAppNavigation } from "@umituz/react-native-design-system";
6
+ import { createSinglePropComparator } from "../../../../infrastructure/utils/memoComparisonUtils";
6
7
 
7
8
  export interface ProfileSectionLoaderProps {
8
9
  userProfile?: {
@@ -16,24 +17,27 @@ export interface ProfileSectionLoaderProps {
16
17
  };
17
18
  }
18
19
 
19
-
20
- export const ProfileSectionLoader: React.FC<ProfileSectionLoaderProps> = ({ userProfile }) => {
20
+ export const ProfileSectionLoader: React.FC<ProfileSectionLoaderProps> = React.memo(({ userProfile }) => {
21
21
  const { t } = useLocalization();
22
22
  const navigation = useAppNavigation();
23
23
 
24
- if (!userProfile) return null;
25
-
26
- const handlePress = () => {
24
+ const handlePress = React.useCallback(() => {
25
+ if (!userProfile) return;
27
26
  if (userProfile.onPress) {
28
27
  userProfile.onPress();
29
28
  } else if (userProfile.accountSettingsRoute) {
30
29
  navigation.navigate(userProfile.accountSettingsRoute);
31
30
  }
32
- };
31
+ }, [navigation, userProfile]);
33
32
 
34
- const anonymousDisplayName = userProfile.isAnonymous && userProfile.userId
35
- ? `${t("profile.guest", "Guest")} ${userProfile.userId.substring(0, 8)}`
36
- : t("settings.profile.anonymousName", "Anonymous User");
33
+ const anonymousDisplayName = React.useMemo(() => {
34
+ if (!userProfile) return "";
35
+ return userProfile.isAnonymous && userProfile.userId
36
+ ? `${t("profile.guest", "Guest")} ${userProfile.userId.substring(0, 8)}`
37
+ : t("settings.profile.anonymousName", "Anonymous User");
38
+ }, [userProfile, t]);
39
+
40
+ if (!userProfile) return null;
37
41
 
38
42
  return (
39
43
  <View style={styles.profileContainer}>
@@ -53,7 +57,9 @@ export const ProfileSectionLoader: React.FC<ProfileSectionLoaderProps> = ({ user
53
57
  />
54
58
  </View>
55
59
  );
56
- };
60
+ }, createSinglePropComparator("userProfile"));
61
+
62
+ ProfileSectionLoader.displayName = "ProfileSectionLoader";
57
63
 
58
64
  const styles = StyleSheet.create({
59
65
  profileContainer: {
@@ -5,6 +5,7 @@ import { SupportSection } from "../../../../domains/feedback/presentation/compon
5
5
  import { SettingsSection } from "../../../components/SettingsSection";
6
6
  import { SettingsItemCard } from "../../../components/SettingsItemCard";
7
7
  import type { NormalizedConfig } from "../../utils/normalizeConfig";
8
+ import { compareConfigAndFeatures } from "../../../../infrastructure/utils/memoComparisonUtils";
8
9
 
9
10
  interface SupportSettingsSectionProps {
10
11
  features: { feedback: boolean; rating: boolean; faqs: boolean; [key: string]: boolean };
@@ -30,10 +31,10 @@ export const SupportSettingsSection: React.FC<SupportSettingsSectionProps> = ({
30
31
  <SupportSection
31
32
  renderSection={(props: { title: string; children: React.ReactNode }) => <>{props.children}</>}
32
33
  renderItem={(props: { title: string; icon: string; onPress: () => void; isLast?: boolean }) => (
33
- <SettingsItemCard
34
- title={props.title}
35
- icon={props.icon}
36
- onPress={props.onPress}
34
+ <SettingsItemCard
35
+ title={props.title}
36
+ icon={props.icon}
37
+ onPress={props.onPress}
37
38
  noBackground={true}
38
39
  hideMargin={true}
39
40
  />
@@ -98,3 +99,7 @@ export const SupportSettingsSection: React.FC<SupportSettingsSectionProps> = ({
98
99
  );
99
100
  };
100
101
 
102
+ SupportSettingsSection.displayName = "SupportSettingsSection";
103
+
104
+ export const MemoizedSupportSettingsSection = React.memo(SupportSettingsSection, compareConfigAndFeatures);
105
+ MemoizedSupportSettingsSection.displayName = "MemoizedSupportSettingsSection";
@@ -51,6 +51,7 @@ export function useFeatureDetection(
51
51
  subscription,
52
52
  wallet,
53
53
  gamification,
54
+ videoTutorial,
54
55
  } = normalizedConfig;
55
56
 
56
57
  const notificationServiceAvailable = !!options?.notificationServiceAvailable;
@@ -118,6 +119,7 @@ export function useFeatureDetection(
118
119
  subscription: subscription.enabled,
119
120
  wallet: wallet.enabled,
120
121
  gamification: gamification.enabled,
122
+ videoTutorial: videoTutorial.enabled,
121
123
  };
122
124
  }, [normalizedConfig, navigation, options]);
123
125
  }