@umituz/react-native-settings 4.21.18 → 4.21.20

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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/domains/about/presentation/components/AboutContent.tsx +3 -4
  3. package/src/domains/about/presentation/components/AboutHeader.tsx +7 -14
  4. package/src/domains/about/presentation/components/AboutSection.tsx +2 -2
  5. package/src/domains/about/presentation/components/AboutSettingItem.tsx +16 -16
  6. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +2 -2
  7. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +3 -4
  8. package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +2 -2
  9. package/src/domains/gamification/components/AchievementCard.tsx +26 -19
  10. package/src/domains/gamification/components/AchievementItem.tsx +27 -25
  11. package/src/domains/gamification/components/AchievementToast.tsx +16 -12
  12. package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +6 -5
  13. package/src/domains/gamification/components/GamificationScreen/Header.tsx +4 -3
  14. package/src/domains/gamification/components/GamificationScreen/StatsGrid.tsx +4 -3
  15. package/src/domains/gamification/components/GamificationScreen/index.tsx +38 -28
  16. package/src/domains/gamification/components/LevelProgress.tsx +25 -18
  17. package/src/domains/gamification/components/PointsBadge.tsx +13 -9
  18. package/src/domains/gamification/components/StatsCard.tsx +20 -14
  19. package/src/domains/gamification/components/StreakDisplay.tsx +28 -22
  20. package/src/domains/legal/presentation/components/LegalSection.tsx +2 -2
  21. package/src/domains/notifications/presentation/components/NotificationsSection.tsx +2 -4
  22. package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.tsx +3 -4
  23. package/src/domains/rating/presentation/components/StarRating.tsx +2 -2
  24. package/src/domains/video-tutorials/presentation/components/VideoTutorialCard.tsx +35 -127
  25. package/src/domains/video-tutorials/presentation/screens/VideoTutorialsScreen.tsx +88 -153
  26. package/src/presentation/components/SettingsItemCard.tsx +3 -22
  27. package/src/presentation/navigation/SettingsStackNavigator.tsx +112 -100
  28. package/src/presentation/screens/SettingsScreen.tsx +6 -2
  29. package/src/presentation/screens/components/GamificationSettingsItem.tsx +48 -0
  30. package/src/presentation/screens/components/SettingsContent.tsx +17 -41
  31. package/src/presentation/screens/components/SettingsHeader.tsx +2 -3
  32. package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +30 -0
  33. package/src/presentation/screens/components/WalletSettingsItem.tsx +28 -0
  34. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +2 -2
  35. package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +2 -2
  36. package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +2 -2
  37. package/src/presentation/screens/hooks/useFeatureDetection.ts +2 -0
  38. package/src/presentation/screens/types/SettingsConfig.ts +7 -0
  39. package/src/presentation/screens/types/UserFeatureConfig.ts +21 -0
  40. package/src/presentation/screens/types/index.ts +2 -0
  41. package/src/presentation/screens/utils/normalizeConfig.ts +6 -0
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { View, Text, Image, StyleSheet, TouchableOpacity } from "react-native";
8
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
7
+ import { View, Image, StyleSheet, TouchableOpacity } from "react-native";
8
+ import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
9
  import type { VideoTutorial } from "../../types";
10
10
 
