@umituz/react-native-settings 4.23.82 → 4.23.83
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/disclaimer/presentation/screens/DisclaimerScreen.tsx +15 -1
- package/src/domains/faqs/presentation/screens/FAQScreen.tsx +25 -23
- package/src/domains/feedback/presentation/components/FeedbackForm.styles.ts +51 -0
- package/src/domains/feedback/presentation/components/FeedbackForm.tsx +3 -49
- package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +54 -57
- package/src/domains/legal/presentation/screens/LegalContentScreen.tsx +8 -8
- package/src/domains/legal/presentation/screens/LegalScreen.tsx +14 -3
- package/src/domains/notifications/index.ts +1 -1
- package/src/domains/notifications/presentation/screens/NotificationsScreen.tsx +53 -26
- package/src/domains/notifications/reminders/presentation/components/ReminderForm.constants.ts +35 -0
- package/src/domains/notifications/reminders/presentation/components/ReminderForm.styles.ts +22 -0
- package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +13 -57
- package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +25 -10
- package/src/domains/video-tutorials/presentation/screens/VideoTutorialsScreen.tsx +17 -6
- package/src/presentation/components/SettingsFooter.tsx +3 -3
- package/src/presentation/navigation/SettingsStackNavigator.tsx +32 -174
- package/src/presentation/navigation/hooks/index.ts +1 -0
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +163 -0
- package/src/presentation/screens/SettingsScreen.tsx +0 -1
- package/src/presentation/screens/components/SettingsHeader.tsx +2 -5
- package/src/utils/appUtils.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.83",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
import React from 'react';
|
|
16
16
|
import { View, StyleSheet } from 'react-native';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
useAppDesignTokens,
|
|
19
|
+
withAlpha,
|
|
20
|
+
ScreenLayout,
|
|
21
|
+
useAppNavigation,
|
|
22
|
+
NavigationHeader
|
|
23
|
+
} from '@umituz/react-native-design-system';
|
|
18
24
|
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
19
25
|
import type { IconName } from '@umituz/react-native-design-system';
|
|
20
26
|
import { useLocalization } from '../../../localization';
|
|
@@ -46,12 +52,20 @@ export const DisclaimerScreen: React.FC<DisclaimerScreenProps> = ({
|
|
|
46
52
|
const displayTitle = title || t(titleKey);
|
|
47
53
|
const displayContent = content || t(contentKey);
|
|
48
54
|
|
|
55
|
+
const navigation = useAppNavigation();
|
|
56
|
+
|
|
49
57
|
return (
|
|
50
58
|
<ScreenLayout
|
|
51
59
|
scrollable={true}
|
|
52
60
|
edges={['bottom']}
|
|
53
61
|
contentContainerStyle={styles.scrollContent}
|
|
54
62
|
hideScrollIndicator={false}
|
|
63
|
+
header={
|
|
64
|
+
<NavigationHeader
|
|
65
|
+
title={displayTitle}
|
|
66
|
+
onBackPress={() => navigation.goBack()}
|
|
67
|
+
/>
|
|
68
|
+
}
|
|
55
69
|
>
|
|
56
70
|
{/* Icon Header */}
|
|
57
71
|
<View style={styles.iconHeader}>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import React, { useMemo } from 'react';
|
|
8
8
|
import { View, ScrollView, StyleSheet, ViewStyle, TextStyle, useWindowDimensions } from 'react-native';
|
|
9
|
-
import { useAppDesignTokens, AtomicText, ScreenLayout, getContentMaxWidth } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens, AtomicText, ScreenLayout, getContentMaxWidth, NavigationHeader, useAppNavigation } from '@umituz/react-native-design-system';
|
|
10
10
|
import { FAQCategory } from '../../domain/entities/FAQEntity';
|
|
11
11
|
import { useFAQSearch } from '../hooks/useFAQSearch';
|
|
12
12
|
import { useFAQExpansion } from '../hooks/useFAQExpansion';
|
|
@@ -45,6 +45,7 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
|
|
|
45
45
|
renderHeader,
|
|
46
46
|
styles: customStyles,
|
|
47
47
|
}) => {
|
|
48
|
+
const navigation = useAppNavigation();
|
|
48
49
|
const tokens = useAppDesignTokens();
|
|
49
50
|
const { width: windowWidth } = useWindowDimensions();
|
|
50
51
|
const contentMaxWidth = useMemo(() => getContentMaxWidth(windowWidth), [windowWidth]);
|
|
@@ -67,6 +68,21 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
|
|
|
67
68
|
[tokens]
|
|
68
69
|
);
|
|
69
70
|
|
|
71
|
+
const handleBack = () => {
|
|
72
|
+
if (onBack) {
|
|
73
|
+
onBack();
|
|
74
|
+
} else {
|
|
75
|
+
navigation.goBack();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const header = renderHeader ? renderHeader({ onBack: handleBack }) : (
|
|
80
|
+
<NavigationHeader
|
|
81
|
+
title={headerTitle}
|
|
82
|
+
onBackPress={handleBack}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
|
|
70
86
|
const renderContent = () => {
|
|
71
87
|
if (searchQuery && !hasResults) {
|
|
72
88
|
return (
|
|
@@ -81,13 +97,6 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
|
|
|
81
97
|
return (
|
|
82
98
|
<View style={{ flex: 1 }}>
|
|
83
99
|
<View style={[styles.header, customStyles?.header]}>
|
|
84
|
-
<AtomicText
|
|
85
|
-
type="headlineMedium"
|
|
86
|
-
color="textPrimary"
|
|
87
|
-
style={{ marginBottom: tokens.spacing.md, fontWeight: '700' }}
|
|
88
|
-
>
|
|
89
|
-
{headerTitle}
|
|
90
|
-
</AtomicText>
|
|
91
100
|
<FAQSearchBar
|
|
92
101
|
value={searchQuery}
|
|
93
102
|
onChangeText={setSearchQuery}
|
|
@@ -116,23 +125,16 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
|
|
|
116
125
|
);
|
|
117
126
|
};
|
|
118
127
|
|
|
119
|
-
if (renderHeader && onBack) {
|
|
120
|
-
return (
|
|
121
|
-
<View style={{ flex: 1, backgroundColor: tokens.colors.backgroundPrimary }}>
|
|
122
|
-
<View style={[styles.container, customStyles?.container]}>
|
|
123
|
-
<View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth }}>
|
|
124
|
-
{renderHeader({ onBack })}
|
|
125
|
-
</View>
|
|
126
|
-
{renderContent()}
|
|
127
|
-
</View>
|
|
128
|
-
</View>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
128
|
return (
|
|
133
|
-
<ScreenLayout
|
|
129
|
+
<ScreenLayout
|
|
130
|
+
edges={['bottom']}
|
|
131
|
+
scrollable={false}
|
|
132
|
+
header={header}
|
|
133
|
+
>
|
|
134
134
|
<View style={[styles.container, customStyles?.container]}>
|
|
135
|
-
{
|
|
135
|
+
<View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth, flex: 1 }}>
|
|
136
|
+
{renderContent()}
|
|
137
|
+
</View>
|
|
136
138
|
</View>
|
|
137
139
|
</ScreenLayout>
|
|
138
140
|
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import type { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
3
|
+
|
|
4
|
+
export const getFeedbackFormStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
5
|
+
StyleSheet.create({
|
|
6
|
+
container: {
|
|
7
|
+
width: "100%",
|
|
8
|
+
},
|
|
9
|
+
typeContainer: {
|
|
10
|
+
marginBottom: 24,
|
|
11
|
+
},
|
|
12
|
+
typeScroll: {
|
|
13
|
+
gap: 8,
|
|
14
|
+
},
|
|
15
|
+
typeButton: {
|
|
16
|
+
flexDirection: "row",
|
|
17
|
+
alignItems: "center",
|
|
18
|
+
paddingHorizontal: 16,
|
|
19
|
+
paddingVertical: 8,
|
|
20
|
+
borderRadius: 20,
|
|
21
|
+
borderWidth: 1,
|
|
22
|
+
gap: 6,
|
|
23
|
+
},
|
|
24
|
+
ratingContainer: {
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
marginBottom: 24,
|
|
27
|
+
},
|
|
28
|
+
stars: {
|
|
29
|
+
flexDirection: "row",
|
|
30
|
+
gap: 8,
|
|
31
|
+
},
|
|
32
|
+
starButton: {
|
|
33
|
+
padding: 4,
|
|
34
|
+
},
|
|
35
|
+
inputContainer: {
|
|
36
|
+
marginBottom: 24,
|
|
37
|
+
},
|
|
38
|
+
textArea: {
|
|
39
|
+
textAlignVertical: "top",
|
|
40
|
+
minHeight: 120,
|
|
41
|
+
borderWidth: 1,
|
|
42
|
+
borderRadius: 8,
|
|
43
|
+
padding: 12,
|
|
44
|
+
},
|
|
45
|
+
errorText: {
|
|
46
|
+
marginTop: 8,
|
|
47
|
+
},
|
|
48
|
+
submitButton: {
|
|
49
|
+
width: "100%",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -8,6 +8,8 @@ import { View, StyleSheet, TouchableOpacity, ScrollView, TextInput } from "react
|
|
|
8
8
|
import { useAppDesignTokens, AtomicText, AtomicButton, AtomicIcon } from "@umituz/react-native-design-system";
|
|
9
9
|
import type { FeedbackType, FeedbackRating } from "../../domain/entities/FeedbackEntity";
|
|
10
10
|
|
|
11
|
+
import { getFeedbackFormStyles as getStyles } from "./FeedbackForm.styles";
|
|
12
|
+
|
|
11
13
|
export interface FeedbackFormProps {
|
|
12
14
|
onSubmit: (data: { type: FeedbackType; rating: FeedbackRating; description: string; title: string }) => Promise<void>;
|
|
13
15
|
texts: {
|
|
@@ -151,6 +153,7 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
|
|
|
151
153
|
color: tokens.colors.textPrimary,
|
|
152
154
|
backgroundColor: tokens.colors.surface,
|
|
153
155
|
borderColor: error ? tokens.colors.error : tokens.colors.border,
|
|
156
|
+
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
154
157
|
}
|
|
155
158
|
]}
|
|
156
159
|
/>
|
|
@@ -176,52 +179,3 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
|
|
|
176
179
|
);
|
|
177
180
|
};
|
|
178
181
|
|
|
179
|
-
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
180
|
-
StyleSheet.create({
|
|
181
|
-
container: {
|
|
182
|
-
width: "100%",
|
|
183
|
-
},
|
|
184
|
-
typeContainer: {
|
|
185
|
-
marginBottom: 24,
|
|
186
|
-
},
|
|
187
|
-
typeScroll: {
|
|
188
|
-
gap: 8,
|
|
189
|
-
},
|
|
190
|
-
typeButton: {
|
|
191
|
-
flexDirection: "row",
|
|
192
|
-
alignItems: "center",
|
|
193
|
-
paddingHorizontal: 16,
|
|
194
|
-
paddingVertical: 8,
|
|
195
|
-
borderRadius: 20,
|
|
196
|
-
borderWidth: 1,
|
|
197
|
-
gap: 6,
|
|
198
|
-
},
|
|
199
|
-
ratingContainer: {
|
|
200
|
-
alignItems: "center",
|
|
201
|
-
marginBottom: 24,
|
|
202
|
-
},
|
|
203
|
-
stars: {
|
|
204
|
-
flexDirection: "row",
|
|
205
|
-
gap: 8,
|
|
206
|
-
},
|
|
207
|
-
starButton: {
|
|
208
|
-
padding: 4,
|
|
209
|
-
},
|
|
210
|
-
inputContainer: {
|
|
211
|
-
marginBottom: 24,
|
|
212
|
-
},
|
|
213
|
-
textArea: {
|
|
214
|
-
textAlignVertical: "top",
|
|
215
|
-
minHeight: 120,
|
|
216
|
-
borderWidth: 1,
|
|
217
|
-
borderRadius: 8,
|
|
218
|
-
padding: 12,
|
|
219
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
220
|
-
},
|
|
221
|
-
errorText: {
|
|
222
|
-
marginTop: 8,
|
|
223
|
-
},
|
|
224
|
-
submitButton: {
|
|
225
|
-
width: "100%",
|
|
226
|
-
},
|
|
227
|
-
});
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { View, ScrollView } from "react-native";
|
|
9
|
-
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
+
import { useAppDesignTokens, AtomicText, ScreenLayout, NavigationHeader, useAppNavigation } from "@umituz/react-native-design-system";
|
|
10
10
|
import { LevelProgress } from "../LevelProgress";
|
|
11
11
|
import { StreakDisplay } from "../StreakDisplay";
|
|
12
|
-
import { Header } from "./Header";
|
|
13
12
|
import { StatsGrid } from "./StatsGrid";
|
|
14
13
|
import { AchievementsList } from "./AchievementsList";
|
|
15
14
|
import { styles } from "./styles";
|
|
@@ -39,6 +38,7 @@ export const GamificationScreenInner: React.FC<GamificationScreenProps> = ({
|
|
|
39
38
|
subtextColor,
|
|
40
39
|
headerComponent,
|
|
41
40
|
}) => {
|
|
41
|
+
const navigation = useAppNavigation();
|
|
42
42
|
const tokens = useAppDesignTokens();
|
|
43
43
|
|
|
44
44
|
// Use tokens for fallbacks
|
|
@@ -48,75 +48,72 @@ export const GamificationScreenInner: React.FC<GamificationScreenProps> = ({
|
|
|
48
48
|
const finalTextColor = textColor || tokens.colors.textPrimary;
|
|
49
49
|
const finalSubtextColor = subtextColor || tokens.colors.textSecondary;
|
|
50
50
|
|
|
51
|
+
const header = (
|
|
52
|
+
<NavigationHeader
|
|
53
|
+
title={title}
|
|
54
|
+
onBackPress={() => navigation.goBack()}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
51
58
|
return (
|
|
52
|
-
<
|
|
59
|
+
<ScreenLayout
|
|
60
|
+
header={header}
|
|
61
|
+
contentContainerStyle={styles.scrollContent}
|
|
62
|
+
hideScrollIndicator={false}
|
|
63
|
+
>
|
|
53
64
|
{headerComponent}
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<Header
|
|
62
|
-
title={title}
|
|
63
|
-
headerStyle={headerStyle}
|
|
64
|
-
titleStyle={titleStyle}
|
|
66
|
+
{/* Level Progress */}
|
|
67
|
+
<View style={styles.section}>
|
|
68
|
+
<LevelProgress
|
|
69
|
+
{...levelProps}
|
|
70
|
+
primaryColor={finalAccentColor}
|
|
71
|
+
backgroundColor={finalCardBackgroundColor}
|
|
65
72
|
textColor={finalTextColor}
|
|
73
|
+
subtextColor={finalSubtextColor}
|
|
66
74
|
/>
|
|
75
|
+
</View>
|
|
67
76
|
|
|
68
|
-
|
|
77
|
+
{/* Streak (if provided) */}
|
|
78
|
+
{streakProps && (
|
|
69
79
|
<View style={styles.section}>
|
|
70
|
-
<
|
|
71
|
-
{
|
|
80
|
+
<AtomicText
|
|
81
|
+
style={[styles.sectionTitle, { color: finalTextColor }, sectionTitleStyle]}
|
|
82
|
+
>
|
|
83
|
+
{streakTitle}
|
|
84
|
+
</AtomicText>
|
|
85
|
+
<StreakDisplay
|
|
86
|
+
{...streakProps}
|
|
72
87
|
primaryColor={finalAccentColor}
|
|
73
88
|
backgroundColor={finalCardBackgroundColor}
|
|
74
89
|
textColor={finalTextColor}
|
|
75
90
|
subtextColor={finalSubtextColor}
|
|
76
91
|
/>
|
|
77
92
|
</View>
|
|
93
|
+
)}
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
primaryColor={finalAccentColor}
|
|
90
|
-
backgroundColor={finalCardBackgroundColor}
|
|
91
|
-
textColor={finalTextColor}
|
|
92
|
-
subtextColor={finalSubtextColor}
|
|
93
|
-
/>
|
|
94
|
-
</View>
|
|
95
|
-
)}
|
|
96
|
-
|
|
97
|
-
{/* Stats Grid */}
|
|
98
|
-
<StatsGrid
|
|
99
|
-
statsTitle={statsTitle}
|
|
100
|
-
stats={stats}
|
|
101
|
-
accentColor={finalAccentColor}
|
|
102
|
-
cardBackgroundColor={finalCardBackgroundColor}
|
|
103
|
-
textColor={finalTextColor}
|
|
104
|
-
subtextColor={finalSubtextColor}
|
|
105
|
-
sectionTitleStyle={sectionTitleStyle}
|
|
106
|
-
/>
|
|
95
|
+
{/* Stats Grid */}
|
|
96
|
+
<StatsGrid
|
|
97
|
+
statsTitle={statsTitle}
|
|
98
|
+
stats={stats}
|
|
99
|
+
accentColor={finalAccentColor}
|
|
100
|
+
cardBackgroundColor={finalCardBackgroundColor}
|
|
101
|
+
textColor={finalTextColor}
|
|
102
|
+
subtextColor={finalSubtextColor}
|
|
103
|
+
sectionTitleStyle={sectionTitleStyle}
|
|
104
|
+
/>
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
</View>
|
|
106
|
+
{/* Achievements */}
|
|
107
|
+
<AchievementsList
|
|
108
|
+
achievementsTitle={achievementsTitle}
|
|
109
|
+
achievements={achievements}
|
|
110
|
+
emptyAchievementsText={emptyAchievementsText}
|
|
111
|
+
accentColor={finalAccentColor}
|
|
112
|
+
cardBackgroundColor={finalCardBackgroundColor}
|
|
113
|
+
textColor={finalTextColor}
|
|
114
|
+
subtextColor={finalSubtextColor}
|
|
115
|
+
sectionTitleStyle={sectionTitleStyle}
|
|
116
|
+
/>
|
|
117
|
+
</ScreenLayout>
|
|
121
118
|
);
|
|
122
119
|
};
|
|
@@ -9,6 +9,7 @@ import { AtomicText, AtomicButton } from "@umituz/react-native-design-system";
|
|
|
9
9
|
import { UrlHandlerService } from "../../domain/services/UrlHandlerService";
|
|
10
10
|
import { ContentValidationService } from "../../domain/services/ContentValidationService";
|
|
11
11
|
import { StyleCacheService } from "../../domain/services/StyleCacheService";
|
|
12
|
+
import { useAppNavigation, NavigationHeader } from "@umituz/react-native-design-system";
|
|
12
13
|
|
|
13
14
|
export interface LegalContentScreenProps {
|
|
14
15
|
content?: string;
|
|
@@ -34,6 +35,7 @@ export const LegalContentScreen: React.FC<LegalContentScreenProps> = React.memo(
|
|
|
34
35
|
createStyles,
|
|
35
36
|
}) => {
|
|
36
37
|
const tokens = useAppDesignTokens();
|
|
38
|
+
const navigation = useAppNavigation();
|
|
37
39
|
|
|
38
40
|
const styles = React.useMemo(() => {
|
|
39
41
|
const cacheKey = StyleCacheService.createTokenCacheKey(tokens);
|
|
@@ -101,17 +103,15 @@ export const LegalContentScreen: React.FC<LegalContentScreenProps> = React.memo(
|
|
|
101
103
|
testID={testID}
|
|
102
104
|
scrollable={true}
|
|
103
105
|
hideScrollIndicator={false}
|
|
106
|
+
header={
|
|
107
|
+
<NavigationHeader
|
|
108
|
+
title={title}
|
|
109
|
+
onBackPress={() => navigation.goBack()}
|
|
110
|
+
/>
|
|
111
|
+
}
|
|
104
112
|
contentContainerStyle={styles.scrollContent}
|
|
105
113
|
>
|
|
106
114
|
<View style={styles.content}>
|
|
107
|
-
<AtomicText
|
|
108
|
-
type="headlineLarge"
|
|
109
|
-
color="primary"
|
|
110
|
-
style={styles.title}
|
|
111
|
-
>
|
|
112
|
-
{title}
|
|
113
|
-
</AtomicText>
|
|
114
|
-
|
|
115
115
|
{contentSection}
|
|
116
116
|
</View>
|
|
117
117
|
</ScreenLayout>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from "react";
|
|
9
|
-
import { ScreenLayout } from "@umituz/react-native-design-system";
|
|
9
|
+
import { ScreenLayout, NavigationHeader, useAppNavigation } from "@umituz/react-native-design-system";
|
|
10
10
|
import { LegalScreenHeader } from "../components/LegalScreenHeader";
|
|
11
11
|
import { LegalDocumentsList } from "../components/LegalDocumentsList";
|
|
12
12
|
|
|
@@ -45,10 +45,21 @@ export const LegalScreen: React.FC<LegalScreenProps> = React.memo((props) => {
|
|
|
45
45
|
testID = "legal-screen",
|
|
46
46
|
} = props;
|
|
47
47
|
|
|
48
|
+
const navigation = useAppNavigation();
|
|
49
|
+
|
|
48
50
|
return (
|
|
49
|
-
<ScreenLayout
|
|
51
|
+
<ScreenLayout
|
|
52
|
+
testID={testID}
|
|
53
|
+
hideScrollIndicator
|
|
54
|
+
header={
|
|
55
|
+
<NavigationHeader
|
|
56
|
+
title={title || ""}
|
|
57
|
+
onBackPress={() => navigation.goBack()}
|
|
58
|
+
/>
|
|
59
|
+
}
|
|
60
|
+
>
|
|
50
61
|
<LegalScreenHeader title={title} description={description} />
|
|
51
|
-
|
|
62
|
+
|
|
52
63
|
<LegalDocumentsList
|
|
53
64
|
documentsHeader={documentsHeader}
|
|
54
65
|
privacyTitle={privacyTitle}
|
|
@@ -110,7 +110,7 @@ export { ReminderItem } from './reminders/presentation/components/ReminderItem';
|
|
|
110
110
|
export type { ReminderItemProps, ReminderItemTranslations } from './reminders/presentation/components/ReminderItem';
|
|
111
111
|
|
|
112
112
|
export { ReminderForm } from './reminders/presentation/components/ReminderForm';
|
|
113
|
-
export type { ReminderFormProps, ReminderFormTranslations } from './reminders/presentation/components/ReminderForm';
|
|
113
|
+
export type { ReminderFormProps, ReminderFormTranslations } from './reminders/presentation/components/ReminderForm.constants';
|
|
114
114
|
|
|
115
115
|
export { FormButton } from './reminders/presentation/components/FormButton';
|
|
116
116
|
export type { FormButtonProps } from './reminders/presentation/components/FormButton';
|
|
@@ -7,11 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
import React, { useMemo } from 'react';
|
|
9
9
|
import { View, StyleSheet } from 'react-native';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AtomicIcon,
|
|
12
|
+
AtomicCard,
|
|
13
|
+
AtomicText,
|
|
14
|
+
ScreenLayout,
|
|
15
|
+
BASE_TOKENS,
|
|
16
|
+
AtomicSpinner,
|
|
17
|
+
type IconColor,
|
|
18
|
+
NavigationHeader,
|
|
19
|
+
useAppNavigation,
|
|
20
|
+
useAppDesignTokens,
|
|
21
|
+
type DesignTokens
|
|
22
|
+
} from '@umituz/react-native-design-system';
|
|
11
23
|
import { Switch } from 'react-native';
|
|
12
|
-
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
13
24
|
import { useNotificationSettings } from '../../infrastructure/hooks/useNotificationSettings';
|
|
14
|
-
import type { DesignTokens } from '@umituz/react-native-design-system';
|
|
15
25
|
|
|
16
26
|
export interface NotificationsScreenProps {
|
|
17
27
|
translations: {
|
|
@@ -30,13 +40,21 @@ export const NotificationsScreen: React.FC<NotificationsScreenProps> = ({
|
|
|
30
40
|
iconColor = 'primary',
|
|
31
41
|
testID = 'notifications-screen',
|
|
32
42
|
}) => {
|
|
43
|
+
const navigation = useAppNavigation();
|
|
33
44
|
const tokens = useAppDesignTokens();
|
|
34
45
|
const styles = useMemo(() => getStyles(tokens), [tokens]);
|
|
35
46
|
const { notificationsEnabled, setNotificationsEnabled, isLoading } = useNotificationSettings();
|
|
36
47
|
|
|
48
|
+
const header = (
|
|
49
|
+
<NavigationHeader
|
|
50
|
+
title={translations.title}
|
|
51
|
+
onBackPress={() => navigation.goBack()}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
|
|
37
55
|
if (isLoading) {
|
|
38
56
|
return (
|
|
39
|
-
<ScreenLayout testID={testID}>
|
|
57
|
+
<ScreenLayout testID={testID} header={header}>
|
|
40
58
|
<AtomicSpinner
|
|
41
59
|
size="lg"
|
|
42
60
|
color="primary"
|
|
@@ -48,34 +66,43 @@ export const NotificationsScreen: React.FC<NotificationsScreenProps> = ({
|
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
return (
|
|
51
|
-
<ScreenLayout
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
<ScreenLayout
|
|
70
|
+
testID={testID}
|
|
71
|
+
hideScrollIndicator
|
|
72
|
+
header={header}
|
|
73
|
+
>
|
|
74
|
+
<View style={styles.content}>
|
|
75
|
+
<AtomicCard style={styles.card}>
|
|
76
|
+
<View style={styles.settingItem}>
|
|
77
|
+
<View style={styles.iconContainer}>
|
|
78
|
+
<AtomicIcon name={iconName} size="lg" color={iconColor as IconColor} />
|
|
79
|
+
</View>
|
|
80
|
+
<View style={styles.textContainer}>
|
|
81
|
+
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
82
|
+
{translations.title}
|
|
83
|
+
</AtomicText>
|
|
84
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: BASE_TOKENS.spacing.xs }}>
|
|
85
|
+
{translations.description}
|
|
86
|
+
</AtomicText>
|
|
87
|
+
</View>
|
|
88
|
+
<Switch
|
|
89
|
+
value={notificationsEnabled}
|
|
90
|
+
onValueChange={setNotificationsEnabled}
|
|
91
|
+
trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
|
|
92
|
+
thumbColor={tokens.colors.surface}
|
|
93
|
+
testID="notifications-toggle"
|
|
94
|
+
/>
|
|
56
95
|
</View>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{translations.title}
|
|
60
|
-
</AtomicText>
|
|
61
|
-
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: BASE_TOKENS.spacing.xs }}>
|
|
62
|
-
{translations.description}
|
|
63
|
-
</AtomicText>
|
|
64
|
-
</View>
|
|
65
|
-
<Switch
|
|
66
|
-
value={notificationsEnabled}
|
|
67
|
-
onValueChange={setNotificationsEnabled}
|
|
68
|
-
trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
|
|
69
|
-
thumbColor={tokens.colors.surface}
|
|
70
|
-
testID="notifications-toggle"
|
|
71
|
-
/>
|
|
72
|
-
</View>
|
|
73
|
-
</AtomicCard>
|
|
96
|
+
</AtomicCard>
|
|
97
|
+
</View>
|
|
74
98
|
</ScreenLayout>
|
|
75
99
|
);
|
|
76
100
|
};
|
|
77
101
|
|
|
78
102
|
const getStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
103
|
+
content: {
|
|
104
|
+
padding: BASE_TOKENS.spacing.md,
|
|
105
|
+
},
|
|
79
106
|
loadingContainer: {
|
|
80
107
|
flex: 1,
|
|
81
108
|
justifyContent: 'center',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Reminder, ReminderFrequency, CreateReminderInput, TimePreset } from '../../../infrastructure/services/types';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_HOUR = 9;
|
|
4
|
+
export const DEFAULT_MINUTE = 0;
|
|
5
|
+
export const DEFAULT_WEEKDAY = 2; // Tuesday
|
|
6
|
+
export const MAX_TITLE_LENGTH = 100;
|
|
7
|
+
export const MAX_BODY_LENGTH = 500;
|
|
8
|
+
|
|
9
|
+
export const VALID_HOUR_RANGE = { min: 0, max: 23 } as const;
|
|
10
|
+
export const VALID_MINUTE_RANGE = { min: 0, max: 59 } as const;
|
|
11
|
+
export const VALID_WEEKDAY_RANGE = { min: 0, max: 6 } as const;
|
|
12
|
+
|
|
13
|
+
export interface ReminderFormTranslations {
|
|
14
|
+
titleLabel: string;
|
|
15
|
+
titlePlaceholder: string;
|
|
16
|
+
bodyLabel: string;
|
|
17
|
+
bodyPlaceholder: string;
|
|
18
|
+
timeLabel: string;
|
|
19
|
+
frequencyLabel: string;
|
|
20
|
+
weekdayLabel: string;
|
|
21
|
+
saveButton: string;
|
|
22
|
+
cancelButton: string;
|
|
23
|
+
customTimeLabel: string;
|
|
24
|
+
getPresetLabel: (key: string) => string;
|
|
25
|
+
getFrequencyLabel: (key: string) => string;
|
|
26
|
+
getWeekdayLabel: (key: string) => string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ReminderFormProps {
|
|
30
|
+
initialData?: Reminder;
|
|
31
|
+
translations: ReminderFormTranslations;
|
|
32
|
+
onSave: (data: CreateReminderInput) => void;
|
|
33
|
+
onCancel: () => void;
|
|
34
|
+
timePresets?: TimePreset[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import type { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
3
|
+
|
|
4
|
+
export const createReminderFormStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
5
|
+
StyleSheet.create({
|
|
6
|
+
container: {
|
|
7
|
+
flex: 1,
|
|
8
|
+
padding: 16,
|
|
9
|
+
backgroundColor: tokens.colors.surface,
|
|
10
|
+
},
|
|
11
|
+
section: { marginBottom: 20 },
|
|
12
|
+
label: { color: tokens.colors.textPrimary, marginBottom: 8 },
|
|
13
|
+
input: {
|
|
14
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
15
|
+
borderRadius: 8,
|
|
16
|
+
padding: 12,
|
|
17
|
+
fontSize: 16,
|
|
18
|
+
color: tokens.colors.textPrimary,
|
|
19
|
+
},
|
|
20
|
+
multilineInput: { minHeight: 80, textAlignVertical: 'top' },
|
|
21
|
+
buttonRow: { flexDirection: 'row', gap: 12, marginTop: 24 },
|
|
22
|
+
});
|
|
@@ -11,44 +11,20 @@ import { TimePresetSelector } from './TimePresetSelector';
|
|
|
11
11
|
import { FrequencySelector } from './FrequencySelector';
|
|
12
12
|
import { WeekdaySelector } from './WeekdaySelector';
|
|
13
13
|
import { FormButton } from './FormButton';
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_HOUR,
|
|
16
|
+
DEFAULT_MINUTE,
|
|
17
|
+
DEFAULT_WEEKDAY,
|
|
18
|
+
MAX_TITLE_LENGTH,
|
|
19
|
+
MAX_BODY_LENGTH,
|
|
20
|
+
VALID_HOUR_RANGE,
|
|
21
|
+
VALID_MINUTE_RANGE,
|
|
22
|
+
VALID_WEEKDAY_RANGE,
|
|
23
|
+
type ReminderFormProps,
|
|
24
|
+
} from './ReminderForm.constants';
|
|
25
|
+
import { createReminderFormStyles as createStyles } from './ReminderForm.styles';
|
|
14
26
|
import { DEFAULT_TIME_PRESETS, FREQUENCY_OPTIONS } from '../../infrastructure/config/reminderPresets';
|
|
15
|
-
import type {
|
|
16
|
-
|
|
17
|
-
// Constants for magic numbers
|
|
18
|
-
const DEFAULT_HOUR = 9;
|
|
19
|
-
const DEFAULT_MINUTE = 0;
|
|
20
|
-
const DEFAULT_WEEKDAY = 2; // Tuesday
|
|
21
|
-
const MAX_TITLE_LENGTH = 100;
|
|
22
|
-
const MAX_BODY_LENGTH = 500;
|
|
23
|
-
|
|
24
|
-
// Validation constants
|
|
25
|
-
const VALID_HOUR_RANGE = { min: 0, max: 23 } as const;
|
|
26
|
-
const VALID_MINUTE_RANGE = { min: 0, max: 59 } as const;
|
|
27
|
-
const VALID_WEEKDAY_RANGE = { min: 0, max: 6 } as const;
|
|
28
|
-
|
|
29
|
-
export interface ReminderFormTranslations {
|
|
30
|
-
titleLabel: string;
|
|
31
|
-
titlePlaceholder: string;
|
|
32
|
-
bodyLabel: string;
|
|
33
|
-
bodyPlaceholder: string;
|
|
34
|
-
timeLabel: string;
|
|
35
|
-
frequencyLabel: string;
|
|
36
|
-
weekdayLabel: string;
|
|
37
|
-
saveButton: string;
|
|
38
|
-
cancelButton: string;
|
|
39
|
-
customTimeLabel: string;
|
|
40
|
-
getPresetLabel: (key: string) => string;
|
|
41
|
-
getFrequencyLabel: (key: string) => string;
|
|
42
|
-
getWeekdayLabel: (key: string) => string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ReminderFormProps {
|
|
46
|
-
initialData?: Reminder;
|
|
47
|
-
translations: ReminderFormTranslations;
|
|
48
|
-
onSave: (data: CreateReminderInput) => void;
|
|
49
|
-
onCancel: () => void;
|
|
50
|
-
timePresets?: TimePreset[];
|
|
51
|
-
}
|
|
27
|
+
import type { ReminderFrequency, TimePreset } from '../../../infrastructure/services/types';
|
|
52
28
|
|
|
53
29
|
export const ReminderForm: React.FC<ReminderFormProps> = ({
|
|
54
30
|
initialData,
|
|
@@ -207,23 +183,3 @@ export const ReminderForm: React.FC<ReminderFormProps> = ({
|
|
|
207
183
|
</ScrollView>
|
|
208
184
|
);
|
|
209
185
|
};
|
|
210
|
-
|
|
211
|
-
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
212
|
-
StyleSheet.create({
|
|
213
|
-
container: {
|
|
214
|
-
flex: 1,
|
|
215
|
-
padding: 16,
|
|
216
|
-
backgroundColor: tokens.colors.surface,
|
|
217
|
-
},
|
|
218
|
-
section: { marginBottom: 20 },
|
|
219
|
-
label: { color: tokens.colors.textPrimary, marginBottom: 8 },
|
|
220
|
-
input: {
|
|
221
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
222
|
-
borderRadius: 8,
|
|
223
|
-
padding: 12,
|
|
224
|
-
fontSize: 16,
|
|
225
|
-
color: tokens.colors.textPrimary,
|
|
226
|
-
},
|
|
227
|
-
multilineInput: { minHeight: 80, textAlignVertical: 'top' },
|
|
228
|
-
buttonRow: { flexDirection: 'row', gap: 12, marginTop: 24 },
|
|
229
|
-
});
|
|
@@ -5,8 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
import React, { useMemo, useCallback } from 'react';
|
|
7
7
|
import { View, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
AtomicSpinner,
|
|
12
|
+
ScreenLayout,
|
|
13
|
+
NavigationHeader,
|
|
14
|
+
useAppNavigation,
|
|
15
|
+
useAppDesignTokens
|
|
16
|
+
} from '@umituz/react-native-design-system';
|
|
10
17
|
import { ReminderItem } from '../components/ReminderItem';
|
|
11
18
|
import { useReminders, useRemindersLoading } from '../../infrastructure/storage/RemindersStore';
|
|
12
19
|
import { useReminderActions } from '../../infrastructure/hooks/useReminderActions';
|
|
@@ -25,6 +32,7 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
|
|
|
25
32
|
onEditPress,
|
|
26
33
|
maxReminders = 20,
|
|
27
34
|
}) => {
|
|
35
|
+
const navigation = useAppNavigation();
|
|
28
36
|
const tokens = useAppDesignTokens();
|
|
29
37
|
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
30
38
|
|
|
@@ -69,32 +77,39 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
|
|
|
69
77
|
|
|
70
78
|
const keyExtractor = useCallback((item: Reminder) => item.id, []);
|
|
71
79
|
|
|
80
|
+
const header = (
|
|
81
|
+
<NavigationHeader
|
|
82
|
+
title={translations.screenTitle}
|
|
83
|
+
onBackPress={() => navigation.goBack()}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
|
|
72
87
|
if (isLoading) {
|
|
73
88
|
return (
|
|
74
|
-
<
|
|
89
|
+
<ScreenLayout header={header}>
|
|
75
90
|
<AtomicSpinner size="lg" color="primary" fullContainer />
|
|
76
|
-
</
|
|
91
|
+
</ScreenLayout>
|
|
77
92
|
);
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
return (
|
|
81
|
-
<
|
|
96
|
+
<ScreenLayout header={header}>
|
|
82
97
|
<FlatList
|
|
83
98
|
data={reminders}
|
|
84
99
|
renderItem={renderItem}
|
|
85
100
|
keyExtractor={keyExtractor}
|
|
86
101
|
ListEmptyComponent={renderEmpty}
|
|
87
|
-
contentContainerStyle={
|
|
102
|
+
contentContainerStyle={styles.listContent}
|
|
88
103
|
showsVerticalScrollIndicator={false}
|
|
89
104
|
/>
|
|
90
105
|
|
|
91
106
|
{canAddMore && (
|
|
92
107
|
<TouchableOpacity style={styles.fab} onPress={onAddPress} activeOpacity={0.8}>
|
|
93
|
-
<AtomicIcon name="add" size="md" color="
|
|
108
|
+
<AtomicIcon name="add" size="md" color="onPrimary" />
|
|
94
109
|
<AtomicText type="bodyMedium" style={styles.fabText}>{translations.addButtonLabel}</AtomicText>
|
|
95
110
|
</TouchableOpacity>
|
|
96
111
|
)}
|
|
97
|
-
</
|
|
112
|
+
</ScreenLayout>
|
|
98
113
|
);
|
|
99
114
|
};
|
|
100
115
|
|
|
@@ -102,7 +117,7 @@ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
|
102
117
|
StyleSheet.create({
|
|
103
118
|
loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
104
119
|
listContent: { padding: 16, paddingBottom: 100, flexGrow: 1 },
|
|
105
|
-
emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32 },
|
|
120
|
+
emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, paddingTop: 100 },
|
|
106
121
|
emptyIconContainer: {
|
|
107
122
|
width: 80,
|
|
108
123
|
height: 80,
|
|
@@ -127,5 +142,5 @@ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
|
127
142
|
justifyContent: 'center',
|
|
128
143
|
gap: 8,
|
|
129
144
|
},
|
|
130
|
-
fabText: { color: tokens.colors.
|
|
145
|
+
fabText: { color: tokens.colors.onPrimary, fontWeight: '600' },
|
|
131
146
|
});
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
ScreenLayout,
|
|
19
19
|
AtomicSpinner,
|
|
20
20
|
AtomicText,
|
|
21
|
+
useAppNavigation,
|
|
22
|
+
NavigationHeader,
|
|
21
23
|
} from "@umituz/react-native-design-system";
|
|
22
24
|
import type { DesignTokens } from "@umituz/react-native-design-system";
|
|
23
25
|
import type { VideoTutorial } from "../../types";
|
|
@@ -67,23 +69,32 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> = React.m
|
|
|
67
69
|
[handleTutorialPress]
|
|
68
70
|
);
|
|
69
71
|
|
|
72
|
+
const navigation = useAppNavigation();
|
|
73
|
+
|
|
70
74
|
if (isLoading) return <AtomicSpinner size="lg" fullContainer />;
|
|
71
75
|
|
|
72
76
|
const hasFeatured = featuredTutorials && featuredTutorials.length > 0;
|
|
73
77
|
const hasTutorials = tutorials && tutorials.length > 0;
|
|
74
78
|
|
|
79
|
+
const header = (
|
|
80
|
+
<NavigationHeader
|
|
81
|
+
title={title}
|
|
82
|
+
onBackPress={() => navigation.goBack()}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
|
|
75
86
|
if (!hasTutorials && !hasFeatured) {
|
|
76
87
|
return (
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
|
|
88
|
+
<ScreenLayout header={header} edges={["bottom"]}>
|
|
89
|
+
<View style={styles.emptyContainer}>
|
|
90
|
+
<AtomicText color="secondary" type="bodyLarge">{emptyMessage}</AtomicText>
|
|
91
|
+
</View>
|
|
92
|
+
</ScreenLayout>
|
|
80
93
|
);
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
return (
|
|
84
|
-
<ScreenLayout scrollable={false} edges={["
|
|
85
|
-
<AtomicText style={styles.title}>{title}</AtomicText>
|
|
86
|
-
|
|
97
|
+
<ScreenLayout header={header} scrollable={false} edges={["bottom"]}>
|
|
87
98
|
{hasFeatured && (
|
|
88
99
|
<View style={styles.section}>
|
|
89
100
|
<AtomicText color="secondary" style={styles.sectionTitle}>{featuredTitle}</AtomicText>
|
|
@@ -13,9 +13,9 @@ export const SettingsFooter: React.FC<SettingsFooterProps> = ({
|
|
|
13
13
|
appVersion,
|
|
14
14
|
versionLabel,
|
|
15
15
|
}) => {
|
|
16
|
-
const displayText = versionText || (appVersion
|
|
17
|
-
? `${versionLabel} ${appVersion}`
|
|
18
|
-
:
|
|
16
|
+
const displayText = versionText || (appVersion
|
|
17
|
+
? (versionLabel ? `${versionLabel} ${appVersion}` : `Version ${appVersion}`)
|
|
18
|
+
: undefined);
|
|
19
19
|
|
|
20
20
|
if (!displayText) return null;
|
|
21
21
|
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from "react";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
StackNavigator,
|
|
12
|
+
type StackScreen,
|
|
13
|
+
type StackNavigatorConfig,
|
|
14
|
+
} from "@umituz/react-native-design-system";
|
|
10
15
|
import { useLocalization, LanguageSelectionScreen } from "../../domains/localization";
|
|
11
16
|
import { NotificationSettingsScreen } from "../../domains/notifications";
|
|
12
17
|
import { AccountScreen } from "@umituz/react-native-auth";
|
|
@@ -16,30 +21,31 @@ import { FAQScreen } from "../../domains/faqs";
|
|
|
16
21
|
import { AboutScreen } from "../../domains/about";
|
|
17
22
|
import { LegalScreen } from "../../domains/legal";
|
|
18
23
|
import { GamificationScreen } from "../../domains/gamification";
|
|
19
|
-
import { useNavigationHandlers } from "./hooks";
|
|
24
|
+
import { useNavigationHandlers, useSettingsScreens } from "./hooks";
|
|
20
25
|
import {
|
|
21
26
|
createNotificationTranslations,
|
|
22
27
|
createQuietHoursTranslations,
|
|
23
28
|
createLegalScreenProps,
|
|
24
29
|
} from "./utils";
|
|
25
|
-
import type { SettingsStackParamList, SettingsStackNavigatorProps
|
|
30
|
+
import type { SettingsStackParamList, SettingsStackNavigatorProps } from "./types";
|
|
26
31
|
|
|
27
|
-
export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = ({
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = (props) => {
|
|
33
|
+
const {
|
|
34
|
+
appInfo,
|
|
35
|
+
legalUrls,
|
|
36
|
+
faqData,
|
|
37
|
+
config = {},
|
|
38
|
+
showUserProfile = false,
|
|
39
|
+
userProfile,
|
|
40
|
+
accountConfig,
|
|
41
|
+
additionalScreens = [],
|
|
42
|
+
devSettings,
|
|
43
|
+
customSections = [],
|
|
44
|
+
showHeader = true,
|
|
45
|
+
showCloseButton = false,
|
|
46
|
+
onClose,
|
|
47
|
+
gamificationConfig,
|
|
48
|
+
} = props;
|
|
43
49
|
const tokens = useAppDesignTokens();
|
|
44
50
|
const { t } = useLocalization();
|
|
45
51
|
const { handlePrivacyPress, handleTermsPress, handleEulaPress, aboutConfig } =
|
|
@@ -47,21 +53,9 @@ export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = ({
|
|
|
47
53
|
|
|
48
54
|
const screenOptions = React.useMemo(
|
|
49
55
|
() => ({
|
|
50
|
-
|
|
51
|
-
backgroundColor: tokens.colors.surface,
|
|
52
|
-
borderBottomColor: tokens.colors.borderLight,
|
|
53
|
-
borderBottomWidth: 1,
|
|
54
|
-
elevation: 0,
|
|
55
|
-
shadowOpacity: 0,
|
|
56
|
-
},
|
|
57
|
-
headerTintColor: tokens.colors.textPrimary,
|
|
58
|
-
headerTitleStyle: {
|
|
59
|
-
color: tokens.colors.textPrimary,
|
|
60
|
-
fontWeight: "600" as const,
|
|
61
|
-
},
|
|
62
|
-
headerBackTitle: t("settings.title"),
|
|
56
|
+
headerShown: false,
|
|
63
57
|
}),
|
|
64
|
-
[
|
|
58
|
+
[]
|
|
65
59
|
);
|
|
66
60
|
|
|
67
61
|
const notificationTranslations = React.useMemo(() => createNotificationTranslations(t), [t]);
|
|
@@ -71,150 +65,14 @@ export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = ({
|
|
|
71
65
|
[t, handlePrivacyPress, handleTermsPress, handleEulaPress]
|
|
72
66
|
);
|
|
73
67
|
|
|
74
|
-
const screens =
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
name: "SettingsMain",
|
|
78
|
-
options: { headerShown: false },
|
|
79
|
-
children: () => (
|
|
80
|
-
<SettingsScreen
|
|
81
|
-
config={config}
|
|
82
|
-
appVersion={appInfo.version}
|
|
83
|
-
showUserProfile={showUserProfile}
|
|
84
|
-
userProfile={userProfile}
|
|
85
|
-
devSettings={devSettings}
|
|
86
|
-
customSections={customSections}
|
|
87
|
-
showHeader={showHeader}
|
|
88
|
-
showCloseButton={showCloseButton}
|
|
89
|
-
onClose={onClose}
|
|
90
|
-
/>
|
|
91
|
-
),
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: "Appearance",
|
|
95
|
-
component: AppearanceScreen,
|
|
96
|
-
options: { headerShown: false },
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: "About",
|
|
100
|
-
options: { headerTitle: t("settings.about.title") },
|
|
101
|
-
children: () => <AboutScreen config={aboutConfig} />,
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: "Legal",
|
|
105
|
-
options: { headerTitle: t("settings.legal.title") },
|
|
106
|
-
children: () => <LegalScreen {...legalScreenProps} />,
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: "Notifications",
|
|
110
|
-
options: { headerShown: false },
|
|
111
|
-
children: () => (
|
|
112
|
-
<NotificationSettingsScreen
|
|
113
|
-
translations={notificationTranslations}
|
|
114
|
-
quietHoursTranslations={quietHoursTranslations}
|
|
115
|
-
/>
|
|
116
|
-
),
|
|
117
|
-
},
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
// FAQ screen - conditionally add
|
|
121
|
-
const faqScreen: StackScreen | null = (faqData && faqData.categories.length > 0)
|
|
122
|
-
? {
|
|
123
|
-
name: "FAQ",
|
|
124
|
-
options: { headerTitle: t("settings.faqs.title") },
|
|
125
|
-
children: () => (
|
|
126
|
-
<FAQScreen
|
|
127
|
-
categories={faqData.categories}
|
|
128
|
-
searchPlaceholder={t("settings.faqs.searchPlaceholder")}
|
|
129
|
-
emptySearchTitle={t("settings.faqs.emptySearchTitle")}
|
|
130
|
-
emptySearchMessage={t("settings.faqs.emptySearchMessage")}
|
|
131
|
-
headerTitle={t("settings.faqs.headerTitle")}
|
|
132
|
-
/>
|
|
133
|
-
),
|
|
134
|
-
}
|
|
135
|
-
: null;
|
|
136
|
-
|
|
137
|
-
// Additional screens - map to StackScreen format
|
|
138
|
-
const additionalStackScreens: StackScreen[] = (additionalScreens as readonly AdditionalScreen[]).map((screen: AdditionalScreen): StackScreen => {
|
|
139
|
-
// Create base screen object
|
|
140
|
-
const stackScreen: Record<string, unknown> = {
|
|
141
|
-
name: screen.name,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// Conditionally add properties
|
|
145
|
-
if (screen.component) {
|
|
146
|
-
stackScreen.component = screen.component;
|
|
147
|
-
}
|
|
148
|
-
if (screen.children) {
|
|
149
|
-
stackScreen.children = screen.children;
|
|
150
|
-
}
|
|
151
|
-
if (screen.options) {
|
|
152
|
-
stackScreen.options = screen.options;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Type assertion to StackScreen
|
|
156
|
-
return stackScreen as unknown as StackScreen;
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Gamification screen - conditionally add
|
|
160
|
-
const gamificationScreen: StackScreen | null = gamificationConfig?.enabled
|
|
161
|
-
? {
|
|
162
|
-
name: "Gamification",
|
|
163
|
-
options: { headerTitle: t("settings.gamification.title") },
|
|
164
|
-
children: () => <GamificationScreen config={gamificationConfig} />,
|
|
165
|
-
}
|
|
166
|
-
: null;
|
|
167
|
-
|
|
168
|
-
// Language selection screen
|
|
169
|
-
const languageScreen: StackScreen = {
|
|
170
|
-
name: "LanguageSelection",
|
|
171
|
-
options: { headerShown: false },
|
|
172
|
-
children: () => (
|
|
173
|
-
<LanguageSelectionScreen
|
|
174
|
-
headerTitle={t("settings.language.title")}
|
|
175
|
-
searchPlaceholder={t("settings.languageSelection.searchPlaceholder")}
|
|
176
|
-
/>
|
|
177
|
-
),
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// Account screen - conditionally add
|
|
181
|
-
const accountScreen: StackScreen | null = accountConfig
|
|
182
|
-
? {
|
|
183
|
-
name: "Account",
|
|
184
|
-
options: { headerTitle: t("settings.account.title") },
|
|
185
|
-
children: () => <AccountScreen config={accountConfig} />,
|
|
186
|
-
}
|
|
187
|
-
: null;
|
|
188
|
-
|
|
189
|
-
// Compose final list using spread operator (immutable)
|
|
190
|
-
return [
|
|
191
|
-
...baseScreens,
|
|
192
|
-
...(faqScreen ? [faqScreen] : []),
|
|
193
|
-
...additionalStackScreens,
|
|
194
|
-
...(gamificationScreen ? [gamificationScreen] : []),
|
|
195
|
-
languageScreen,
|
|
196
|
-
...(accountScreen ? [accountScreen] : []),
|
|
197
|
-
];
|
|
198
|
-
}, [
|
|
199
|
-
t,
|
|
200
|
-
showHeader,
|
|
201
|
-
showCloseButton,
|
|
202
|
-
onClose,
|
|
203
|
-
config,
|
|
204
|
-
appInfo.version,
|
|
205
|
-
showUserProfile,
|
|
206
|
-
userProfile,
|
|
207
|
-
devSettings,
|
|
208
|
-
customSections,
|
|
68
|
+
const screens = useSettingsScreens({
|
|
69
|
+
...props,
|
|
209
70
|
aboutConfig,
|
|
210
|
-
legalScreenProps,
|
|
71
|
+
legalProps: legalScreenProps,
|
|
211
72
|
notificationTranslations,
|
|
212
73
|
quietHoursTranslations,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
gamificationConfig,
|
|
216
|
-
accountConfig,
|
|
217
|
-
]);
|
|
74
|
+
t,
|
|
75
|
+
});
|
|
218
76
|
|
|
219
77
|
const navigatorConfig: StackNavigatorConfig<SettingsStackParamList> = {
|
|
220
78
|
initialRouteName: "SettingsMain",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import type { StackScreen } from "@umituz/react-native-design-system";
|
|
3
|
+
import { LanguageSelectionScreen } from "../../../domains/localization";
|
|
4
|
+
import { NotificationSettingsScreen } from "../../../domains/notifications";
|
|
5
|
+
import { AccountScreen } from "@umituz/react-native-auth";
|
|
6
|
+
import { SettingsScreen } from "../../screens/SettingsScreen";
|
|
7
|
+
import { AppearanceScreen } from "../../screens/AppearanceScreen";
|
|
8
|
+
import { FAQScreen } from "../../../domains/faqs";
|
|
9
|
+
import { AboutScreen } from "../../../domains/about";
|
|
10
|
+
import { LegalScreen } from "../../../domains/legal";
|
|
11
|
+
import { GamificationScreen } from "../../../domains/gamification";
|
|
12
|
+
import type { SettingsStackNavigatorProps, AdditionalScreen } from "../types";
|
|
13
|
+
|
|
14
|
+
export interface UseSettingsScreensProps extends SettingsStackNavigatorProps {
|
|
15
|
+
aboutConfig: any;
|
|
16
|
+
legalProps: any;
|
|
17
|
+
notificationTranslations: any;
|
|
18
|
+
quietHoursTranslations: any;
|
|
19
|
+
t: (key: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[] => {
|
|
23
|
+
const {
|
|
24
|
+
appInfo,
|
|
25
|
+
config,
|
|
26
|
+
showUserProfile,
|
|
27
|
+
userProfile,
|
|
28
|
+
devSettings,
|
|
29
|
+
customSections,
|
|
30
|
+
showHeader,
|
|
31
|
+
showCloseButton,
|
|
32
|
+
onClose,
|
|
33
|
+
aboutConfig,
|
|
34
|
+
legalProps,
|
|
35
|
+
notificationTranslations,
|
|
36
|
+
quietHoursTranslations,
|
|
37
|
+
faqData,
|
|
38
|
+
additionalScreens,
|
|
39
|
+
gamificationConfig,
|
|
40
|
+
accountConfig,
|
|
41
|
+
t,
|
|
42
|
+
} = props;
|
|
43
|
+
|
|
44
|
+
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
|
+
},
|
|
66
|
+
{
|
|
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
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "Notifications",
|
|
78
|
+
options: { headerShown: false },
|
|
79
|
+
children: () => React.createElement(NotificationSettingsScreen, {
|
|
80
|
+
translations: notificationTranslations,
|
|
81
|
+
quietHoursTranslations,
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
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;
|
|
99
|
+
|
|
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
|
+
});
|
|
107
|
+
|
|
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;
|
|
115
|
+
|
|
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
|
+
};
|
|
124
|
+
|
|
125
|
+
const accountScreen: StackScreen | null = accountConfig
|
|
126
|
+
? {
|
|
127
|
+
name: "Account",
|
|
128
|
+
options: { headerShown: false },
|
|
129
|
+
children: () => React.createElement(AccountScreen, { config: accountConfig }),
|
|
130
|
+
}
|
|
131
|
+
: null;
|
|
132
|
+
|
|
133
|
+
const allScreens: StackScreen[] = [
|
|
134
|
+
...baseScreens,
|
|
135
|
+
...(faqScreen ? [faqScreen] : []),
|
|
136
|
+
...additionalStackScreens,
|
|
137
|
+
...(gamificationScreen ? [gamificationScreen] : []),
|
|
138
|
+
languageScreen,
|
|
139
|
+
...(accountScreen ? [accountScreen] : []),
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
return allScreens;
|
|
143
|
+
}, [
|
|
144
|
+
t,
|
|
145
|
+
showHeader,
|
|
146
|
+
showCloseButton,
|
|
147
|
+
onClose,
|
|
148
|
+
config,
|
|
149
|
+
appInfo.version,
|
|
150
|
+
showUserProfile,
|
|
151
|
+
userProfile,
|
|
152
|
+
devSettings,
|
|
153
|
+
customSections,
|
|
154
|
+
aboutConfig,
|
|
155
|
+
legalProps,
|
|
156
|
+
notificationTranslations,
|
|
157
|
+
quietHoursTranslations,
|
|
158
|
+
faqData,
|
|
159
|
+
additionalScreens,
|
|
160
|
+
gamificationConfig,
|
|
161
|
+
accountConfig,
|
|
162
|
+
]);
|
|
163
|
+
};
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Handles close button functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Pressable } from "react-native";
|
|
6
3
|
import { useAppDesignTokens, AtomicIcon, useAppNavigation, NavigationHeader } from "@umituz/react-native-design-system";
|
|
7
4
|
import { useLocalization } from "../../../domains/localization";
|
|
8
5
|
|
package/src/utils/appUtils.ts
CHANGED
|
@@ -16,6 +16,13 @@ export function getAppVersion(): string {
|
|
|
16
16
|
return version;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Gets the current build number from Expo constants
|
|
21
|
+
*/
|
|
22
|
+
export function getBuildNumber(): string | undefined {
|
|
23
|
+
return Constants.expoConfig?.ios?.buildNumber ?? Constants.expoConfig?.android?.versionCode?.toString();
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
27
|
* Validates if the current platform is supported
|
|
21
28
|
*/
|