@umituz/react-native-notifications 1.1.6 → 1.3.0
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 +18 -40
- package/src/index.ts +84 -13
- package/src/infrastructure/config/reminderPresets.ts +120 -0
- package/src/infrastructure/hooks/useQuietHoursActions.ts +52 -0
- package/src/infrastructure/hooks/useReminderActions.ts +147 -0
- package/src/infrastructure/services/types.ts +137 -44
- package/src/infrastructure/storage/RemindersStore.ts +150 -0
- package/src/presentation/components/FormButton.tsx +66 -0
- package/src/presentation/components/FrequencySelector.tsx +72 -0
- package/src/presentation/components/NotificationsSection.tsx +96 -159
- package/src/presentation/components/QuietHoursCard.tsx +105 -0
- package/src/presentation/components/ReminderForm.tsx +165 -0
- package/src/presentation/components/ReminderItem.tsx +124 -0
- package/src/presentation/components/TimePresetSelector.tsx +100 -0
- package/src/presentation/components/WeekdaySelector.tsx +61 -0
- package/src/presentation/screens/NotificationSettingsScreen.tsx +210 -0
- package/src/presentation/screens/ReminderListScreen.tsx +138 -0
- package/src/types/global.d.ts +11 -8
- package/lib/index.d.ts +0 -16
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -23
- package/lib/index.js.map +0 -1
- package/lib/infrastructure/config/notificationsConfig.d.ts +0 -20
- package/lib/infrastructure/config/notificationsConfig.d.ts.map +0 -1
- package/lib/infrastructure/config/notificationsConfig.js +0 -81
- package/lib/infrastructure/config/notificationsConfig.js.map +0 -1
- package/lib/infrastructure/hooks/actions/useNotificationActions.d.ts +0 -7
- package/lib/infrastructure/hooks/actions/useNotificationActions.d.ts.map +0 -1
- package/lib/infrastructure/hooks/actions/useNotificationActions.js +0 -75
- package/lib/infrastructure/hooks/actions/useNotificationActions.js.map +0 -1
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.d.ts +0 -8
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.d.ts.map +0 -1
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.js +0 -78
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.js.map +0 -1
- package/lib/infrastructure/hooks/state/useNotificationsState.d.ts +0 -12
- package/lib/infrastructure/hooks/state/useNotificationsState.d.ts.map +0 -1
- package/lib/infrastructure/hooks/state/useNotificationsState.js +0 -30
- package/lib/infrastructure/hooks/state/useNotificationsState.js.map +0 -1
- package/lib/infrastructure/hooks/types.d.ts +0 -87
- package/lib/infrastructure/hooks/types.d.ts.map +0 -1
- package/lib/infrastructure/hooks/types.js +0 -8
- package/lib/infrastructure/hooks/types.js.map +0 -1
- package/lib/infrastructure/hooks/useNotificationSettings.d.ts +0 -10
- package/lib/infrastructure/hooks/useNotificationSettings.d.ts.map +0 -1
- package/lib/infrastructure/hooks/useNotificationSettings.js +0 -43
- package/lib/infrastructure/hooks/useNotificationSettings.js.map +0 -1
- package/lib/infrastructure/hooks/useNotifications.d.ts +0 -24
- package/lib/infrastructure/hooks/useNotifications.d.ts.map +0 -1
- package/lib/infrastructure/hooks/useNotifications.js +0 -72
- package/lib/infrastructure/hooks/useNotifications.js.map +0 -1
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.d.ts +0 -8
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.d.ts.map +0 -1
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.js +0 -99
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.js.map +0 -1
- package/lib/infrastructure/services/NotificationBadgeManager.d.ts +0 -5
- package/lib/infrastructure/services/NotificationBadgeManager.d.ts.map +0 -1
- package/lib/infrastructure/services/NotificationBadgeManager.js +0 -29
- package/lib/infrastructure/services/NotificationBadgeManager.js.map +0 -1
- package/lib/infrastructure/services/NotificationManager.d.ts +0 -59
- package/lib/infrastructure/services/NotificationManager.d.ts.map +0 -1
- package/lib/infrastructure/services/NotificationManager.js +0 -118
- package/lib/infrastructure/services/NotificationManager.js.map +0 -1
- package/lib/infrastructure/services/NotificationPermissions.d.ts +0 -6
- package/lib/infrastructure/services/NotificationPermissions.d.ts.map +0 -1
- package/lib/infrastructure/services/NotificationPermissions.js +0 -75
- package/lib/infrastructure/services/NotificationPermissions.js.map +0 -1
- package/lib/infrastructure/services/NotificationScheduler.d.ts +0 -8
- package/lib/infrastructure/services/NotificationScheduler.d.ts.map +0 -1
- package/lib/infrastructure/services/NotificationScheduler.js +0 -72
- package/lib/infrastructure/services/NotificationScheduler.js.map +0 -1
- package/lib/infrastructure/services/NotificationService.d.ts +0 -30
- package/lib/infrastructure/services/NotificationService.d.ts.map +0 -1
- package/lib/infrastructure/services/NotificationService.js +0 -41
- package/lib/infrastructure/services/NotificationService.js.map +0 -1
- package/lib/infrastructure/services/channels/ChannelManager.d.ts +0 -18
- package/lib/infrastructure/services/channels/ChannelManager.d.ts.map +0 -1
- package/lib/infrastructure/services/channels/ChannelManager.js +0 -87
- package/lib/infrastructure/services/channels/ChannelManager.js.map +0 -1
- package/lib/infrastructure/services/delivery/NotificationDelivery.d.ts +0 -10
- package/lib/infrastructure/services/delivery/NotificationDelivery.d.ts.map +0 -1
- package/lib/infrastructure/services/delivery/NotificationDelivery.js +0 -71
- package/lib/infrastructure/services/delivery/NotificationDelivery.js.map +0 -1
- package/lib/infrastructure/services/preferences/PreferencesManager.d.ts +0 -18
- package/lib/infrastructure/services/preferences/PreferencesManager.d.ts.map +0 -1
- package/lib/infrastructure/services/preferences/PreferencesManager.js +0 -65
- package/lib/infrastructure/services/preferences/PreferencesManager.js.map +0 -1
- package/lib/infrastructure/services/types.d.ts +0 -89
- package/lib/infrastructure/services/types.d.ts.map +0 -1
- package/lib/infrastructure/services/types.js +0 -7
- package/lib/infrastructure/services/types.js.map +0 -1
- package/lib/infrastructure/storage/NotificationsStore.d.ts +0 -23
- package/lib/infrastructure/storage/NotificationsStore.d.ts.map +0 -1
- package/lib/infrastructure/storage/NotificationsStore.js +0 -26
- package/lib/infrastructure/storage/NotificationsStore.js.map +0 -1
- package/lib/infrastructure/utils/dev.d.ts +0 -5
- package/lib/infrastructure/utils/dev.d.ts.map +0 -1
- package/lib/infrastructure/utils/dev.js +0 -24
- package/lib/infrastructure/utils/dev.js.map +0 -1
- package/lib/presentation/components/NotificationsSection.d.ts +0 -17
- package/lib/presentation/components/NotificationsSection.d.ts.map +0 -1
- package/lib/presentation/components/NotificationsSection.js +0 -132
- package/lib/presentation/components/NotificationsSection.js.map +0 -1
- package/lib/presentation/screens/NotificationsScreen.d.ts +0 -20
- package/lib/presentation/screens/NotificationsScreen.d.ts.map +0 -1
- package/lib/presentation/screens/NotificationsScreen.js +0 -74
- package/lib/presentation/screens/NotificationsScreen.js.map +0 -1
- package/src/__tests__/NotificationManager.test.ts +0 -215
- package/src/__tests__/useNotificationActions.test.ts +0 -189
- package/src/__tests__/useNotificationRefresh.test.ts +0 -213
- package/src/infrastructure/hooks/actions/useNotificationActions.ts +0 -131
- package/src/infrastructure/hooks/actions/useNotificationManagementActions.ts +0 -131
- package/src/infrastructure/hooks/state/useNotificationsState.ts +0 -46
- package/src/infrastructure/hooks/types.ts +0 -83
- package/src/infrastructure/hooks/useNotifications.ts +0 -96
- package/src/infrastructure/hooks/utils/useNotificationRefresh.ts +0 -131
- package/src/infrastructure/services/channels/ChannelManager.ts +0 -111
- package/src/infrastructure/services/delivery/NotificationDelivery.ts +0 -83
- package/src/infrastructure/services/preferences/PreferencesManager.ts +0 -77
|
@@ -1,182 +1,119 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* NotificationsSection Component
|
|
3
|
+
* Settings section for notifications with toggle
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
7
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
8
|
+
import { View, TouchableOpacity, StyleSheet, Switch } from 'react-native';
|
|
9
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
5
10
|
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
6
11
|
import { notificationService } from '../../infrastructure/services/NotificationService';
|
|
7
12
|
|
|
8
13
|
export interface NotificationsSectionConfig {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
showToggle?: boolean;
|
|
14
|
+
initialValue?: boolean;
|
|
15
|
+
onToggleChange?: (value: boolean) => void;
|
|
16
|
+
route?: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
showToggle?: boolean;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
export interface NotificationsSectionProps {
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
config?: NotificationsSectionConfig;
|
|
24
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
25
|
+
onNavigate?: (route: string) => void;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export const NotificationsSection: React.FC<NotificationsSectionProps> = ({
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
config,
|
|
30
|
+
containerStyle,
|
|
31
|
+
onNavigate,
|
|
26
32
|
}) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const colors = tokens.colors;
|
|
30
|
-
|
|
31
|
-
const [notificationsEnabled, setNotificationsEnabled] = useState(
|
|
32
|
-
config?.initialValue ?? true,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (config?.initialValue !== undefined) {
|
|
37
|
-
setNotificationsEnabled(config.initialValue);
|
|
38
|
-
}
|
|
39
|
-
}, [config?.initialValue]);
|
|
40
|
-
|
|
41
|
-
const handleToggle = useCallback(async (value: boolean) => {
|
|
42
|
-
if (!value) {
|
|
43
|
-
// When turning ON (value is false -> true? Wait. onChange value is the NEW value)
|
|
44
|
-
// If value is true (turning on)
|
|
45
|
-
}
|
|
33
|
+
const tokens = useAppDesignTokens();
|
|
34
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
/*
|
|
49
|
-
if (notificationService && !value) { // Wait, logic in settings was: if (!value) ...?
|
|
50
|
-
// Actually typically we request permissions when enabling.
|
|
51
|
-
// Settings code:
|
|
52
|
-
// if (notificationService && !value) { ... } -> This implies when value is FALSE?
|
|
53
|
-
// Ah, maybe the switch value logic was inverted or I misread.
|
|
54
|
-
// Let's re-read settings code.
|
|
55
|
-
}
|
|
56
|
-
*/
|
|
36
|
+
const [enabled, setEnabled] = useState(config?.initialValue ?? false);
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Permission denied, maybe don't enable switch?
|
|
64
|
-
// For now just allow toggle and let app handle it or sync with state
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (config?.initialValue !== undefined) {
|
|
40
|
+
setEnabled(config.initialValue);
|
|
41
|
+
}
|
|
42
|
+
}, [config?.initialValue]);
|
|
68
43
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
const handleToggle = useCallback(async (value: boolean) => {
|
|
45
|
+
if (value) {
|
|
46
|
+
const hasPermissions = await notificationService.hasPermissions();
|
|
47
|
+
if (!hasPermissions) {
|
|
48
|
+
const granted = await notificationService.requestPermissions();
|
|
49
|
+
if (!granted) return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
setEnabled(value);
|
|
53
|
+
config?.onToggleChange?.(value);
|
|
54
|
+
}, [config]);
|
|
72
55
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
56
|
+
const handlePress = useCallback(async () => {
|
|
57
|
+
const hasPermissions = await notificationService.hasPermissions();
|
|
58
|
+
if (!hasPermissions) {
|
|
59
|
+
await notificationService.requestPermissions();
|
|
60
|
+
}
|
|
61
|
+
if (config?.route && onNavigate) {
|
|
62
|
+
onNavigate(config.route);
|
|
63
|
+
}
|
|
64
|
+
}, [config?.route, onNavigate]);
|
|
80
65
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
const title = config?.title || 'Notifications';
|
|
67
|
+
const description = config?.description || 'Manage notification preferences';
|
|
68
|
+
const showToggle = config?.showToggle ?? true;
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
return (
|
|
71
|
+
<View style={[styles.container, containerStyle]}>
|
|
72
|
+
<AtomicText type="bodyLarge" style={styles.sectionTitle}>General</AtomicText>
|
|
88
73
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
style={[
|
|
102
|
-
styles.iconContainer,
|
|
103
|
-
{ backgroundColor: `${colors.primary}15` },
|
|
104
|
-
]}
|
|
105
|
-
>
|
|
106
|
-
<Feather name="bell" size={24} color={colors.primary} />
|
|
107
|
-
</View>
|
|
108
|
-
<View style={styles.textContainer}>
|
|
109
|
-
<Text style={[styles.title, { color: colors.textPrimary }]}>{title}</Text>
|
|
110
|
-
{!showToggle && (
|
|
111
|
-
<Text style={[styles.description, { color: colors.textSecondary }]}>
|
|
112
|
-
{description}
|
|
113
|
-
</Text>
|
|
114
|
-
)}
|
|
115
|
-
</View>
|
|
116
|
-
|
|
117
|
-
{showToggle ? (
|
|
118
|
-
<Switch
|
|
119
|
-
value={notificationsEnabled}
|
|
120
|
-
onValueChange={handleToggle}
|
|
121
|
-
trackColor={{
|
|
122
|
-
false: `${colors.textSecondary}30`,
|
|
123
|
-
true: colors.primary,
|
|
124
|
-
}}
|
|
125
|
-
thumbColor={"#FFFFFF"}
|
|
126
|
-
ios_backgroundColor={`${colors.textSecondary}30`}
|
|
127
|
-
/>
|
|
128
|
-
) : (
|
|
129
|
-
<Feather name="chevron-right" size={20} color={colors.textSecondary} />
|
|
130
|
-
)}
|
|
131
|
-
</View>
|
|
132
|
-
</Pressable>
|
|
74
|
+
<TouchableOpacity
|
|
75
|
+
style={styles.itemContainer}
|
|
76
|
+
onPress={showToggle ? undefined : handlePress}
|
|
77
|
+
disabled={showToggle}
|
|
78
|
+
activeOpacity={0.7}
|
|
79
|
+
>
|
|
80
|
+
<View style={styles.iconContainer}>
|
|
81
|
+
<AtomicIcon name="bell" size="md" color="primary" />
|
|
82
|
+
</View>
|
|
83
|
+
<View style={styles.textContainer}>
|
|
84
|
+
<AtomicText type="bodyLarge">{title}</AtomicText>
|
|
85
|
+
{!showToggle && <AtomicText type="bodySmall" style={styles.description}>{description}</AtomicText>}
|
|
133
86
|
</View>
|
|
134
|
-
|
|
87
|
+
|
|
88
|
+
{showToggle ? (
|
|
89
|
+
<Switch
|
|
90
|
+
value={enabled}
|
|
91
|
+
onValueChange={handleToggle}
|
|
92
|
+
trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
|
|
93
|
+
thumbColor={tokens.colors.surface}
|
|
94
|
+
/>
|
|
95
|
+
) : (
|
|
96
|
+
<AtomicIcon name="chevron-right" size="md" color="textSecondary" />
|
|
97
|
+
)}
|
|
98
|
+
</TouchableOpacity>
|
|
99
|
+
</View>
|
|
100
|
+
);
|
|
135
101
|
};
|
|
136
102
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
},
|
|
143
|
-
sectionTitle: {
|
|
144
|
-
fontSize: 18,
|
|
145
|
-
fontWeight: '600',
|
|
146
|
-
paddingHorizontal: 16,
|
|
147
|
-
paddingTop: 16,
|
|
148
|
-
paddingBottom: 8,
|
|
149
|
-
},
|
|
150
|
-
itemContainer: {
|
|
151
|
-
flexDirection: 'row',
|
|
152
|
-
alignItems: 'center',
|
|
153
|
-
paddingHorizontal: 16,
|
|
154
|
-
paddingVertical: 16,
|
|
155
|
-
minHeight: 72,
|
|
156
|
-
},
|
|
157
|
-
content: {
|
|
158
|
-
flex: 1,
|
|
159
|
-
flexDirection: 'row',
|
|
160
|
-
alignItems: 'center',
|
|
161
|
-
},
|
|
103
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
104
|
+
StyleSheet.create({
|
|
105
|
+
container: { marginBottom: 16, borderRadius: 12, overflow: 'hidden', backgroundColor: tokens.colors.surface },
|
|
106
|
+
sectionTitle: { fontWeight: '600', paddingHorizontal: 16, paddingTop: 16, paddingBottom: 8 },
|
|
107
|
+
itemContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, minHeight: 72 },
|
|
162
108
|
iconContainer: {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
textContainer: {
|
|
171
|
-
flex: 1,
|
|
172
|
-
marginRight: 8,
|
|
173
|
-
},
|
|
174
|
-
title: {
|
|
175
|
-
fontSize: 16,
|
|
176
|
-
fontWeight: '500',
|
|
177
|
-
marginBottom: 4,
|
|
178
|
-
},
|
|
179
|
-
description: {
|
|
180
|
-
fontSize: 14,
|
|
109
|
+
width: 48,
|
|
110
|
+
height: 48,
|
|
111
|
+
borderRadius: 12,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
marginRight: 16,
|
|
115
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
181
116
|
},
|
|
182
|
-
}
|
|
117
|
+
textContainer: { flex: 1, marginRight: 8 },
|
|
118
|
+
description: { color: tokens.colors.textSecondary, marginTop: 4 },
|
|
119
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuietHoursCard Component
|
|
3
|
+
* Displays and manages quiet hours settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
8
|
+
import { AtomicText, AtomicIcon, AtomicSwitch, AtomicCard } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
10
|
+
import type { QuietHoursConfig, QuietHoursTranslations } from '../../infrastructure/services/types';
|
|
11
|
+
|
|
12
|
+
export interface QuietHoursCardProps {
|
|
13
|
+
config: QuietHoursConfig;
|
|
14
|
+
translations: QuietHoursTranslations;
|
|
15
|
+
onToggle: (enabled: boolean) => void;
|
|
16
|
+
onStartTimePress: () => void;
|
|
17
|
+
onEndTimePress: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const formatTime = (hour: number, minute: number): string => {
|
|
21
|
+
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const QuietHoursCard: React.FC<QuietHoursCardProps> = ({
|
|
25
|
+
config,
|
|
26
|
+
translations,
|
|
27
|
+
onToggle,
|
|
28
|
+
onStartTimePress,
|
|
29
|
+
onEndTimePress,
|
|
30
|
+
}) => {
|
|
31
|
+
const tokens = useAppDesignTokens();
|
|
32
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<AtomicCard style={styles.card}>
|
|
36
|
+
<View style={styles.header}>
|
|
37
|
+
<View style={styles.iconContainer}>
|
|
38
|
+
<AtomicIcon name="moon" size="md" color="primary" />
|
|
39
|
+
</View>
|
|
40
|
+
<View style={styles.headerText}>
|
|
41
|
+
<AtomicText type="bodyLarge">{translations.title}</AtomicText>
|
|
42
|
+
<AtomicText type="bodySmall" style={styles.description}>{translations.description}</AtomicText>
|
|
43
|
+
</View>
|
|
44
|
+
<AtomicSwitch value={config.enabled} onValueChange={onToggle} />
|
|
45
|
+
</View>
|
|
46
|
+
|
|
47
|
+
{config.enabled && (
|
|
48
|
+
<View style={styles.timeContainer}>
|
|
49
|
+
<TouchableOpacity style={styles.timeButton} onPress={onStartTimePress} activeOpacity={0.7}>
|
|
50
|
+
<AtomicText type="bodySmall" style={styles.timeLabel}>{translations.startTimeLabel}</AtomicText>
|
|
51
|
+
<AtomicText type="bodyLarge" style={styles.timeValue}>
|
|
52
|
+
{formatTime(config.startHour, config.startMinute)}
|
|
53
|
+
</AtomicText>
|
|
54
|
+
</TouchableOpacity>
|
|
55
|
+
|
|
56
|
+
<View style={styles.timeSeparator}>
|
|
57
|
+
<AtomicIcon name="arrow-right" size="sm" color="textSecondary" />
|
|
58
|
+
</View>
|
|
59
|
+
|
|
60
|
+
<TouchableOpacity style={styles.timeButton} onPress={onEndTimePress} activeOpacity={0.7}>
|
|
61
|
+
<AtomicText type="bodySmall" style={styles.timeLabel}>{translations.endTimeLabel}</AtomicText>
|
|
62
|
+
<AtomicText type="bodyLarge" style={styles.timeValue}>
|
|
63
|
+
{formatTime(config.endHour, config.endMinute)}
|
|
64
|
+
</AtomicText>
|
|
65
|
+
</TouchableOpacity>
|
|
66
|
+
</View>
|
|
67
|
+
)}
|
|
68
|
+
</AtomicCard>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
73
|
+
StyleSheet.create({
|
|
74
|
+
card: { padding: 16, backgroundColor: tokens.colors.surface },
|
|
75
|
+
header: { flexDirection: 'row', alignItems: 'center' },
|
|
76
|
+
iconContainer: {
|
|
77
|
+
width: 48,
|
|
78
|
+
height: 48,
|
|
79
|
+
borderRadius: 24,
|
|
80
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
marginRight: 12,
|
|
84
|
+
},
|
|
85
|
+
headerText: { flex: 1, marginRight: 12 },
|
|
86
|
+
description: { color: tokens.colors.textSecondary, marginTop: 2 },
|
|
87
|
+
timeContainer: {
|
|
88
|
+
flexDirection: 'row',
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
marginTop: 16,
|
|
91
|
+
paddingTop: 16,
|
|
92
|
+
borderTopWidth: 1,
|
|
93
|
+
borderTopColor: tokens.colors.surfaceSecondary,
|
|
94
|
+
},
|
|
95
|
+
timeButton: {
|
|
96
|
+
flex: 1,
|
|
97
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
98
|
+
borderRadius: 8,
|
|
99
|
+
padding: 12,
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
},
|
|
102
|
+
timeLabel: { color: tokens.colors.textSecondary, marginBottom: 4 },
|
|
103
|
+
timeValue: { color: tokens.colors.textPrimary, fontWeight: '600' },
|
|
104
|
+
timeSeparator: { paddingHorizontal: 12 },
|
|
105
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReminderForm Component
|
|
3
|
+
* Form for creating and editing reminders
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
7
|
+
import { View, TextInput, StyleSheet, ScrollView } from 'react-native';
|
|
8
|
+
import { AtomicText } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
10
|
+
import { TimePresetSelector } from './TimePresetSelector';
|
|
11
|
+
import { FrequencySelector } from './FrequencySelector';
|
|
12
|
+
import { WeekdaySelector } from './WeekdaySelector';
|
|
13
|
+
import { FormButton } from './FormButton';
|
|
14
|
+
import { DEFAULT_TIME_PRESETS, FREQUENCY_OPTIONS } from '../../infrastructure/config/reminderPresets';
|
|
15
|
+
import type { Reminder, ReminderFrequency, CreateReminderInput, TimePreset } from '../../infrastructure/services/types';
|
|
16
|
+
|
|
17
|
+
export interface ReminderFormTranslations {
|
|
18
|
+
titleLabel: string;
|
|
19
|
+
titlePlaceholder: string;
|
|
20
|
+
bodyLabel: string;
|
|
21
|
+
bodyPlaceholder: string;
|
|
22
|
+
timeLabel: string;
|
|
23
|
+
frequencyLabel: string;
|
|
24
|
+
weekdayLabel: string;
|
|
25
|
+
saveButton: string;
|
|
26
|
+
cancelButton: string;
|
|
27
|
+
customTimeLabel: string;
|
|
28
|
+
getPresetLabel: (key: string) => string;
|
|
29
|
+
getFrequencyLabel: (key: string) => string;
|
|
30
|
+
getWeekdayLabel: (key: string) => string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReminderFormProps {
|
|
34
|
+
initialData?: Reminder;
|
|
35
|
+
translations: ReminderFormTranslations;
|
|
36
|
+
onSave: (data: CreateReminderInput) => void;
|
|
37
|
+
onCancel: () => void;
|
|
38
|
+
timePresets?: TimePreset[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ReminderForm: React.FC<ReminderFormProps> = ({
|
|
42
|
+
initialData,
|
|
43
|
+
translations,
|
|
44
|
+
onSave,
|
|
45
|
+
onCancel,
|
|
46
|
+
timePresets = DEFAULT_TIME_PRESETS,
|
|
47
|
+
}) => {
|
|
48
|
+
const tokens = useAppDesignTokens();
|
|
49
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
50
|
+
|
|
51
|
+
const [title, setTitle] = useState(initialData?.title || '');
|
|
52
|
+
const [body, setBody] = useState(initialData?.body || '');
|
|
53
|
+
const [frequency, setFrequency] = useState<ReminderFrequency>(initialData?.frequency || 'daily');
|
|
54
|
+
const [selectedPresetId, setSelectedPresetId] = useState<string | undefined>(initialData?.timePresetId);
|
|
55
|
+
const [hour, setHour] = useState(initialData?.hour ?? 9);
|
|
56
|
+
const [minute, setMinute] = useState(initialData?.minute ?? 0);
|
|
57
|
+
const [weekday, setWeekday] = useState(initialData?.weekday ?? 2);
|
|
58
|
+
const [isCustomTime, setIsCustomTime] = useState(!initialData?.timePresetId);
|
|
59
|
+
|
|
60
|
+
const handlePresetSelect = useCallback((preset: TimePreset) => {
|
|
61
|
+
setSelectedPresetId(preset.id);
|
|
62
|
+
setHour(preset.hour);
|
|
63
|
+
setMinute(preset.minute);
|
|
64
|
+
setIsCustomTime(false);
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const handleCustomSelect = useCallback(() => {
|
|
68
|
+
setSelectedPresetId(undefined);
|
|
69
|
+
setIsCustomTime(true);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const handleSave = useCallback(() => {
|
|
73
|
+
if (!title.trim()) return;
|
|
74
|
+
onSave({
|
|
75
|
+
title: title.trim(),
|
|
76
|
+
body: body.trim(),
|
|
77
|
+
frequency,
|
|
78
|
+
timePresetId: isCustomTime ? undefined : selectedPresetId,
|
|
79
|
+
hour,
|
|
80
|
+
minute,
|
|
81
|
+
weekday: frequency === 'weekly' ? weekday : undefined,
|
|
82
|
+
dayOfMonth: frequency === 'monthly' ? 1 : undefined,
|
|
83
|
+
});
|
|
84
|
+
}, [title, body, frequency, selectedPresetId, hour, minute, weekday, isCustomTime, onSave]);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
|
|
88
|
+
<View style={styles.section}>
|
|
89
|
+
<AtomicText type="bodyMedium" style={styles.label}>{translations.titleLabel}</AtomicText>
|
|
90
|
+
<TextInput
|
|
91
|
+
style={styles.input}
|
|
92
|
+
value={title}
|
|
93
|
+
onChangeText={setTitle}
|
|
94
|
+
placeholder={translations.titlePlaceholder}
|
|
95
|
+
placeholderTextColor={tokens.colors.textSecondary}
|
|
96
|
+
/>
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
<View style={styles.section}>
|
|
100
|
+
<AtomicText type="bodyMedium" style={styles.label}>{translations.bodyLabel}</AtomicText>
|
|
101
|
+
<TextInput
|
|
102
|
+
style={[styles.input, styles.multilineInput]}
|
|
103
|
+
value={body}
|
|
104
|
+
onChangeText={setBody}
|
|
105
|
+
placeholder={translations.bodyPlaceholder}
|
|
106
|
+
placeholderTextColor={tokens.colors.textSecondary}
|
|
107
|
+
multiline
|
|
108
|
+
numberOfLines={3}
|
|
109
|
+
/>
|
|
110
|
+
</View>
|
|
111
|
+
|
|
112
|
+
<View style={styles.section}>
|
|
113
|
+
<AtomicText type="bodyMedium" style={styles.label}>{translations.frequencyLabel}</AtomicText>
|
|
114
|
+
<FrequencySelector
|
|
115
|
+
options={FREQUENCY_OPTIONS}
|
|
116
|
+
selectedFrequency={frequency}
|
|
117
|
+
onSelect={setFrequency}
|
|
118
|
+
getLabel={translations.getFrequencyLabel}
|
|
119
|
+
/>
|
|
120
|
+
</View>
|
|
121
|
+
|
|
122
|
+
<View style={styles.section}>
|
|
123
|
+
<AtomicText type="bodyMedium" style={styles.label}>{translations.timeLabel}</AtomicText>
|
|
124
|
+
<TimePresetSelector
|
|
125
|
+
presets={timePresets}
|
|
126
|
+
selectedPresetId={selectedPresetId}
|
|
127
|
+
customTime={isCustomTime ? { hour, minute } : undefined}
|
|
128
|
+
onSelectPreset={handlePresetSelect}
|
|
129
|
+
onSelectCustom={handleCustomSelect}
|
|
130
|
+
getPresetLabel={translations.getPresetLabel}
|
|
131
|
+
customLabel={translations.customTimeLabel}
|
|
132
|
+
isCustomSelected={isCustomTime}
|
|
133
|
+
/>
|
|
134
|
+
</View>
|
|
135
|
+
|
|
136
|
+
{frequency === 'weekly' && (
|
|
137
|
+
<View style={styles.section}>
|
|
138
|
+
<AtomicText type="bodyMedium" style={styles.label}>{translations.weekdayLabel}</AtomicText>
|
|
139
|
+
<WeekdaySelector selectedWeekday={weekday} onSelect={setWeekday} getLabel={translations.getWeekdayLabel} />
|
|
140
|
+
</View>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
<View style={styles.buttonRow}>
|
|
144
|
+
<FormButton label={translations.cancelButton} onPress={onCancel} variant="secondary" />
|
|
145
|
+
<FormButton label={translations.saveButton} onPress={handleSave} disabled={!title.trim()} />
|
|
146
|
+
</View>
|
|
147
|
+
</ScrollView>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
152
|
+
StyleSheet.create({
|
|
153
|
+
container: { flex: 1, padding: 16 },
|
|
154
|
+
section: { marginBottom: 20 },
|
|
155
|
+
label: { color: tokens.colors.textPrimary, marginBottom: 8 },
|
|
156
|
+
input: {
|
|
157
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
158
|
+
borderRadius: 8,
|
|
159
|
+
padding: 12,
|
|
160
|
+
fontSize: 16,
|
|
161
|
+
color: tokens.colors.textPrimary,
|
|
162
|
+
},
|
|
163
|
+
multilineInput: { minHeight: 80, textAlignVertical: 'top' },
|
|
164
|
+
buttonRow: { flexDirection: 'row', gap: 12, marginTop: 24 },
|
|
165
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReminderItem Component
|
|
3
|
+
* Displays a single reminder with toggle and actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
8
|
+
import { AtomicText, AtomicIcon, AtomicSwitch } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
10
|
+
import type { Reminder, ReminderFrequency } from '../../infrastructure/services/types';
|
|
11
|
+
|
|
12
|
+
export interface ReminderItemTranslations {
|
|
13
|
+
frequencyOnce: string;
|
|
14
|
+
frequencyDaily: string;
|
|
15
|
+
frequencyWeekly: string;
|
|
16
|
+
frequencyMonthly: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ReminderItemProps {
|
|
20
|
+
reminder: Reminder;
|
|
21
|
+
translations: ReminderItemTranslations;
|
|
22
|
+
onToggle: (id: string) => void;
|
|
23
|
+
onEdit: (reminder: Reminder) => void;
|
|
24
|
+
onDelete: (id: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const formatTime = (hour: number, minute: number): string => {
|
|
28
|
+
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getFrequencyIcon = (frequency: ReminderFrequency): string => {
|
|
32
|
+
const icons: Record<ReminderFrequency, string> = {
|
|
33
|
+
once: 'calendar',
|
|
34
|
+
daily: 'repeat',
|
|
35
|
+
weekly: 'calendar-days',
|
|
36
|
+
monthly: 'calendar-range',
|
|
37
|
+
};
|
|
38
|
+
return icons[frequency] || 'bell';
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const ReminderItem: React.FC<ReminderItemProps> = ({
|
|
42
|
+
reminder,
|
|
43
|
+
translations,
|
|
44
|
+
onToggle,
|
|
45
|
+
onEdit,
|
|
46
|
+
onDelete,
|
|
47
|
+
}) => {
|
|
48
|
+
const tokens = useAppDesignTokens();
|
|
49
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
50
|
+
|
|
51
|
+
const getFrequencyLabel = (frequency: ReminderFrequency): string => {
|
|
52
|
+
const labels: Record<ReminderFrequency, string> = {
|
|
53
|
+
once: translations.frequencyOnce,
|
|
54
|
+
daily: translations.frequencyDaily,
|
|
55
|
+
weekly: translations.frequencyWeekly,
|
|
56
|
+
monthly: translations.frequencyMonthly,
|
|
57
|
+
};
|
|
58
|
+
return labels[frequency] || '';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View style={[styles.container, !reminder.enabled ? styles.disabled : undefined]}>
|
|
63
|
+
<TouchableOpacity style={styles.content} onPress={() => onEdit(reminder)} activeOpacity={0.7}>
|
|
64
|
+
<View style={styles.iconContainer}>
|
|
65
|
+
<AtomicIcon
|
|
66
|
+
name={getFrequencyIcon(reminder.frequency)}
|
|
67
|
+
size="md"
|
|
68
|
+
color={reminder.enabled ? 'primary' : 'textSecondary'}
|
|
69
|
+
/>
|
|
70
|
+
</View>
|
|
71
|
+
<View style={styles.textContainer}>
|
|
72
|
+
<AtomicText type="bodyLarge" style={!reminder.enabled ? styles.disabledText : undefined}>
|
|
73
|
+
{reminder.title}
|
|
74
|
+
</AtomicText>
|
|
75
|
+
<View style={styles.metaRow}>
|
|
76
|
+
<AtomicText type="bodySmall" style={styles.time}>
|
|
77
|
+
{formatTime(reminder.hour, reminder.minute)}
|
|
78
|
+
</AtomicText>
|
|
79
|
+
<AtomicText type="bodySmall" style={styles.frequency}>
|
|
80
|
+
{getFrequencyLabel(reminder.frequency)}
|
|
81
|
+
</AtomicText>
|
|
82
|
+
</View>
|
|
83
|
+
</View>
|
|
84
|
+
</TouchableOpacity>
|
|
85
|
+
|
|
86
|
+
<View style={styles.actions}>
|
|
87
|
+
<TouchableOpacity style={styles.deleteButton} onPress={() => onDelete(reminder.id)}>
|
|
88
|
+
<AtomicIcon name="trash-2" size="sm" color="error" />
|
|
89
|
+
</TouchableOpacity>
|
|
90
|
+
<AtomicSwitch value={reminder.enabled} onValueChange={() => onToggle(reminder.id)} />
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
97
|
+
StyleSheet.create({
|
|
98
|
+
container: {
|
|
99
|
+
flexDirection: 'row',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
padding: 16,
|
|
102
|
+
backgroundColor: tokens.colors.surface,
|
|
103
|
+
borderRadius: 12,
|
|
104
|
+
marginBottom: 8,
|
|
105
|
+
},
|
|
106
|
+
disabled: { opacity: 0.6 },
|
|
107
|
+
content: { flex: 1, flexDirection: 'row', alignItems: 'center' },
|
|
108
|
+
iconContainer: {
|
|
109
|
+
width: 44,
|
|
110
|
+
height: 44,
|
|
111
|
+
borderRadius: 22,
|
|
112
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
113
|
+
justifyContent: 'center',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
marginRight: 12,
|
|
116
|
+
},
|
|
117
|
+
textContainer: { flex: 1 },
|
|
118
|
+
disabledText: { color: tokens.colors.textSecondary },
|
|
119
|
+
metaRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 4 },
|
|
120
|
+
time: { color: tokens.colors.primary, fontWeight: '600' },
|
|
121
|
+
frequency: { color: tokens.colors.textSecondary },
|
|
122
|
+
actions: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
|
123
|
+
deleteButton: { padding: 4 },
|
|
124
|
+
});
|