11
11
  interface VideoTutorialCardProps {
@@ -32,10 +32,7 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
32
32
  <TouchableOpacity
33
33
  style={[
34
34
  styles.container,
35
- {
36
- backgroundColor: tokens.colors.surface,
37
- borderColor: tokens.colors.border,
38
- },
35
+ { backgroundColor: tokens.colors.surface, borderColor: tokens.colors.border },
39
36
  horizontal && styles.horizontalContainer,
40
37
  ]}
41
38
  onPress={onPress}
@@ -47,149 +44,60 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
47
44
  style={[styles.thumbnail, horizontal && styles.horizontalThumbnail]}
48
45
  resizeMode="cover"
49
46
  />
50
- <View
51
- style={[styles.durationBadge, { backgroundColor: "rgba(0,0,0,0.7)" }]}
52
- >
53
- <Text style={styles.durationText}>
54
- {formatDuration(tutorial.duration)}
55
- </Text>
47
+ <View style={[styles.durationBadge, { backgroundColor: "rgba(0,0,0,0.7)" }]}>
48
+ <AtomicText style={styles.durationText}>{formatDuration(tutorial.duration)}</AtomicText>
56
49
  </View>
57
50
  {tutorial.featured && (
58
- <View
59
- style={[
60
- styles.featuredBadge,
61
- { backgroundColor: tokens.colors.primary },
62
- ]}
63
- >
64
- <Text style={[styles.featuredText, { color: tokens.colors.onPrimary }]}>
65
- Featured
66
- </Text>
51
+ <View style={[styles.featuredBadge, { backgroundColor: tokens.colors.primary }]}>
52
+ <AtomicText style={[styles.featuredText, { color: tokens.colors.onPrimary }]}>Featured</AtomicText>
67
53
  </View>
68
54
  )}
69
55
  </View>
70
56
 
71
57
  <View style={styles.content}>
72
- <Text
73
- style={[
74
- styles.title,
75
- { color: tokens.colors.textPrimary },
76
- horizontal && styles.horizontalTitle,
77
- ]}
58
+ <AtomicText
59
+ style={[styles.title, horizontal && styles.horizontalTitle]}
78
60
  numberOfLines={2}
79
61
  >
80
62
  {tutorial.title}
81
- </Text>
63
+ </AtomicText>
82
64
 
83
- <Text
84
- style={[
85
- styles.description,
86
- { color: tokens.colors.textSecondary },
87
- horizontal && styles.horizontalDescription,
88
- ]}
65
+ <AtomicText
66
+ style={[styles.description, horizontal && styles.horizontalDescription]}
89
67
  numberOfLines={horizontal ? 2 : 3}
90
68
  >
91
69
  {tutorial.description}
92
- </Text>
70
+ </AtomicText>
93
71
 
94
72
  <View style={styles.metadata}>
95
- <Text
96
- style={[styles.category, { color: tokens.colors.textTertiary }]}
97
- >
73
+ <AtomicText style={styles.category}>
98
74
  {tutorial.category.replace("-", " ")}
99
- </Text>
100
- <Text
101
- style={[styles.difficulty, { color: tokens.colors.textSecondary }]}
102
- >
75
+ </AtomicText>
76
+ <AtomicText style={styles.difficulty}>
103
77
  {tutorial.difficulty}
104
- </Text>
78
+ </AtomicText>
105
79
  </View>
106
80
  </View>
107
81
  </TouchableOpacity>
108
82
  );
109
83
  };
110
84
 
