@umituz/react-native-notifications 1.3.10 → 1.3.11
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
CHANGED
package/src/index.ts
CHANGED
|
@@ -74,6 +74,7 @@ export {
|
|
|
74
74
|
export { useNotificationSettings } from './infrastructure/hooks/useNotificationSettings';
|
|
75
75
|
export { useReminderActions } from './infrastructure/hooks/useReminderActions';
|
|
76
76
|
export { useQuietHoursActions } from './infrastructure/hooks/useQuietHoursActions';
|
|
77
|
+
export { useNotificationSettingsUI } from './presentation/hooks/useNotificationSettingsUI';
|
|
77
78
|
|
|
78
79
|
// ============================================================================
|
|
79
80
|
// SCREENS
|
|
@@ -115,3 +116,6 @@ export type { FormButtonProps } from './presentation/components/FormButton';
|
|
|
115
116
|
|
|
116
117
|
export { QuietHoursCard } from './presentation/components/QuietHoursCard';
|
|
117
118
|
export type { QuietHoursCardProps } from './presentation/components/QuietHoursCard';
|
|
119
|
+
|
|
120
|
+
export { SettingRow } from './presentation/components/SettingRow';
|
|
121
|
+
export type { SettingRowProps } from './presentation/components/SettingRow';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setting Row Component
|
|
3
|
+
* Reusable toggle row for settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { View, StyleSheet, Switch } from 'react-native';
|
|
8
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
10
|
+
|
|
11
|
+
export interface SettingRowProps {
|
|
12
|
+
iconName: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
value: boolean;
|
|
16
|
+
onToggle: (value: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SettingRow: React.FC<SettingRowProps> = ({
|
|
20
|
+
iconName,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
value,
|
|
24
|
+
onToggle
|
|
25
|
+
}) => {
|
|
26
|
+
const tokens = useAppDesignTokens();
|
|
27
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<View style={styles.iconContainer}>
|
|
32
|
+
<AtomicIcon name={iconName} size="md" color="primary" />
|
|
33
|
+
</View>
|
|
34
|
+
<View style={styles.textContainer}>
|
|
35
|
+
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
36
|
+
{title}
|
|
37
|
+
</AtomicText>
|
|
38
|
+
<AtomicText type="bodySmall" style={styles.description}>
|
|
39
|
+
{description}
|
|
40
|
+
</AtomicText>
|
|
41
|
+
</View>
|
|
42
|
+
<Switch
|
|
43
|
+
value={value}
|
|
44
|
+
onValueChange={onToggle}
|
|
45
|
+
trackColor={{
|
|
46
|
+
false: tokens.colors.surfaceSecondary,
|
|
47
|
+
true: tokens.colors.primary
|
|
48
|
+
}}
|
|
49
|
+
thumbColor={tokens.colors.surface}
|
|
50
|
+
ios_backgroundColor={tokens.colors.surfaceSecondary}
|
|
51
|
+
/>
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
57
|
+
StyleSheet.create({
|
|
58
|
+
container: {
|
|
59
|
+
flexDirection: 'row',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
},
|
|
62
|
+
iconContainer: {
|
|
63
|
+
width: 44,
|
|
64
|
+
height: 44,
|
|
65
|
+
borderRadius: 22,
|
|
66
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
67
|
+
justifyContent: 'center',
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
marginRight: 12,
|
|
70
|
+
},
|
|
71
|
+
textContainer: {
|
|
72
|
+
flex: 1,
|
|
73
|
+
marginRight: 12,
|
|
74
|
+
},
|
|
75
|
+
description: {
|
|
76
|
+
color: tokens.colors.textSecondary,
|
|
77
|
+
marginTop: 2,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useNotificationSettingsUI Hook
|
|
3
|
+
* Handles all business logic for notification settings screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useCallback } from 'react';
|
|
7
|
+
import { useRemindersStore, useNotificationPreferences, useQuietHours } from '../../infrastructure/storage/RemindersStore';
|
|
8
|
+
import { notificationService } from '../../infrastructure/services/NotificationService';
|
|
9
|
+
|
|
10
|
+
export const useNotificationSettingsUI = () => {
|
|
11
|
+
const preferences = useNotificationPreferences();
|
|
12
|
+
const quietHours = useQuietHours();
|
|
13
|
+
const { initialize, updatePreferences, updateQuietHours, isLoading } = useRemindersStore();
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
initialize();
|
|
17
|
+
}, [initialize]);
|
|
18
|
+
|
|
19
|
+
const handleMasterToggle = useCallback(async (value: boolean) => {
|
|
20
|
+
if (value) {
|
|
21
|
+
const hasPermission = await notificationService.hasPermissions();
|
|
22
|
+
if (!hasPermission) {
|
|
23
|
+
const granted = await notificationService.requestPermissions();
|
|
24
|
+
if (!granted) return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
await updatePreferences({ enabled: value });
|
|
28
|
+
}, [updatePreferences]);
|
|
29
|
+
|
|
30
|
+
const handleSoundToggle = useCallback(async (value: boolean) => {
|
|
31
|
+
await updatePreferences({ sound: value });
|
|
32
|
+
}, [updatePreferences]);
|
|
33
|
+
|
|
34
|
+
const handleVibrationToggle = useCallback(async (value: boolean) => {
|
|
35
|
+
await updatePreferences({ vibration: value });
|
|
36
|
+
}, [updatePreferences]);
|
|
37
|
+
|
|
38
|
+
const handleQuietHoursToggle = useCallback(async (value: boolean) => {
|
|
39
|
+
await updateQuietHours({ ...quietHours, enabled: value });
|
|
40
|
+
}, [quietHours, updateQuietHours]);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
preferences,
|
|
44
|
+
quietHours,
|
|
45
|
+
isLoading,
|
|
46
|
+
handleMasterToggle,
|
|
47
|
+
handleSoundToggle,
|
|
48
|
+
handleVibrationToggle,
|
|
49
|
+
handleQuietHoursToggle,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NotificationSettingsScreen
|
|
3
|
-
*
|
|
3
|
+
* Clean presentation-only screen for notification settings
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, {
|
|
7
|
-
import { View, StyleSheet, ActivityIndicator,
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { View, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
|
|
8
8
|
import { AtomicText, AtomicIcon, AtomicCard, ScreenLayout } from '@umituz/react-native-design-system';
|
|
9
|
-
import { Switch } from 'react-native';
|
|
10
9
|
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
11
10
|
import { QuietHoursCard } from '../components/QuietHoursCard';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { SettingRow } from '../components/SettingRow';
|
|
12
|
+
import { useNotificationSettingsUI } from '../hooks/useNotificationSettingsUI';
|
|
13
|
+
import { useReminders } from '../../infrastructure/storage/RemindersStore';
|
|
14
14
|
import type { NotificationSettingsTranslations, QuietHoursTranslations } from '../../infrastructure/services/types';
|
|
15
15
|
|
|
16
16
|
export interface NotificationSettingsScreenProps {
|
|
@@ -30,38 +30,17 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
30
30
|
}) => {
|
|
31
31
|
const tokens = useAppDesignTokens();
|
|
32
32
|
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
33
|
-
|
|
34
|
-
const preferences = useNotificationPreferences();
|
|
35
|
-
const quietHours = useQuietHours();
|
|
36
33
|
const reminders = useReminders();
|
|
37
|
-
const { initialize, updatePreferences, updateQuietHours, isLoading } = useRemindersStore();
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
initialize();
|
|
41
|
-
}, [initialize]);
|
|
42
|
-
|
|
43
|
-
const handleMasterToggle = useCallback(async (value: boolean) => {
|
|
44
|
-
if (value) {
|
|
45
|
-
const hasPermission = await notificationService.hasPermissions();
|
|
46
|
-
if (!hasPermission) {
|
|
47
|
-
const granted = await notificationService.requestPermissions();
|
|
48
|
-
if (!granted) return;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
await updatePreferences({ enabled: value });
|
|
52
|
-
}, [updatePreferences]);
|
|
53
|
-
|
|
54
|
-
const handleSoundToggle = useCallback(async (value: boolean) => {
|
|
55
|
-
await updatePreferences({ sound: value });
|
|
56
|
-
}, [updatePreferences]);
|
|
57
|
-
|
|
58
|
-
const handleVibrationToggle = useCallback(async (value: boolean) => {
|
|
59
|
-
await updatePreferences({ vibration: value });
|
|
60
|
-
}, [updatePreferences]);
|
|
61
34
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
35
|
+
const {
|
|
36
|
+
preferences,
|
|
37
|
+
quietHours,
|
|
38
|
+
isLoading,
|
|
39
|
+
handleMasterToggle,
|
|
40
|
+
handleSoundToggle,
|
|
41
|
+
handleVibrationToggle,
|
|
42
|
+
handleQuietHoursToggle,
|
|
43
|
+
} = useNotificationSettingsUI();
|
|
65
44
|
|
|
66
45
|
if (isLoading) {
|
|
67
46
|
return (
|
|
@@ -75,7 +54,7 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
75
54
|
|
|
76
55
|
return (
|
|
77
56
|
<ScreenLayout hideScrollIndicator>
|
|
78
|
-
<
|
|
57
|
+
<View style={styles.container}>
|
|
79
58
|
<AtomicCard style={styles.card}>
|
|
80
59
|
<SettingRow
|
|
81
60
|
iconName="notifications"
|
|
@@ -83,7 +62,6 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
83
62
|
description={translations.masterToggleDescription}
|
|
84
63
|
value={preferences.enabled}
|
|
85
64
|
onToggle={handleMasterToggle}
|
|
86
|
-
tokens={tokens}
|
|
87
65
|
/>
|
|
88
66
|
</AtomicCard>
|
|
89
67
|
|
|
@@ -96,7 +74,6 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
96
74
|
description={translations.soundDescription}
|
|
97
75
|
value={preferences.sound}
|
|
98
76
|
onToggle={handleSoundToggle}
|
|
99
|
-
tokens={tokens}
|
|
100
77
|
/>
|
|
101
78
|
<View style={styles.divider} />
|
|
102
79
|
<SettingRow
|
|
@@ -105,7 +82,6 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
105
82
|
description={translations.vibrationDescription}
|
|
106
83
|
value={preferences.vibration}
|
|
107
84
|
onToggle={handleVibrationToggle}
|
|
108
|
-
tokens={tokens}
|
|
109
85
|
/>
|
|
110
86
|
</AtomicCard>
|
|
111
87
|
|
|
@@ -115,12 +91,18 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
115
91
|
<AtomicIcon name="time" size="md" color="primary" />
|
|
116
92
|
</View>
|
|
117
93
|
<View style={styles.textContainer}>
|
|
118
|
-
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
119
|
-
|
|
94
|
+
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
95
|
+
{translations.remindersTitle}
|
|
96
|
+
</AtomicText>
|
|
97
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
|
|
98
|
+
{translations.remindersDescription}
|
|
99
|
+
</AtomicText>
|
|
120
100
|
</View>
|
|
121
101
|
{reminders.length > 0 && (
|
|
122
102
|
<View style={styles.badge}>
|
|
123
|
-
<AtomicText type="bodySmall" style={styles.badgeText}>
|
|
103
|
+
<AtomicText type="bodySmall" style={styles.badgeText}>
|
|
104
|
+
{reminders.length}
|
|
105
|
+
</AtomicText>
|
|
124
106
|
</View>
|
|
125
107
|
)}
|
|
126
108
|
<AtomicIcon name="chevron-forward" size="md" color="secondary" />
|
|
@@ -136,49 +118,36 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
|
|
|
136
118
|
/>
|
|
137
119
|
</>
|
|
138
120
|
)}
|
|
139
|
-
</ScrollView>
|
|
140
|
-
</ScreenLayout>
|
|
141
|
-
);
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
interface SettingRowProps {
|
|
145
|
-
iconName: string;
|
|
146
|
-
title: string;
|
|
147
|
-
description: string;
|
|
148
|
-
value: boolean;
|
|
149
|
-
onToggle: (value: boolean) => void;
|
|
150
|
-
tokens: ReturnType<typeof useAppDesignTokens>;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const SettingRow: React.FC<SettingRowProps> = ({ iconName, title, description, value, onToggle, tokens }) => {
|
|
154
|
-
const styles = useMemo(() => createRowStyles(tokens), [tokens]);
|
|
155
|
-
return (
|
|
156
|
-
<View style={styles.container}>
|
|
157
|
-
<View style={styles.iconContainer}>
|
|
158
|
-
<AtomicIcon name={iconName} size="md" color="primary" />
|
|
159
121
|
</View>
|
|
160
|
-
|
|
161
|
-
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>{title}</AtomicText>
|
|
162
|
-
<AtomicText type="bodySmall" style={styles.description}>{description}</AtomicText>
|
|
163
|
-
</View>
|
|
164
|
-
<Switch
|
|
165
|
-
value={value}
|
|
166
|
-
onValueChange={onToggle}
|
|
167
|
-
trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
|
|
168
|
-
thumbColor={tokens.colors.surface}
|
|
169
|
-
ios_backgroundColor={tokens.colors.surfaceSecondary}
|
|
170
|
-
/>
|
|
171
|
-
</View>
|
|
122
|
+
</ScreenLayout>
|
|
172
123
|
);
|
|
173
124
|
};
|
|
174
125
|
|
|
175
126
|
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
176
127
|
StyleSheet.create({
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
128
|
+
container: {
|
|
129
|
+
flex: 1,
|
|
130
|
+
padding: 16,
|
|
131
|
+
},
|
|
132
|
+
loadingContainer: {
|
|
133
|
+
flex: 1,
|
|
134
|
+
justifyContent: 'center',
|
|
135
|
+
alignItems: 'center',
|
|
136
|
+
},
|
|
137
|
+
card: {
|
|
138
|
+
marginBottom: 16,
|
|
139
|
+
padding: 16,
|
|
140
|
+
backgroundColor: tokens.colors.surface,
|
|
141
|
+
},
|
|
142
|
+
divider: {
|
|
143
|
+
height: 1,
|
|
144
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
145
|
+
marginVertical: 12,
|
|
146
|
+
},
|
|
147
|
+
navRow: {
|
|
148
|
+
flexDirection: 'row',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
},
|
|
182
151
|
iconContainer: {
|
|
183
152
|
width: 44,
|
|
184
153
|
height: 44,
|
|
@@ -188,8 +157,10 @@ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
|
188
157
|
alignItems: 'center',
|
|
189
158
|
marginRight: 12,
|
|
190
159
|
},
|
|
191
|
-
textContainer: {
|
|
192
|
-
|
|
160
|
+
textContainer: {
|
|
161
|
+
flex: 1,
|
|
162
|
+
marginRight: 12,
|
|
163
|
+
},
|
|
193
164
|
badge: {
|
|
194
165
|
backgroundColor: tokens.colors.primary,
|
|
195
166
|
paddingHorizontal: 8,
|
|
@@ -197,21 +168,8 @@ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
|
197
168
|
borderRadius: 10,
|
|
198
169
|
marginRight: 8,
|
|
199
170
|
},
|
|
200
|
-
badgeText: {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const createRowStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
204
|
-
StyleSheet.create({
|
|
205
|
-
container: { flexDirection: 'row', alignItems: 'center' },
|
|
206
|
-
iconContainer: {
|
|
207
|
-
width: 44,
|
|
208
|
-
height: 44,
|
|
209
|
-
borderRadius: 22,
|
|
210
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
211
|
-
justifyContent: 'center',
|
|
212
|
-
alignItems: 'center',
|
|
213
|
-
marginRight: 12,
|
|
171
|
+
badgeText: {
|
|
172
|
+
color: tokens.colors.surface,
|
|
173
|
+
fontWeight: '600',
|
|
214
174
|
},
|
|
215
|
-
textContainer: { flex: 1, marginRight: 12 },
|
|
216
|
-
description: { color: tokens.colors.textSecondary, marginTop: 2 },
|
|
217
175
|
});
|