@umituz/react-native-notifications 1.5.8 → 1.5.10
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/presentation/components/RemindersNavRow.styles.ts +38 -0
- package/src/presentation/components/RemindersNavRow.tsx +51 -0
- package/src/presentation/hooks/useTimePicker.ts +71 -0
- package/src/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
- package/src/presentation/screens/NotificationSettingsScreen.tsx +25 -117
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemindersNavRow Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleSheet } from 'react-native';
|
|
6
|
+
import type { DesignTokens } from '@umituz/react-native-design-system';
|
|
7
|
+
|
|
8
|
+
export const createStyles = (tokens: DesignTokens) =>
|
|
9
|
+
StyleSheet.create({
|
|
10
|
+
navRow: {
|
|
11
|
+
flexDirection: 'row',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
},
|
|
14
|
+
iconContainer: {
|
|
15
|
+
width: 44,
|
|
16
|
+
height: 44,
|
|
17
|
+
borderRadius: 22,
|
|
18
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
19
|
+
justifyContent: 'center',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
marginRight: 12,
|
|
22
|
+
},
|
|
23
|
+
textContainer: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
marginRight: 12,
|
|
26
|
+
},
|
|
27
|
+
badge: {
|
|
28
|
+
backgroundColor: tokens.colors.primary,
|
|
29
|
+
paddingHorizontal: 8,
|
|
30
|
+
paddingVertical: 2,
|
|
31
|
+
borderRadius: 10,
|
|
32
|
+
marginRight: 8,
|
|
33
|
+
},
|
|
34
|
+
badgeText: {
|
|
35
|
+
color: tokens.colors.surface,
|
|
36
|
+
fontWeight: '600',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemindersNavRow Component
|
|
3
|
+
* Reusable navigation row for reminders section with badge
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
8
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
9
|
+
import { createStyles } from './RemindersNavRow.styles';
|
|
10
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
11
|
+
|
|
12
|
+
export interface RemindersNavRowProps {
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
count: number;
|
|
16
|
+
onPress: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const RemindersNavRow: React.FC<RemindersNavRowProps> = ({
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
count,
|
|
23
|
+
onPress,
|
|
24
|
+
}) => {
|
|
25
|
+
const tokens = useAppDesignTokens();
|
|
26
|
+
const styles = createStyles(tokens);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<TouchableOpacity style={styles.navRow} onPress={onPress} activeOpacity={0.7}>
|
|
30
|
+
<View style={styles.iconContainer}>
|
|
31
|
+
<AtomicIcon name="time" size="md" color="primary" />
|
|
32
|
+
</View>
|
|
33
|
+
<View style={styles.textContainer}>
|
|
34
|
+
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
35
|
+
{title}
|
|
36
|
+
</AtomicText>
|
|
37
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
|
|
38
|
+
{description}
|
|
39
|
+
</AtomicText>
|
|
40
|
+
</View>
|
|
41
|
+
{count > 0 && (
|
|
42
|
+
<View style={styles.badge}>
|
|
43
|
+
<AtomicText type="bodySmall" style={styles.badgeText}>
|
|
44
|
+
{count}
|
|
45
|
+
</AtomicText>
|
|
46
|
+
</View>
|
|
47
|
+
)}
|
|
48
|
+
<AtomicIcon name="chevron-forward" size="md" color="secondary" />
|
|
49
|
+
</TouchableOpacity>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useTimePicker Hook
|
|
3
|
+
* Encapsulates DateTimePicker logic for notification settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from 'react';
|
|
7
|
+
import type { QuietHoursConfig } from '../../infrastructure/services/types';
|
|
8
|
+
|
|
9
|
+
export type PickerMode = 'start' | 'end' | null;
|
|
10
|
+
|
|
11
|
+
export interface UseTimePickerParams {
|
|
12
|
+
quietHours: QuietHoursConfig;
|
|
13
|
+
onStartTimeChange: (hours: number, minutes: number) => void;
|
|
14
|
+
onEndTimeChange: (hours: number, minutes: number) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TimePickerHandlers {
|
|
18
|
+
pickerMode: PickerMode;
|
|
19
|
+
handleStartTimePress: () => void;
|
|
20
|
+
handleEndTimePress: () => void;
|
|
21
|
+
handleTimeChange: (event: any, selectedDate?: Date) => void;
|
|
22
|
+
getPickerDate: () => Date;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useTimePicker = ({
|
|
26
|
+
quietHours,
|
|
27
|
+
onStartTimeChange,
|
|
28
|
+
onEndTimeChange,
|
|
29
|
+
}: UseTimePickerParams): TimePickerHandlers => {
|
|
30
|
+
const [pickerMode, setPickerMode] = useState<PickerMode>(null);
|
|
31
|
+
|
|
32
|
+
const handleStartTimePress = useCallback(() => {
|
|
33
|
+
setPickerMode('start');
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const handleEndTimePress = useCallback(() => {
|
|
37
|
+
setPickerMode('end');
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const handleTimeChange = useCallback((event: any, selectedDate?: Date) => {
|
|
41
|
+
if (event.type === 'set' && selectedDate) {
|
|
42
|
+
const hours = selectedDate.getHours();
|
|
43
|
+
const minutes = selectedDate.getMinutes();
|
|
44
|
+
|
|
45
|
+
if (pickerMode === 'start') {
|
|
46
|
+
onStartTimeChange(hours, minutes);
|
|
47
|
+
} else if (pickerMode === 'end') {
|
|
48
|
+
onEndTimeChange(hours, minutes);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
setPickerMode(null);
|
|
52
|
+
}, [pickerMode, onStartTimeChange, onEndTimeChange]);
|
|
53
|
+
|
|
54
|
+
const getPickerDate = useCallback((): Date => {
|
|
55
|
+
const date = new Date();
|
|
56
|
+
if (pickerMode === 'start') {
|
|
57
|
+
date.setHours(quietHours.startHour, quietHours.startMinute);
|
|
58
|
+
} else if (pickerMode === 'end') {
|
|
59
|
+
date.setHours(quietHours.endHour, quietHours.endMinute);
|
|
60
|
+
}
|
|
61
|
+
return date;
|
|
62
|
+
}, [pickerMode, quietHours]);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
pickerMode,
|
|
66
|
+
handleStartTimePress,
|
|
67
|
+
handleEndTimePress,
|
|
68
|
+
handleTimeChange,
|
|
69
|
+
getPickerDate,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationSettingsScreen Styles
|
|
3
|
+
* Extracted styles for better organization and maintainability
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { StyleSheet } from 'react-native';
|
|
7
|
+
import type { DesignTokens } from '@umituz/react-native-design-system';
|
|
8
|
+
|
|
9
|
+
export const createStyles = (tokens: DesignTokens) =>
|
|
10
|
+
StyleSheet.create({
|
|
11
|
+
container: {
|
|
12
|
+
flex: 1,
|
|
13
|
+
padding: 16,
|
|
14
|
+
},
|
|
15
|
+
loadingContainer: {
|
|
16
|
+
flex: 1,
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
},
|
|
20
|
+
card: {
|
|
21
|
+
marginBottom: 16,
|
|
22
|
+
padding: 16,
|
|
23
|
+
backgroundColor: tokens.colors.surface,
|
|
24
|
+
},
|
|
25
|
+
divider: {
|
|
26
|
+
height: 1,
|
|
27
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
28
|
+
marginVertical: 12,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -3,21 +3,22 @@
|
|
|
3
3
|
* Clean presentation-only screen for notification settings
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React
|
|
7
|
-
import { View
|
|
8
|
-
import {
|
|
9
|
-
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View } from 'react-native';
|
|
8
|
+
import { AtomicCard, ScreenLayout, AtomicSpinner } from '@umituz/react-native-design-system';
|
|
10
9
|
import { QuietHoursCard } from '../../domains/quietHours/presentation/components/QuietHoursCard';
|
|
11
10
|
import { SettingRow } from '../components/SettingRow';
|
|
11
|
+
import { RemindersNavRow } from '../components/RemindersNavRow';
|
|
12
12
|
import { useNotificationSettingsUI } from '../hooks/useNotificationSettingsUI';
|
|
13
|
+
import { useTimePicker } from '../hooks/useTimePicker';
|
|
13
14
|
import { useReminders } from '../../domains/reminders/infrastructure/storage/RemindersStore';
|
|
14
15
|
import { useQuietHoursActions } from '../../domains/quietHours/infrastructure/hooks/useQuietHoursActions';
|
|
15
16
|
import type { NotificationSettingsTranslations, QuietHoursTranslations } from '../../infrastructure/services/types';
|
|
17
|
+
import { createStyles } from './NotificationSettingsScreen.styles';
|
|
18
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
16
19
|
// @ts-ignore - Optional peer dependency
|
|
17
20
|
import DateTimePicker from '@react-native-community/datetimepicker';
|
|
18
21
|
|
|
19
|
-
type PickerMode = 'start' | 'end' | null;
|
|
20
|
-
|
|
21
22
|
export interface NotificationSettingsScreenProps {
|
|
22
23
|
translations: NotificationSettingsTranslations;
|
|
23
24
|
quietHoursTranslations: QuietHoursTranslations;
|
|
@@ -30,10 +31,9 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
30
31
|
onHapticFeedback,
|
|
31
32
|
}) => {
|
|
32
33
|
const tokens = useAppDesignTokens();
|
|
33
|
-
const styles =
|
|
34
|
+
const styles = createStyles(tokens);
|
|
34
35
|
const reminders = useReminders();
|
|
35
36
|
const { setStartTime, setEndTime } = useQuietHoursActions();
|
|
36
|
-
const [pickerMode, setPickerMode] = useState<PickerMode>(null);
|
|
37
37
|
|
|
38
38
|
const {
|
|
39
39
|
preferences,
|
|
@@ -45,42 +45,16 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
45
45
|
handleQuietHoursToggle,
|
|
46
46
|
} = useNotificationSettingsUI();
|
|
47
47
|
|
|
48
|
+
const timePicker = useTimePicker({
|
|
49
|
+
quietHours,
|
|
50
|
+
onStartTimeChange: setStartTime,
|
|
51
|
+
onEndTimeChange: setEndTime,
|
|
52
|
+
});
|
|
53
|
+
|
|
48
54
|
const handleRemindersPress = () => {
|
|
49
55
|
// Navigate to reminders screen when implemented
|
|
50
56
|
};
|
|
51
57
|
|
|
52
|
-
const handleStartTimePress = () => {
|
|
53
|
-
setPickerMode('start');
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const handleEndTimePress = () => {
|
|
57
|
-
setPickerMode('end');
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const handleTimeChange = (event: any, selectedDate?: Date) => {
|
|
61
|
-
if (event.type === 'set' && selectedDate) {
|
|
62
|
-
const hours = selectedDate.getHours();
|
|
63
|
-
const minutes = selectedDate.getMinutes();
|
|
64
|
-
|
|
65
|
-
if (pickerMode === 'start') {
|
|
66
|
-
setStartTime(hours, minutes);
|
|
67
|
-
} else if (pickerMode === 'end') {
|
|
68
|
-
setEndTime(hours, minutes);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
setPickerMode(null);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const getPickerDate = () => {
|
|
75
|
-
const date = new Date();
|
|
76
|
-
if (pickerMode === 'start') {
|
|
77
|
-
date.setHours(quietHours.startHour, quietHours.startMinute);
|
|
78
|
-
} else if (pickerMode === 'end') {
|
|
79
|
-
date.setHours(quietHours.endHour, quietHours.endMinute);
|
|
80
|
-
}
|
|
81
|
-
return date;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
58
|
if (isLoading) {
|
|
85
59
|
return (
|
|
86
60
|
<ScreenLayout>
|
|
@@ -126,98 +100,32 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
126
100
|
</AtomicCard>
|
|
127
101
|
|
|
128
102
|
<AtomicCard style={styles.card}>
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{translations.remindersTitle}
|
|
136
|
-
</AtomicText>
|
|
137
|
-
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
|
|
138
|
-
{translations.remindersDescription}
|
|
139
|
-
</AtomicText>
|
|
140
|
-
</View>
|
|
141
|
-
{reminders.length > 0 && (
|
|
142
|
-
<View style={styles.badge}>
|
|
143
|
-
<AtomicText type="bodySmall" style={styles.badgeText}>
|
|
144
|
-
{reminders.length}
|
|
145
|
-
</AtomicText>
|
|
146
|
-
</View>
|
|
147
|
-
)}
|
|
148
|
-
<AtomicIcon name="chevron-forward" size="md" color="secondary" />
|
|
149
|
-
</TouchableOpacity>
|
|
103
|
+
<RemindersNavRow
|
|
104
|
+
title={translations.remindersTitle}
|
|
105
|
+
description={translations.remindersDescription}
|
|
106
|
+
count={reminders.length}
|
|
107
|
+
onPress={handleRemindersPress}
|
|
108
|
+
/>
|
|
150
109
|
</AtomicCard>
|
|
151
110
|
|
|
152
111
|
<QuietHoursCard
|
|
153
112
|
config={quietHours}
|
|
154
113
|
translations={quietHoursTranslations}
|
|
155
114
|
onToggle={handleQuietHoursToggle}
|
|
156
|
-
onStartTimePress={handleStartTimePress}
|
|
157
|
-
onEndTimePress={handleEndTimePress}
|
|
115
|
+
onStartTimePress={timePicker.handleStartTimePress}
|
|
116
|
+
onEndTimePress={timePicker.handleEndTimePress}
|
|
158
117
|
/>
|
|
159
118
|
</>
|
|
160
119
|
)}
|
|
161
120
|
</View>
|
|
162
|
-
{pickerMode && (
|
|
121
|
+
{timePicker.pickerMode && (
|
|
163
122
|
<DateTimePicker
|
|
164
|
-
value={getPickerDate()}
|
|
123
|
+
value={timePicker.getPickerDate()}
|
|
165
124
|
mode="time"
|
|
166
125
|
is24Hour={true}
|
|
167
|
-
onChange={handleTimeChange}
|
|
126
|
+
onChange={timePicker.handleTimeChange}
|
|
168
127
|
/>
|
|
169
128
|
)}
|
|
170
129
|
</ScreenLayout>
|
|
171
130
|
);
|
|
172
131
|
};
|
|
173
|
-
|
|
174
|
-
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
175
|
-
StyleSheet.create({
|
|
176
|
-
container: {
|
|
177
|
-
flex: 1,
|
|
178
|
-
padding: 16,
|
|
179
|
-
},
|
|
180
|
-
loadingContainer: {
|
|
181
|
-
flex: 1,
|
|
182
|
-
justifyContent: 'center',
|
|
183
|
-
alignItems: 'center',
|
|
184
|
-
},
|
|
185
|
-
card: {
|
|
186
|
-
marginBottom: 16,
|
|
187
|
-
padding: 16,
|
|
188
|
-
backgroundColor: tokens.colors.surface,
|
|
189
|
-
},
|
|
190
|
-
divider: {
|
|
191
|
-
height: 1,
|
|
192
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
193
|
-
marginVertical: 12,
|
|
194
|
-
},
|
|
195
|
-
navRow: {
|
|
196
|
-
flexDirection: 'row',
|
|
197
|
-
alignItems: 'center',
|
|
198
|
-
},
|
|
199
|
-
iconContainer: {
|
|
200
|
-
width: 44,
|
|
201
|
-
height: 44,
|
|
202
|
-
borderRadius: 22,
|
|
203
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
204
|
-
justifyContent: 'center',
|
|
205
|
-
alignItems: 'center',
|
|
206
|
-
marginRight: 12,
|
|
207
|
-
},
|
|
208
|
-
textContainer: {
|
|
209
|
-
flex: 1,
|
|
210
|
-
marginRight: 12,
|
|
211
|
-
},
|
|
212
|
-
badge: {
|
|
213
|
-
backgroundColor: tokens.colors.primary,
|
|
214
|
-
paddingHorizontal: 8,
|
|
215
|
-
paddingVertical: 2,
|
|
216
|
-
borderRadius: 10,
|
|
217
|
-
marginRight: 8,
|
|
218
|
-
},
|
|
219
|
-
badgeText: {
|
|
220
|
-
color: tokens.colors.surface,
|
|
221
|
-
fontWeight: '600',
|
|
222
|
-
},
|
|
223
|
-
});
|