111
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
112
- StyleSheet.create({
113
- container: {
114
- borderRadius: 12,
115
- borderWidth: 1,
116
- marginBottom: 12,
117
- overflow: "hidden",
118
- },
119
- horizontalContainer: {
120
- width: 280,
121
- marginRight: 12,
122
- marginBottom: 0,
123
- },
124
- imageContainer: {
125
- position: "relative",
126
- },
127
- thumbnail: {
128
- width: "100%",
129
- height: 180,
130
- },
131
- horizontalThumbnail: {
132
- height: 140,
133
- },
134
- durationBadge: {
135
- position: "absolute",
136
- bottom: 8,
137
- right: 8,
138
- paddingHorizontal: 6,
139
- paddingVertical: 2,
140
- borderRadius: 4,
141
- },
142
- durationText: {
143
- color: tokens.colors.textInverse,
144
- fontSize: 12,
145
- fontWeight: "500",
146
- },
147
- featuredBadge: {
148
- position: "absolute",
149
- top: 8,
150
- left: 8,
151
- paddingHorizontal: 8,
152
- paddingVertical: 4,
153
- borderRadius: 4,
154
- },
155
- featuredText: {
156
- fontSize: 11,
157
- fontWeight: "600",
158
- },
159
- content: {
160
- padding: 12,
161
- },
162
- title: {
163
- fontSize: tokens.typography.titleMedium.fontSize,
164
- fontWeight: "600",
165
- marginBottom: 6,
166
- },
167
- horizontalTitle: {
168
- fontSize: tokens.typography.bodyLarge.fontSize,
169
- },
170
- description: {
171
- fontSize: tokens.typography.bodyMedium.fontSize,
172
- lineHeight: 20,
173
- marginBottom: 8,
174
- },
175
- horizontalDescription: {
176
- fontSize: tokens.typography.bodySmall.fontSize,
177
- lineHeight: 16,
178
- marginBottom: 6,
179
- },
180
- metadata: {
181
- flexDirection: "row",
182
- justifyContent: "space-between",
183
- alignItems: "center",
184
- },
185
- category: {
186
- fontSize: 12,
187
- textTransform: "capitalize",
188
- fontWeight: "500",
189
- },
190
- difficulty: {
191
- fontSize: 12,
192
- textTransform: "capitalize",
193
- fontWeight: "500",
194
- },
85
+ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) => StyleSheet.create({
86
+ container: { borderRadius: 12, borderWidth: 1, marginBottom: 12, overflow: "hidden" },
87
+ horizontalContainer: { width: 280, marginRight: 12, marginBottom: 0 },
88
+ imageContainer: { position: "relative" },
89
+ thumbnail: { width: "100%", height: 180 },
90
+ horizontalThumbnail: { height: 140 },
91
+ durationBadge: { position: "absolute", bottom: 8, right: 8, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 },
92
+ durationText: { color: tokens.colors.textInverse, fontSize: 12, fontWeight: "500" },
93
+ featuredBadge: { position: "absolute", top: 8, left: 8, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 4 },
94
+ featuredText: { fontSize: 11, fontWeight: "600" },
95
+ content: { padding: 12 },
96
+ title: { fontSize: tokens.typography.titleMedium.fontSize, fontWeight: "600", marginBottom: 6, color: tokens.colors.textPrimary },
97
+ horizontalTitle: { fontSize: tokens.typography.bodyLarge.fontSize },
98
+ description: { fontSize: tokens.typography.bodyMedium.fontSize, lineHeight: 20, marginBottom: 8, color: tokens.colors.textSecondary },
99
+ horizontalDescription: { fontSize: tokens.typography.bodySmall.fontSize, lineHeight: 16, marginBottom: 6 },
100
+ metadata: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
101
+ category: { fontSize: 12, textTransform: "capitalize", fontWeight: "500", color: tokens.colors.textTertiary },
102
+ difficulty: { fontSize: 12, textTransform: "capitalize", fontWeight: "500", color: tokens.colors.textSecondary },
195
103
  });
@@ -23,176 +23,111 @@ import type { VideoTutorial } from "../../types";
23
23
  import { VideoTutorialCard } from "../components/VideoTutorialCard";
24
24
 
25
25
  export interface VideoTutorialsScreenProps {
26
- /**
27
- * All tutorials to display (required)
28
- */
29
26
  tutorials: VideoTutorial[];
30
- /**
31
- * Featured tutorials to display in horizontal scroll (optional)
32
- */
33
27
  featuredTutorials?: VideoTutorial[];
34
- /**
35
- * Title of the screen
36
- */
37
28
  title?: string;
38
- /**
39
- * Title for the featured tutorials section
40
- */
41
29
  featuredTitle?: string;
42
- /**
43
- * Title for all tutorials section
44
- */
45
30
  allTutorialsTitle?: string;
46
- /**
47
- * Empty state message when no tutorials
48
- */
49
31
  emptyMessage?: string;
50
- /**
51
- * Loading state (optional - defaults to false)
52
- */
53
32
  isLoading?: boolean;
54
- /**
55
- * Callback when a tutorial is pressed
56
- */
57
33
  onTutorialPress?: (tutorial: VideoTutorial) => void;
58
34
  }
59
35
 
60
- export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
61
- React.memo(
62
- ({
63
- tutorials,
64
- featuredTutorials,
65
- title = "Video Tutorials",
66
- featuredTitle = "Featured",
67
- allTutorialsTitle = "All Tutorials",
68
- emptyMessage = "No tutorials available.",
69
- isLoading = false,
70
- onTutorialPress,
71
- }) => {
72
- const tokens = useAppDesignTokens();
73
- const styles = getStyles(tokens);
36
+ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> = React.memo(
37
+ ({
38
+ tutorials,
39
+ featuredTutorials,
40
+ title = "Video Tutorials",
41
+ featuredTitle = "Featured",
42
+ allTutorialsTitle = "All Tutorials",
43
+ emptyMessage = "No tutorials available.",
44
+ isLoading = false,
45
+ onTutorialPress,
46
+ }) => {
47
+ const tokens = useAppDesignTokens();
48
+ const styles = getStyles(tokens);
74
49
 
75
- const handleTutorialPress = React.useCallback(
76
- (tutorial: VideoTutorial) => {
77
- onTutorialPress?.(tutorial);
78
- },
79
- [onTutorialPress],
80
- );
50
+ const handleTutorialPress = React.useCallback(
51
+ (tutorial: VideoTutorial) => onTutorialPress?.(tutorial),
52
+ [onTutorialPress]
53
+ );
81
54
 
82
- const renderTutorialItem = React.useCallback(
83
- ({ item }: { item: VideoTutorial }) => (
84
- <VideoTutorialCard
85
- tutorial={item}
86
- onPress={() => handleTutorialPress(item)}
87
- />
88
- ),
89
- [handleTutorialPress],
90
- );
55
+ const renderTutorialItem = React.useCallback(
56
+ ({ item }: { item: VideoTutorial }) => (
57
+ <VideoTutorialCard tutorial={item} onPress={() => handleTutorialPress(item)} />
58
+ ),
59
+ [handleTutorialPress]
60
+ );
91
61
 
92
- const renderFeaturedItem = React.useCallback(
93
- ({ item }: { item: VideoTutorial }) => (
94
- <VideoTutorialCard
95
- tutorial={item}
96
- onPress={() => handleTutorialPress(item)}
97
- horizontal
98
- />
99
- ),
100
- [handleTutorialPress],
101
- );
62
+ const renderFeaturedItem = React.useCallback(
63
+ ({ item }: { item: VideoTutorial }) => (
64
+ <VideoTutorialCard tutorial={item} onPress={() => handleTutorialPress(item)} horizontal />
65
+ ),
66
+ [handleTutorialPress]
67
+ );
102
68
 
103
- if (isLoading) {
104
- return <AtomicSpinner size="lg" fullContainer />;
105
- }
69
+ if (isLoading) return <AtomicSpinner size="lg" fullContainer />;
106
70
 
107
- const hasFeatured = featuredTutorials && featuredTutorials.length > 0;
108
- const hasTutorials = tutorials && tutorials.length > 0;
109
-
110
- if (!hasTutorials && !hasFeatured) {
111
- return (
112
- <View style={styles.emptyContainer}>
113
- <AtomicText color="secondary" type="bodyLarge">
114
- {emptyMessage}
115
- </AtomicText>
116
- </View>
117
- );
118
- }
71
+ const hasFeatured = featuredTutorials && featuredTutorials.length > 0;
72
+ const hasTutorials = tutorials && tutorials.length > 0;
119
73
 
74
+ if (!hasTutorials && !hasFeatured) {
120
75
  return (
121
- <ScreenLayout scrollable={false} edges={["top", "bottom"]}>
122
- <Text style={[styles.title, { color: tokens.colors.textPrimary }]}>
123
- {title}
124
- </Text>
76
+ <View style={styles.emptyContainer}>
77
+ <AtomicText color="secondary" type="bodyLarge">{emptyMessage}</AtomicText>
78
+ </View>
79
+ );
80
+ }
125
81
 
126
- {hasFeatured && (
127
- <View style={styles.section}>
128
- <Text
129
- style={[
130
- styles.sectionTitle,
131
- { color: tokens.colors.textSecondary },
132
- ]}
133
- >
134
- {featuredTitle}
135
- </Text>
136
- <FlatList
137
- data={featuredTutorials}
138
- renderItem={renderFeaturedItem}
139
- keyExtractor={(item: VideoTutorial) => item.id}
140
- horizontal
141
- showsHorizontalScrollIndicator={false}
142
- contentContainerStyle={styles.horizontalList}
143
- />
144
- </View>
145
- )}
82
+ return (
83
+ <ScreenLayout scrollable={false} edges={["top", "bottom"]}>
84
+ <AtomicText style={styles.title}>{title}</AtomicText>
146
85
 
147
- {hasTutorials && (
148
- <View style={styles.section}>
149
- <Text
150
- style={[
151
- styles.sectionTitle,
152
- { color: tokens.colors.textSecondary },
153
- ]}
154
- >
155
- {allTutorialsTitle}
156
- </Text>
157
- <FlatList
158
- data={tutorials}
159
- renderItem={renderTutorialItem}
160
- keyExtractor={(item: VideoTutorial) => item.id}
161
- showsVerticalScrollIndicator={false}
162
- contentContainerStyle={styles.verticalList}
163
- />
164
- </View>
165
- )}
166
- </ScreenLayout>
167
- );
168
- },
169
- );
86
+ {hasFeatured && (
87
+ <View style={styles.section}>
88
+ <AtomicText color="secondary" style={styles.sectionTitle}>{featuredTitle}</AtomicText>
89
+ <FlatList
90
+ data={featuredTutorials}
91
+ renderItem={renderFeaturedItem}
92
+ keyExtractor={(item) => item.id}
93
+ horizontal
94
+ showsHorizontalScrollIndicator={false}
95
+ contentContainerStyle={styles.horizontalList}
96
+ />
97
+ </View>
98
+ )}
99
+
100
+ {hasTutorials && (
101
+ <View style={styles.section}>
102
+ <AtomicText color="secondary" style={styles.sectionTitle}>{allTutorialsTitle}</AtomicText>
103
+ <FlatList
104
+ data={tutorials}
105
+ renderItem={renderTutorialItem}
106
+ keyExtractor={(item) => item.id}
107
+ showsVerticalScrollIndicator={false}
108
+ contentContainerStyle={styles.verticalList}
109
+ />
110
+ </View>
111
+ )}
112
+ </ScreenLayout>
113
+ );
114
+ }
115
+ );
170
116
 
