@umituz/react-native-settings 4.23.82 → 4.23.84
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/feedback/presentation/components/FeedbackModal.tsx +16 -19
- 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/components/VideoTutorialCard.tsx +17 -14
- package/src/domains/video-tutorials/presentation/screens/VideoTutorialsScreen.tsx +58 -33
- 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
|
@@ -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
|
});
|
|
@@ -44,18 +44,19 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
|
|
|
44
44
|
style={[styles.thumbnail, horizontal && styles.horizontalThumbnail]}
|
|
45
45
|
resizeMode="cover"
|
|
46
46
|
/>
|
|
47
|
-
<View style={
|
|
48
|
-
<AtomicText style={styles.durationText}>{formatDuration(tutorial.duration)}</AtomicText>
|
|
47
|
+
<View style={styles.durationBadge}>
|
|
48
|
+
<AtomicText type="labelSmall" style={styles.durationText}>{formatDuration(tutorial.duration)}</AtomicText>
|
|
49
49
|
</View>
|
|
50
50
|
{tutorial.featured && (
|
|
51
51
|
<View style={[styles.featuredBadge, { backgroundColor: tokens.colors.primary }]}>
|
|
52
|
-
<AtomicText style={[styles.featuredText, { color: tokens.colors.onPrimary }]}>Featured</AtomicText>
|
|
52
|
+
<AtomicText type="labelSmall" style={[styles.featuredText, { color: tokens.colors.onPrimary }]}>Featured</AtomicText>
|
|
53
53
|
</View>
|
|
54
54
|
)}
|
|
55
55
|
</View>
|
|
56
56
|
|
|
57
57
|
<View style={styles.content}>
|
|
58
58
|
<AtomicText
|
|
59
|
+
type={horizontal ? "titleMedium" : "titleLarge"}
|
|
59
60
|
style={[styles.title, horizontal && styles.horizontalTitle]}
|
|
60
61
|
numberOfLines={2}
|
|
61
62
|
>
|
|
@@ -63,6 +64,8 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
|
|
|
63
64
|
</AtomicText>
|
|
64
65
|
|
|
65
66
|
<AtomicText
|
|
67
|
+
type={horizontal ? "bodySmall" : "bodyMedium"}
|
|
68
|
+
color="textSecondary"
|
|
66
69
|
style={[styles.description, horizontal && styles.horizontalDescription]}
|
|
67
70
|
numberOfLines={horizontal ? 2 : 3}
|
|
68
71
|
>
|
|
@@ -70,10 +73,10 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
|
|
|
70
73
|
</AtomicText>
|
|
71
74
|
|
|
72
75
|
<View style={styles.metadata}>
|
|
73
|
-
<AtomicText style={styles.category}>
|
|
76
|
+
<AtomicText type="labelSmall" color="textTertiary" style={styles.category}>
|
|
74
77
|
{tutorial.category.replace("-", " ")}
|
|
75
78
|
</AtomicText>
|
|
76
|
-
<AtomicText style={styles.difficulty}>
|
|
79
|
+
<AtomicText type="labelSmall" color="textSecondary" style={styles.difficulty}>
|
|
77
80
|
{tutorial.difficulty}
|
|
78
81
|
</AtomicText>
|
|
79
82
|
</View>
|
|
@@ -88,16 +91,16 @@ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) => StyleSheet.
|
|
|
88
91
|
imageContainer: { position: "relative" },
|
|
89
92
|
thumbnail: { width: "100%", height: 180 },
|
|
90
93
|
horizontalThumbnail: { height: 140 },
|
|
91
|
-
durationBadge: { position: "absolute", bottom: 8, right: 8, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 },
|
|
92
|
-
durationText: { color: tokens.colors.textInverse
|
|
94
|
+
durationBadge: { position: "absolute", bottom: 8, right: 8, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, backgroundColor: "rgba(0,0,0,0.7)" },
|
|
95
|
+
durationText: { color: tokens.colors.textInverse },
|
|
93
96
|
featuredBadge: { position: "absolute", top: 8, left: 8, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 4 },
|
|
94
|
-
featuredText: {
|
|
97
|
+
featuredText: { fontWeight: "600" },
|
|
95
98
|
content: { padding: 12 },
|
|
96
|
-
title: {
|
|
97
|
-
horizontalTitle: {
|
|
98
|
-
description: {
|
|
99
|
-
horizontalDescription: {
|
|
99
|
+
title: { fontWeight: "600", marginBottom: 6, color: tokens.colors.textPrimary },
|
|
100
|
+
horizontalTitle: {},
|
|
101
|
+
description: { marginBottom: 8 },
|
|
102
|
+
horizontalDescription: { marginBottom: 6 },
|
|
100
103
|
metadata: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
|
|
101
|
-
category: {
|
|
102
|
-
difficulty: {
|
|
104
|
+
category: { textTransform: "capitalize", fontWeight: "500" },
|
|
105
|
+
difficulty: { textTransform: "capitalize", fontWeight: "500" },
|
|
103
106
|
});
|
|
@@ -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";
|
|
@@ -46,6 +48,7 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> = React.m
|
|
|
46
48
|
onTutorialPress,
|
|
47
49
|
}) => {
|
|
48
50
|
const tokens = useAppDesignTokens();
|
|
51
|
+
const navigation = useAppNavigation();
|
|
49
52
|
const styles = getStyles(tokens);
|
|
50
53
|
|
|
51
54
|
const handleTutorialPress = React.useCallback(
|
|
@@ -67,26 +70,39 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> = React.m
|
|
|
67
70
|
[handleTutorialPress]
|
|
68
71
|
);
|
|
69
72
|
|
|
70
|
-
if (isLoading) return <AtomicSpinner size="lg" fullContainer />;
|
|
71
|
-
|
|
72
73
|
const hasFeatured = featuredTutorials && featuredTutorials.length > 0;
|
|
73
74
|
const hasTutorials = tutorials && tutorials.length > 0;
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
const header = (
|
|
77
|
+
<NavigationHeader
|
|
78
|
+
title={title}
|
|
79
|
+
onBackPress={() => navigation.goBack()}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (isLoading) {
|
|
76
84
|
return (
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
</
|
|
85
|
+
<ScreenLayout header={header}>
|
|
86
|
+
<AtomicSpinner size="lg" fullContainer color="primary" />
|
|
87
|
+
</ScreenLayout>
|
|
80
88
|
);
|
|
81
89
|
}
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
91
|
+
if (!hasTutorials && !hasFeatured) {
|
|
92
|
+
return (
|
|
93
|
+
<ScreenLayout header={header}>
|
|
94
|
+
<View style={styles.emptyContainer}>
|
|
95
|
+
<AtomicText color="textSecondary" type="bodyLarge" style={{ textAlign: 'center' }}>{emptyMessage}</AtomicText>
|
|
96
|
+
</View>
|
|
97
|
+
</ScreenLayout>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
86
100
|
|
|
101
|
+
const ListHeader = () => (
|
|
102
|
+
<>
|
|
87
103
|
{hasFeatured && (
|
|
88
104
|
<View style={styles.section}>
|
|
89
|
-
<AtomicText color="
|
|
105
|
+
<AtomicText color="textSecondary" type="titleLarge" style={styles.sectionTitle}>{featuredTitle}</AtomicText>
|
|
90
106
|
<FlatList
|
|
91
107
|
data={featuredTutorials}
|
|
92
108
|
renderItem={renderFeaturedItem}
|
|
@@ -97,38 +113,47 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> = React.m
|
|
|
97
113
|
/>
|
|
98
114
|
</View>
|
|
99
115
|
)}
|
|
100
|
-
|
|
101
116
|
{hasTutorials && (
|
|
102
|
-
<
|
|
103
|
-
<AtomicText color="secondary" style={styles.sectionTitle}>{allTutorialsTitle}</AtomicText>
|
|
104
|
-
<FlatList
|
|
105
|
-
data={tutorials}
|
|
106
|
-
renderItem={renderTutorialItem}
|
|
107
|
-
keyExtractor={(item) => item.id}
|
|
108
|
-
showsVerticalScrollIndicator={false}
|
|
109
|
-
contentContainerStyle={styles.verticalList}
|
|
110
|
-
/>
|
|
111
|
-
</View>
|
|
117
|
+
<AtomicText color="textSecondary" type="titleLarge" style={styles.sectionTitle}>{allTutorialsTitle}</AtomicText>
|
|
112
118
|
)}
|
|
119
|
+
</>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<ScreenLayout header={header} scrollable={false}>
|
|
124
|
+
<FlatList
|
|
125
|
+
data={tutorials}
|
|
126
|
+
renderItem={renderTutorialItem}
|
|
127
|
+
keyExtractor={(item) => item.id}
|
|
128
|
+
showsVerticalScrollIndicator={false}
|
|
129
|
+
contentContainerStyle={styles.verticalList}
|
|
130
|
+
ListHeaderComponent={ListHeader}
|
|
131
|
+
/>
|
|
113
132
|
</ScreenLayout>
|
|
114
133
|
);
|
|
115
134
|
}
|
|
116
135
|
);
|
|
117
136
|
|
|
118
137
|
const getStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
color: tokens.colors.textPrimary,
|
|
122
|
-
fontWeight: "600",
|
|
123
|
-
marginBottom: 24,
|
|
138
|
+
section: {
|
|
139
|
+
marginBottom: tokens.spacing.lg,
|
|
124
140
|
},
|
|
125
|
-
section: { marginBottom: 24 },
|
|
126
141
|
sectionTitle: {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
fontWeight: "600",
|
|
143
|
+
marginBottom: tokens.spacing.sm,
|
|
144
|
+
paddingHorizontal: tokens.spacing.md,
|
|
145
|
+
},
|
|
146
|
+
horizontalList: {
|
|
147
|
+
paddingHorizontal: tokens.spacing.md,
|
|
148
|
+
},
|
|
149
|
+
verticalList: {
|
|
150
|
+
paddingBottom: tokens.spacing.xl,
|
|
151
|
+
paddingHorizontal: tokens.spacing.md,
|
|
152
|
+
},
|
|
153
|
+
emptyContainer: {
|
|
154
|
+
flex: 1,
|
|
155
|
+
justifyContent: "center",
|
|
156
|
+
alignItems: "center",
|
|
157
|
+
padding: tokens.spacing.lg,
|
|
130
158
|
},
|
|
131
|
-
horizontalList: { paddingRight: 16 },
|
|
132
|
-
verticalList: { paddingBottom: 16 },
|
|
133
|
-
emptyContainer: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20 },
|
|
134
159
|
});
|
|
@@ -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",
|