@umituz/react-native-settings 4.21.11 → 4.21.13
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 +11 -2
- package/src/domains/gamification/components/GamificationScreenWrapper.tsx +58 -85
- package/src/domains/gamification/components/index.ts +1 -0
- package/src/domains/gamification/index.ts +4 -11
- package/src/domains/gamification/types/index.ts +18 -0
- package/src/domains/notifications/index.ts +139 -0
- package/src/domains/notifications/infrastructure/config/notificationsConfig.ts +98 -0
- package/src/domains/notifications/infrastructure/hooks/useNotificationSettings.ts +37 -0
- package/src/domains/notifications/infrastructure/services/NotificationBadgeManager.ts +28 -0
- package/src/domains/notifications/infrastructure/services/NotificationManager.ts +138 -0
- package/src/domains/notifications/infrastructure/services/NotificationPermissions.ts +80 -0
- package/src/domains/notifications/infrastructure/services/NotificationScheduler.ts +77 -0
- package/src/domains/notifications/infrastructure/services/NotificationService.ts +50 -0
- package/src/domains/notifications/infrastructure/services/types.ts +176 -0
- package/src/domains/notifications/infrastructure/storage/NotificationsStore.ts +45 -0
- package/src/domains/notifications/infrastructure/utils/dev.ts +25 -0
- package/src/domains/notifications/infrastructure/utils/idGenerator.ts +14 -0
- package/src/domains/notifications/infrastructure/utils/triggerBuilder.ts +45 -0
- package/src/domains/notifications/presentation/components/NotificationsSection.tsx +84 -0
- package/src/domains/notifications/presentation/components/RemindersNavRow.styles.ts +38 -0
- package/src/domains/notifications/presentation/components/RemindersNavRow.tsx +51 -0
- package/src/domains/notifications/presentation/components/SettingRow.tsx +86 -0
- package/src/domains/notifications/presentation/hooks/useNotificationSettingsUI.ts +52 -0
- package/src/domains/notifications/presentation/hooks/useTimePicker.ts +71 -0
- package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
- package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.tsx +131 -0
- package/src/domains/notifications/presentation/screens/NotificationsScreen.tsx +107 -0
- package/src/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.ts +52 -0
- package/src/domains/notifications/quietHours/presentation/components/QuietHoursCard.tsx +112 -0
- package/src/domains/notifications/reminders/infrastructure/config/reminderPresets.ts +120 -0
- package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +106 -0
- package/src/domains/notifications/reminders/infrastructure/storage/RemindersStore.ts +148 -0
- package/src/domains/notifications/reminders/presentation/components/FormButton.tsx +66 -0
- package/src/domains/notifications/reminders/presentation/components/FrequencySelector.tsx +72 -0
- package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +169 -0
- package/src/domains/notifications/reminders/presentation/components/ReminderItem.tsx +130 -0
- package/src/domains/notifications/reminders/presentation/components/TimePresetSelector.tsx +100 -0
- package/src/domains/notifications/reminders/presentation/components/WeekdaySelector.tsx +61 -0
- package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +131 -0
- package/src/index.ts +3 -0
- package/src/presentation/navigation/SettingsStackNavigator.tsx +21 -11
- package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +1 -1
- package/src/domains/gamification/README.md +0 -343
- package/src/domains/gamification/components/GamificationSettingsItem.tsx +0 -33
- package/src/domains/gamification/examples/gamification.config.example.ts +0 -70
- package/src/domains/gamification/examples/localization.example.json +0 -71
- package/src/domains/gamification/types/settings.ts +0 -28
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationsSection Component
|
|
3
|
+
* Settings section for notifications - navigates to full screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useCallback, useMemo } from 'react';
|
|
7
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
8
|
+
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
9
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
10
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
11
|
+
// @ts-ignore - Optional peer dependency
|
|
12
|
+
import { useNavigation } from '@react-navigation/native';
|
|
13
|
+
// @ts-ignore - Optional peer dependency
|
|
14
|
+
import { useLocalization } from '@umituz/react-native-localization';
|
|
15
|
+
|
|
16
|
+
export interface NotificationsSectionConfig {
|
|
17
|
+
route?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
sectionTitle?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NotificationsSectionProps {
|
|
24
|
+
config?: NotificationsSectionConfig;
|
|
25
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const NotificationsSection: React.FC<NotificationsSectionProps> = ({
|
|
29
|
+
config,
|
|
30
|
+
containerStyle,
|
|
31
|
+
}) => {
|
|
32
|
+
const navigation = useNavigation();
|
|
33
|
+
const { t } = useLocalization();
|
|
34
|
+
const tokens = useAppDesignTokens();
|
|
35
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
36
|
+
|
|
37
|
+
const handlePress = useCallback(() => {
|
|
38
|
+
const route = config?.route || 'Notifications';
|
|
39
|
+
navigation.navigate(route as never);
|
|
40
|
+
}, [config?.route, navigation]);
|
|
41
|
+
|
|
42
|
+
const title = config?.title || t('settings.notifications.title');
|
|
43
|
+
const description = config?.description || t('settings.notifications.description');
|
|
44
|
+
const sectionTitle = config?.sectionTitle || t('settings.notifications.sectionTitle');
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<View style={[styles.container, containerStyle]}>
|
|
48
|
+
<AtomicText type="bodyLarge" style={styles.sectionTitle}>{sectionTitle}</AtomicText>
|
|
49
|
+
|
|
50
|
+
<TouchableOpacity
|
|
51
|
+
style={styles.itemContainer}
|
|
52
|
+
onPress={handlePress}
|
|
53
|
+
activeOpacity={0.7}
|
|
54
|
+
>
|
|
55
|
+
<View style={styles.iconContainer}>
|
|
56
|
+
<AtomicIcon name="notifications" size="md" color="primary" />
|
|
57
|
+
</View>
|
|
58
|
+
<View style={styles.textContainer}>
|
|
59
|
+
<AtomicText type="bodyLarge">{title}</AtomicText>
|
|
60
|
+
<AtomicText type="bodySmall" style={styles.description}>{description}</AtomicText>
|
|
61
|
+
</View>
|
|
62
|
+
<AtomicIcon name="chevron-forward" size="md" color="secondary" />
|
|
63
|
+
</TouchableOpacity>
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
69
|
+
StyleSheet.create({
|
|
70
|
+
container: { marginBottom: 16, borderRadius: 12, overflow: 'hidden', backgroundColor: tokens.colors.surface },
|
|
71
|
+
sectionTitle: { fontWeight: '600', paddingHorizontal: 16, paddingTop: 16, paddingBottom: 8 },
|
|
72
|
+
itemContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, minHeight: 72 },
|
|
73
|
+
iconContainer: {
|
|
74
|
+
width: 48,
|
|
75
|
+
height: 48,
|
|
76
|
+
borderRadius: 12,
|
|
77
|
+
justifyContent: 'center',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
marginRight: 16,
|
|
80
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
81
|
+
},
|
|
82
|
+
textContainer: { flex: 1, marginRight: 8 },
|
|
83
|
+
description: { color: tokens.colors.textSecondary, marginTop: 4 },
|
|
84
|
+
});
|
|
@@ -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,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setting Row Component
|
|
3
|
+
* Reusable toggle row for settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo, useCallback } 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';
|
|
10
|
+
|
|
11
|
+
export interface SettingRowProps {
|
|
12
|
+
iconName: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
value: boolean;
|
|
16
|
+
onToggle: (value: boolean) => void;
|
|
17
|
+
onHapticFeedback?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const SettingRow: React.FC<SettingRowProps> = ({
|
|
21
|
+
iconName,
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
value,
|
|
25
|
+
onToggle,
|
|
26
|
+
onHapticFeedback,
|
|
27
|
+
}) => {
|
|
28
|
+
const tokens = useAppDesignTokens();
|
|
29
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
30
|
+
|
|
31
|
+
const handleToggle = useCallback((newValue: boolean) => {
|
|
32
|
+
onHapticFeedback?.();
|
|
33
|
+
onToggle(newValue);
|
|
34
|
+
}, [onToggle, onHapticFeedback]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View style={styles.container}>
|
|
38
|
+
<View style={styles.iconContainer}>
|
|
39
|
+
<AtomicIcon name={iconName} size="md" color="primary" />
|
|
40
|
+
</View>
|
|
41
|
+
<View style={styles.textContainer}>
|
|
42
|
+
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
43
|
+
{title}
|
|
44
|
+
</AtomicText>
|
|
45
|
+
<AtomicText type="bodySmall" style={styles.description}>
|
|
46
|
+
{description}
|
|
47
|
+
</AtomicText>
|
|
48
|
+
</View>
|
|
49
|
+
<Switch
|
|
50
|
+
value={value}
|
|
51
|
+
onValueChange={handleToggle}
|
|
52
|
+
trackColor={{
|
|
53
|
+
false: tokens.colors.surfaceSecondary,
|
|
54
|
+
true: tokens.colors.primary
|
|
55
|
+
}}
|
|
56
|
+
thumbColor={tokens.colors.surface}
|
|
57
|
+
ios_backgroundColor={tokens.colors.surfaceSecondary}
|
|
58
|
+
/>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
64
|
+
StyleSheet.create({
|
|
65
|
+
container: {
|
|
66
|
+
flexDirection: 'row',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
},
|
|
69
|
+
iconContainer: {
|
|
70
|
+
width: 44,
|
|
71
|
+
height: 44,
|
|
72
|
+
borderRadius: 22,
|
|
73
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
74
|
+
justifyContent: 'center',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
marginRight: 12,
|
|
77
|
+
},
|
|
78
|
+
textContainer: {
|
|
79
|
+
flex: 1,
|
|
80
|
+
marginRight: 12,
|
|
81
|
+
},
|
|
82
|
+
description: {
|
|
83
|
+
color: tokens.colors.textSecondary,
|
|
84
|
+
marginTop: 2,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useNotificationSettingsUI Hook
|
|
3
|
+
* Handles all business logic for notification settings screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useCallback } from 'react';
|
|
7
|
+
import { useNotificationPreferences, useQuietHours, usePreferencesStore, useRemindersLoading } from '../../reminders/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 } = usePreferencesStore();
|
|
14
|
+
const isLoading = useRemindersLoading();
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
initialize();
|
|
18
|
+
}, [initialize]);
|
|
19
|
+
|
|
20
|
+
const handleMasterToggle = useCallback(async (value: boolean) => {
|
|
21
|
+
if (value) {
|
|
22
|
+
const hasPermission = await notificationService.hasPermissions();
|
|
23
|
+
if (!hasPermission) {
|
|
24
|
+
const granted = await notificationService.requestPermissions();
|
|
25
|
+
if (!granted) return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
await updatePreferences({ enabled: value });
|
|
29
|
+
}, [updatePreferences]);
|
|
30
|
+
|
|
31
|
+
const handleSoundToggle = useCallback(async (value: boolean) => {
|
|
32
|
+
await updatePreferences({ sound: value });
|
|
33
|
+
}, [updatePreferences]);
|
|
34
|
+
|
|
35
|
+
const handleVibrationToggle = useCallback(async (value: boolean) => {
|
|
36
|
+
await updatePreferences({ vibration: value });
|
|
37
|
+
}, [updatePreferences]);
|
|
38
|
+
|
|
39
|
+
const handleQuietHoursToggle = useCallback(async (value: boolean) => {
|
|
40
|
+
await updateQuietHours({ ...quietHours, enabled: value });
|
|
41
|
+
}, [quietHours, updateQuietHours]);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
preferences,
|
|
45
|
+
quietHours,
|
|
46
|
+
isLoading,
|
|
47
|
+
handleMasterToggle,
|
|
48
|
+
handleSoundToggle,
|
|
49
|
+
handleVibrationToggle,
|
|
50
|
+
handleQuietHoursToggle,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationSettingsScreen
|
|
3
|
+
* Clean presentation-only screen for notification settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View } from 'react-native';
|
|
8
|
+
import { AtomicCard, ScreenLayout, AtomicSpinner } from '@umituz/react-native-design-system';
|
|
9
|
+
import { QuietHoursCard } from '../../quietHours/presentation/components/QuietHoursCard';
|
|
10
|
+
import { SettingRow } from '../components/SettingRow';
|
|
11
|
+
import { RemindersNavRow } from '../components/RemindersNavRow';
|
|
12
|
+
import { useNotificationSettingsUI } from '../hooks/useNotificationSettingsUI';
|
|
13
|
+
import { useTimePicker } from '../hooks/useTimePicker';
|
|
14
|
+
import { useReminders } from '../../reminders/infrastructure/storage/RemindersStore';
|
|
15
|
+
import { useQuietHoursActions } from '../../quietHours/infrastructure/hooks/useQuietHoursActions';
|
|
16
|
+
import type { NotificationSettingsTranslations, QuietHoursTranslations } from '../../infrastructure/services/types';
|
|
17
|
+
import { createStyles } from './NotificationSettingsScreen.styles';
|
|
18
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
19
|
+
// @ts-ignore - Optional peer dependency
|
|
20
|
+
import DateTimePicker from '@react-native-community/datetimepicker';
|
|
21
|
+
|
|
22
|
+
export interface NotificationSettingsScreenProps {
|
|
23
|
+
translations: NotificationSettingsTranslations;
|
|
24
|
+
quietHoursTranslations: QuietHoursTranslations;
|
|
25
|
+
onHapticFeedback?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProps> = ({
|
|
29
|
+
translations,
|
|
30
|
+
quietHoursTranslations,
|
|
31
|
+
onHapticFeedback,
|
|
32
|
+
}) => {
|
|
33
|
+
const tokens = useAppDesignTokens();
|
|
34
|
+
const styles = createStyles(tokens);
|
|
35
|
+
const reminders = useReminders();
|
|
36
|
+
const { setStartTime, setEndTime } = useQuietHoursActions();
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
preferences,
|
|
40
|
+
quietHours,
|
|
41
|
+
isLoading,
|
|
42
|
+
handleMasterToggle,
|
|
43
|
+
handleSoundToggle,
|
|
44
|
+
handleVibrationToggle,
|
|
45
|
+
handleQuietHoursToggle,
|
|
46
|
+
} = useNotificationSettingsUI();
|
|
47
|
+
|
|
48
|
+
const timePicker = useTimePicker({
|
|
49
|
+
quietHours,
|
|
50
|
+
onStartTimeChange: setStartTime,
|
|
51
|
+
onEndTimeChange: setEndTime,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const handleRemindersPress = () => {
|
|
55
|
+
// Navigate to reminders screen when implemented
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (isLoading) {
|
|
59
|
+
return (
|
|
60
|
+
<ScreenLayout>
|
|
61
|
+
<AtomicSpinner size="lg" color="primary" fullContainer />
|
|
62
|
+
</ScreenLayout>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ScreenLayout hideScrollIndicator>
|
|
68
|
+
<View style={styles.container}>
|
|
69
|
+
<AtomicCard style={styles.card}>
|
|
70
|
+
<SettingRow
|
|
71
|
+
iconName="notifications"
|
|
72
|
+
title={translations.masterToggleTitle}
|
|
73
|
+
description={translations.masterToggleDescription}
|
|
74
|
+
value={preferences.enabled}
|
|
75
|
+
onToggle={handleMasterToggle}
|
|
76
|
+
onHapticFeedback={onHapticFeedback}
|
|
77
|
+
/>
|
|
78
|
+
</AtomicCard>
|
|
79
|
+
|
|
80
|
+
{preferences.enabled && (
|
|
81
|
+
<>
|
|
82
|
+
<AtomicCard style={styles.card}>
|
|
83
|
+
<SettingRow
|
|
84
|
+
iconName="volume-high"
|
|
85
|
+
title={translations.soundTitle}
|
|
86
|
+
description={translations.soundDescription}
|
|
87
|
+
value={preferences.sound}
|
|
88
|
+
onToggle={handleSoundToggle}
|
|
89
|
+
onHapticFeedback={onHapticFeedback}
|
|
90
|
+
/>
|
|
91
|
+
<View style={styles.divider} />
|
|
92
|
+
<SettingRow
|
|
93
|
+
iconName="phone-portrait"
|
|
94
|
+
title={translations.vibrationTitle}
|
|
95
|
+
description={translations.vibrationDescription}
|
|
96
|
+
value={preferences.vibration}
|
|
97
|
+
onToggle={handleVibrationToggle}
|
|
98
|
+
onHapticFeedback={onHapticFeedback}
|
|
99
|
+
/>
|
|
100
|
+
</AtomicCard>
|
|
101
|
+
|
|
102
|
+
<AtomicCard style={styles.card}>
|
|
103
|
+
<RemindersNavRow
|
|
104
|
+
title={translations.remindersTitle}
|
|
105
|
+
description={translations.remindersDescription}
|
|
106
|
+
count={reminders.length}
|
|
107
|
+
onPress={handleRemindersPress}
|
|
108
|
+
/>
|
|
109
|
+
</AtomicCard>
|
|
110
|
+
|
|
111
|
+
<QuietHoursCard
|
|
112
|
+
config={quietHours}
|
|
113
|
+
translations={quietHoursTranslations}
|
|
114
|
+
onToggle={handleQuietHoursToggle}
|
|
115
|
+
onStartTimePress={timePicker.handleStartTimePress}
|
|
116
|
+
onEndTimePress={timePicker.handleEndTimePress}
|
|
117
|
+
/>
|
|
118
|
+
</>
|
|
119
|
+
)}
|
|
120
|
+
</View>
|
|
121
|
+
{timePicker.pickerMode && (
|
|
122
|
+
<DateTimePicker
|
|
123
|
+
value={timePicker.getPickerDate()}
|
|
124
|
+
mode="time"
|
|
125
|
+
is24Hour={true}
|
|
126
|
+
onChange={timePicker.handleTimeChange}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
</ScreenLayout>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications Screen - Dynamic and Reusable
|
|
3
|
+
*
|
|
4
|
+
* A clean notification settings screen that accepts all text and configuration
|
|
5
|
+
* as props to make it completely reusable across different applications.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useMemo } from 'react';
|
|
9
|
+
import { View, StyleSheet } from 'react-native';
|
|
10
|
+
import { AtomicIcon, AtomicCard, AtomicText, ScreenLayout, STATIC_TOKENS, AtomicSpinner } from '@umituz/react-native-design-system';
|
|
11
|
+
import { Switch } from 'react-native';
|
|
12
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
13
|
+
import { useNotificationSettings } from '../../infrastructure/hooks/useNotificationSettings';
|
|
14
|
+
import type { DesignTokens } from '@umituz/react-native-design-system';
|
|
15
|
+
|
|
16
|
+
export interface NotificationsScreenProps {
|
|
17
|
+
translations: {
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
loadingText?: string;
|
|
21
|
+
};
|
|
22
|
+
iconName?: string;
|
|
23
|
+
iconColor?: string;
|
|
24
|
+
testID?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const NotificationsScreen: React.FC<NotificationsScreenProps> = ({
|
|
28
|
+
translations,
|
|
29
|
+
iconName = 'notifications',
|
|
30
|
+
iconColor = 'primary',
|
|
31
|
+
testID = 'notifications-screen',
|
|
32
|
+
}) => {
|
|
33
|
+
const tokens = useAppDesignTokens();
|
|
34
|
+
const styles = useMemo(() => getStyles(tokens), [tokens]);
|
|
35
|
+
const { notificationsEnabled, setNotificationsEnabled, isLoading } = useNotificationSettings();
|
|
36
|
+
|
|
37
|
+
if (isLoading) {
|
|
38
|
+
return (
|
|
39
|
+
<ScreenLayout testID={testID}>
|
|
40
|
+
<AtomicSpinner
|
|
41
|
+
size="lg"
|
|
42
|
+
color="primary"
|
|
43
|
+
text={translations.loadingText || 'Loading...'}
|
|
44
|
+
fullContainer
|
|
45
|
+
/>
|
|
46
|
+
</ScreenLayout>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ScreenLayout testID={testID} hideScrollIndicator>
|
|
52
|
+
<AtomicCard style={styles.card}>
|
|
53
|
+
<View style={styles.settingItem}>
|
|
54
|
+
<View style={styles.iconContainer}>
|
|
55
|
+
<AtomicIcon name={iconName} size="lg" color={iconColor as any} />
|
|
56
|
+
</View>
|
|
57
|
+
<View style={styles.textContainer}>
|
|
58
|
+
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
59
|
+
{translations.title}
|
|
60
|
+
</AtomicText>
|
|
61
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: STATIC_TOKENS.spacing.xs }}>
|
|
62
|
+
{translations.description}
|
|
63
|
+
</AtomicText>
|
|
64
|
+
</View>
|
|
65
|
+
<Switch
|
|
66
|
+
value={notificationsEnabled}
|
|
67
|
+
onValueChange={setNotificationsEnabled}
|
|
68
|
+
trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
|
|
69
|
+
thumbColor={tokens.colors.surface}
|
|
70
|
+
testID="notifications-toggle"
|
|
71
|
+
/>
|
|
72
|
+
</View>
|
|
73
|
+
</AtomicCard>
|
|
74
|
+
</ScreenLayout>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
79
|
+
loadingContainer: {
|
|
80
|
+
flex: 1,
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
},
|
|
84
|
+
card: {
|
|
85
|
+
padding: STATIC_TOKENS.spacing.lg,
|
|
86
|
+
backgroundColor: tokens.colors.surface,
|
|
87
|
+
},
|
|
88
|
+
settingItem: {
|
|
89
|
+
flexDirection: 'row',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
},
|
|
92
|
+
iconContainer: {
|
|
93
|
+
width: 48,
|
|
94
|
+
height: 48,
|
|
95
|
+
borderRadius: 24,
|
|
96
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
marginRight: STATIC_TOKENS.spacing.md,
|
|
100
|
+
},
|
|
101
|
+
textContainer: {
|
|
102
|
+
flex: 1,
|
|
103
|
+
marginRight: STATIC_TOKENS.spacing.md,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export default NotificationsScreen;
|