171
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
172
- StyleSheet.create({
173
- title: {
174
- fontSize: tokens.typography.headlineLarge.fontSize,
175
- fontWeight: "600",
176
- marginBottom: 24,
177
- },
178
- section: {
179
- marginBottom: 24,
180
- },
181
- sectionTitle: {
182
- fontSize: tokens.typography.titleLarge.fontSize,
183
- fontWeight: "500",
184
- marginBottom: 12,
185
- },
186
- horizontalList: {
187
- paddingRight: 16,
188
- },
189
- verticalList: {
190
- paddingBottom: 16,
191
- },
192
- emptyContainer: {
193
- flex: 1,
194
- justifyContent: "center",
195
- alignItems: "center",
196
- padding: 20,
197
- },
198
- });
117
+ const getStyles = (tokens: any) => StyleSheet.create({
118
+ title: {
119
+ fontSize: tokens.typography.headlineLarge.fontSize,
120
+ color: tokens.colors.textPrimary,
121
+ fontWeight: "600",
122
+ marginBottom: 24,
123
+ },
124
+ section: { marginBottom: 24 },
125
+ sectionTitle: {
126
+ fontSize: tokens.typography.titleLarge.fontSize,
127
+ fontWeight: "500",
128
+ marginBottom: 12,
129
+ },
130
+ horizontalList: { paddingRight: 16 },
131
+ verticalList: { paddingBottom: 16 },
132
+ emptyContainer: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20 },
133
+ });
@@ -5,45 +5,26 @@ import {
5
5
  AtomicIcon,
6
6
  AtomicText,
7
7
  type IconName,
8
+ withAlpha
8
9
  } from "@umituz/react-native-design-system";
