@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.
- package/package.json +1 -1
- package/src/domains/about/presentation/hooks/useAboutInfo.ts +0 -2
- 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/feedback/presentation/components/FeedbackForm.tsx +0 -1
- package/src/domains/feedback/presentation/components/FeedbackModal.tsx +12 -12
- package/src/domains/feedback/presentation/components/SupportSection.tsx +0 -1
- 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 +0 -3
- 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.tsx +0 -5
- 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 +68 -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/hooks/useSettingsScreens.ts +75 -89
- 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
|
@@ -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(()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}, [userProfileData,
|
|
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
|
|
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";
|