@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.
- package/package.json +3 -3
- package/src/domains/about/presentation/hooks/useAboutInfo.ts +1 -3
- package/src/domains/appearance/presentation/components/ColorPicker.tsx +0 -1
- package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +0 -1
- package/src/domains/appearance/presentation/components/ThemeOption.tsx +0 -1
- package/src/domains/faqs/presentation/screens/FAQScreen.tsx +1 -1
- package/src/domains/feedback/presentation/components/FeedbackForm.styles.ts +1 -1
- package/src/domains/feedback/presentation/components/FeedbackForm.tsx +1 -2
- package/src/domains/feedback/presentation/components/FeedbackModal.tsx +12 -12
- package/src/domains/feedback/presentation/components/SupportSection.tsx +0 -1
- package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +1 -6
- package/src/domains/localization/infrastructure/components/useLanguageSwitcher.ts +0 -1
- package/src/domains/localization/infrastructure/hooks/useLanguageSelection.ts +0 -4
- package/src/domains/localization/infrastructure/hooks/useTranslation.ts +0 -1
- package/src/domains/localization/infrastructure/storage/LanguageInitializer.ts +0 -1
- package/src/domains/localization/infrastructure/storage/LanguageSwitcher.ts +0 -4
- package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +0 -5
- package/src/domains/localization/presentation/components/LanguageItem.tsx +0 -1
- package/src/domains/localization/presentation/providers/LocalizationManager.tsx +0 -1
- package/src/domains/localization/presentation/screens/LanguageSelectionScreen.tsx +0 -3
- package/src/domains/notifications/infrastructure/services/NotificationService.ts +0 -1
- package/src/domains/notifications/infrastructure/utils/dev.ts +3 -6
- package/src/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.ts +0 -1
- package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +0 -6
- package/src/domains/notifications/reminders/presentation/components/ReminderForm.constants.ts +1 -1
- package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +1 -6
- package/src/domains/rating/application/services/RatingService.ts +13 -63
- package/src/domains/rating/infrastructure/storage/RatingStorage.ts +14 -36
- package/src/domains/rating/presentation/hooks/useAppRating.tsx +0 -1
- package/src/infrastructure/utils/dateUtils.ts +61 -0
- package/src/infrastructure/utils/memoComparisonUtils.ts +66 -0
- package/src/infrastructure/utils/sanitizers.ts +49 -0
- package/src/infrastructure/utils/translationHelpers.ts +81 -0
- package/src/infrastructure/utils/validators.ts +59 -0
- package/src/presentation/components/SettingsItemCard.tsx +129 -172
- package/src/presentation/components/settings/SettingsItemCardContent.tsx +70 -0
- package/src/presentation/components/settings/SettingsItemCardRightElement.tsx +42 -0
- package/src/presentation/components/settings/SettingsItemCardSection.tsx +29 -0
- package/src/presentation/hooks/useSettingsScreenConfig.ts +19 -54
- package/src/presentation/navigation/SettingsStackNavigator.tsx +1 -24
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +76 -90
- package/src/presentation/screens/components/GamificationSettingsItem.tsx +20 -17
- package/src/presentation/screens/components/SettingsContent.tsx +29 -0
- package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +13 -4
- package/src/presentation/screens/components/VideoTutorialSettingsItem.tsx +47 -0
- package/src/presentation/screens/components/WalletSettingsItem.tsx +13 -4
- package/src/presentation/screens/components/sections/CustomSettingsList.tsx +12 -5
- package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +12 -11
- package/src/presentation/screens/components/sections/IdentitySettingsSection.tsx +4 -1
- package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +16 -10
- package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +9 -4
- package/src/presentation/screens/hooks/useFeatureDetection.ts +2 -0
- package/src/presentation/screens/types/SettingsConfig.ts +8 -1
- package/src/presentation/screens/types/UserFeatureConfig.ts +20 -0
- package/src/presentation/screens/types/index.ts +1 -0
- package/src/presentation/screens/utils/normalizeConfig.ts +7 -1
- package/src/presentation/utils/accountConfigUtils.ts +67 -0
- package/src/presentation/utils/screenFactory.ts +87 -0
- package/src/presentation/utils/userProfileUtils.ts +51 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
121
|
+
return combineScreens(
|
|
122
|
+
baseScreens,
|
|
123
|
+
faqScreen,
|
|
124
|
+
additionalStackScreens,
|
|
125
|
+
gamificationScreen,
|
|
138
126
|
languageScreen,
|
|
139
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
13
|
+
const SubscriptionSettingsItemComponent: React.FC<SubscriptionSettingsItemProps> = ({ config, t }) => {
|
|
13
14
|
const navigation = useAppNavigation();
|
|
14
|
-
|
|
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 "
|
|
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
|
-
|
|
13
|
+
const WalletSettingsItemComponent: React.FC<WalletSettingsItemProps> = ({ config, t }) => {
|
|
13
14
|
const navigation = useAppNavigation();
|
|
14
|
-
|
|
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-${
|
|
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 =
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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 =
|
|
35
|
-
|
|
36
|
-
|
|
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
|
}
|