9
10
 
10
11
  export interface SettingsItemCardProps {
11
- /** Item title */
12
12
  title: string;
13
- /** Optional description or current value */
14
13
  description?: string;
15
- /** Icon name from AtomicIcon */
16
14
  icon: IconName;
17
- /** On press handler - if undefined, item is not clickable */
18
15
  onPress?: () => void;
19
- /** Optional container style */
20
16
  containerStyle?: ViewStyle;
21
- /** Optional section title (shows above the card) */
22
17
  sectionTitle?: string;
23
- /** Optional right icon (defaults to chevron-forward, hidden if not clickable) */
24
18
  rightIcon?: IconName;
25
- /** Optional icon background color (defaults to primary with opacity) */
26
19
  iconBgColor?: string;
27
- /** Optional icon color */
28
20
  iconColor?: string;
29
- /** Show chevron even if not clickable */
30
21
  showChevron?: boolean;
31
- /** Show switch instead of chevron */
32
22
  showSwitch?: boolean;
33
- /** Switch value (when showSwitch is true) */
34
23
  switchValue?: boolean;
35
- /** Switch change handler */
36
24
  onSwitchChange?: (value: boolean) => void;
37
- /** Disable the item */
38
25
  disabled?: boolean;
39
26
  }
40
27
 
41
- /**
42
- * SettingsItemCard
43
- *
44
- * A premium, consistent card for settings items used across all apps.
45
- * Follows the visual style of the Appearance section.
46
- */
47
28
  export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
48
29
  title,
49
30
  description,
@@ -63,7 +44,7 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
63
44
  const tokens = useAppDesignTokens();
64
45
  const colors = tokens.colors;
65
46
 
66
- const defaultIconBg = iconBgColor || `${colors.primary}15`;
47
+ const defaultIconBg = iconBgColor || withAlpha(colors.primary, 0.15);
67
48
  const defaultIconColor = iconColor || colors.primary;
68
49
  const isClickable = !!onPress && !showSwitch;
69
50
  const shouldShowChevron = !showSwitch && (showChevron ?? isClickable);
@@ -137,7 +118,7 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
137
118
  style={({ pressed }) => [
138
119
  styles.itemContainer,
139
120
  {
140
- backgroundColor: pressed ? `${colors.primary}08` : "transparent",
121
+ backgroundColor: pressed ? withAlpha(colors.primary, 0.08) : "transparent",
141
122
  },
142
123
  ]}
143
124
  onPress={onPress}