@umituz/react-native-settings 4.21.10 → 4.21.12
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 +42 -87
- package/src/domains/gamification/components/index.ts +1 -0
- package/src/domains/gamification/index.ts +3 -11
- package/src/index.ts +3 -0
- package/src/notifications/domains/quietHours/infrastructure/hooks/useQuietHoursActions.ts +52 -0
- package/src/notifications/domains/quietHours/presentation/components/QuietHoursCard.tsx +112 -0
- package/src/notifications/domains/reminders/infrastructure/config/reminderPresets.ts +120 -0
- package/src/notifications/domains/reminders/infrastructure/hooks/useReminderActions.ts +106 -0
- package/src/notifications/domains/reminders/infrastructure/storage/RemindersStore.ts +148 -0
- package/src/notifications/domains/reminders/presentation/components/FormButton.tsx +66 -0
- package/src/notifications/domains/reminders/presentation/components/FrequencySelector.tsx +72 -0
- package/src/notifications/domains/reminders/presentation/components/ReminderForm.tsx +169 -0
- package/src/notifications/domains/reminders/presentation/components/ReminderItem.tsx +130 -0
- package/src/notifications/domains/reminders/presentation/components/TimePresetSelector.tsx +100 -0
- package/src/notifications/domains/reminders/presentation/components/WeekdaySelector.tsx +61 -0
- package/src/notifications/domains/reminders/presentation/screens/ReminderListScreen.tsx +131 -0
- package/src/notifications/index.ts +139 -0
- package/src/notifications/infrastructure/config/notificationsConfig.ts +98 -0
- package/src/notifications/infrastructure/hooks/useNotificationSettings.ts +37 -0
- package/src/notifications/infrastructure/services/NotificationBadgeManager.ts +28 -0
- package/src/notifications/infrastructure/services/NotificationManager.ts +138 -0
- package/src/notifications/infrastructure/services/NotificationPermissions.ts +80 -0
- package/src/notifications/infrastructure/services/NotificationScheduler.ts +77 -0
- package/src/notifications/infrastructure/services/NotificationService.ts +50 -0
- package/src/notifications/infrastructure/services/types.ts +176 -0
- package/src/notifications/infrastructure/storage/NotificationsStore.ts +45 -0
- package/src/notifications/infrastructure/utils/dev.ts +25 -0
- package/src/notifications/infrastructure/utils/idGenerator.ts +14 -0
- package/src/notifications/infrastructure/utils/triggerBuilder.ts +45 -0
- package/src/notifications/presentation/components/NotificationsSection.tsx +84 -0
- package/src/notifications/presentation/components/RemindersNavRow.styles.ts +38 -0
- package/src/notifications/presentation/components/RemindersNavRow.tsx +51 -0
- package/src/notifications/presentation/components/SettingRow.tsx +86 -0
- package/src/notifications/presentation/hooks/useNotificationSettingsUI.ts +52 -0
- package/src/notifications/presentation/hooks/useTimePicker.ts +71 -0
- package/src/notifications/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
- package/src/notifications/presentation/screens/NotificationSettingsScreen.tsx +131 -0
- package/src/notifications/presentation/screens/NotificationsScreen.tsx +107 -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,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeekdaySelector Component
|
|
3
|
+
* Allows selection of a weekday for weekly reminders
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
8
|
+
import { AtomicText } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
10
|
+
import { WEEKDAY_OPTIONS } from '../../infrastructure/config/reminderPresets';
|
|
11
|
+
|
|
12
|
+
export interface WeekdaySelectorProps {
|
|
13
|
+
selectedWeekday: number;
|
|
14
|
+
onSelect: (weekday: number) => void;
|
|
15
|
+
getLabel: (key: string) => string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const WeekdaySelector: React.FC<WeekdaySelectorProps> = ({
|
|
19
|
+
selectedWeekday,
|
|
20
|
+
onSelect,
|
|
21
|
+
getLabel,
|
|
22
|
+
}) => {
|
|
23
|
+
const tokens = useAppDesignTokens();
|
|
24
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View style={styles.container}>
|
|
28
|
+
{WEEKDAY_OPTIONS.map(option => {
|
|
29
|
+
const isSelected = selectedWeekday === option.id;
|
|
30
|
+
return (
|
|
31
|
+
<TouchableOpacity
|
|
32
|
+
key={option.id}
|
|
33
|
+
style={[styles.button, isSelected ? styles.selectedButton : undefined]}
|
|
34
|
+
onPress={() => onSelect(option.id)}
|
|
35
|
+
activeOpacity={0.7}
|
|
36
|
+
>
|
|
37
|
+
<AtomicText type="bodySmall" style={isSelected ? styles.selectedText : styles.text}>
|
|
38
|
+
{getLabel(option.shortLabelKey)}
|
|
39
|
+
</AtomicText>
|
|
40
|
+
</TouchableOpacity>
|
|
41
|
+
);
|
|
42
|
+
})}
|
|
43
|
+
</View>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
48
|
+
StyleSheet.create({
|
|
49
|
+
container: { flexDirection: 'row', justifyContent: 'space-between', gap: 4 },
|
|
50
|
+
button: {
|
|
51
|
+
flex: 1,
|
|
52
|
+
paddingVertical: 10,
|
|
53
|
+
paddingHorizontal: 4,
|
|
54
|
+
borderRadius: 8,
|
|
55
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
},
|
|
58
|
+
selectedButton: { backgroundColor: tokens.colors.primary },
|
|
59
|
+
text: { color: tokens.colors.textSecondary, fontWeight: '500' },
|
|
60
|
+
selectedText: { color: tokens.colors.surface, fontWeight: '500' },
|
|
61
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReminderListScreen
|
|
3
|
+
* Displays list of reminders with add, edit, delete functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useMemo, useCallback } from 'react';
|
|
7
|
+
import { View, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
|
|
8
|
+
import { AtomicText, AtomicIcon, AtomicSpinner } from '@umituz/react-native-design-system';
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
10
|
+
import { ReminderItem } from '../components/ReminderItem';
|
|
11
|
+
import { useRemindersStore, useReminders, useRemindersLoading } from '../../infrastructure/storage/RemindersStore';
|
|
12
|
+
import { useReminderActions } from '../../infrastructure/hooks/useReminderActions';
|
|
13
|
+
import type { Reminder, ReminderTranslations } from '../../../../infrastructure/services/types';
|
|
14
|
+
|
|
15
|
+
export interface ReminderListScreenProps {
|
|
16
|
+
translations: ReminderTranslations;
|
|
17
|
+
onAddPress: () => void;
|
|
18
|
+
onEditPress: (reminder: Reminder) => void;
|
|
19
|
+
maxReminders?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
|
|
23
|
+
translations,
|
|
24
|
+
onAddPress,
|
|
25
|
+
onEditPress,
|
|
26
|
+
maxReminders = 20,
|
|
27
|
+
}) => {
|
|
28
|
+
const tokens = useAppDesignTokens();
|
|
29
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
30
|
+
|
|
31
|
+
const reminders = useReminders();
|
|
32
|
+
const isLoading = useRemindersLoading();
|
|
33
|
+
const { toggleReminderEnabled, removeReminder } = useReminderActions();
|
|
34
|
+
|
|
35
|
+
const handleToggle = useCallback(async (id: string) => {
|
|
36
|
+
await toggleReminderEnabled(id);
|
|
37
|
+
}, [toggleReminderEnabled]);
|
|
38
|
+
|
|
39
|
+
const handleDelete = useCallback(async (id: string) => {
|
|
40
|
+
await removeReminder(id);
|
|
41
|
+
}, [removeReminder]);
|
|
42
|
+
|
|
43
|
+
const canAddMore = reminders.length < maxReminders;
|
|
44
|
+
|
|
45
|
+
const renderItem = useCallback(({ item }: { item: Reminder }) => (
|
|
46
|
+
<ReminderItem
|
|
47
|
+
reminder={item}
|
|
48
|
+
translations={{
|
|
49
|
+
frequencyOnce: translations.frequencyOnce,
|
|
50
|
+
frequencyDaily: translations.frequencyDaily,
|
|
51
|
+
frequencyWeekly: translations.frequencyWeekly,
|
|
52
|
+
frequencyMonthly: translations.frequencyMonthly,
|
|
53
|
+
}}
|
|
54
|
+
onToggle={handleToggle}
|
|
55
|
+
onEdit={onEditPress}
|
|
56
|
+
onDelete={handleDelete}
|
|
57
|
+
/>
|
|
58
|
+
), [translations, handleToggle, onEditPress, handleDelete]);
|
|
59
|
+
|
|
60
|
+
const renderEmpty = useCallback(() => (
|
|
61
|
+
<View style={styles.emptyContainer}>
|
|
62
|
+
<View style={styles.emptyIconContainer}>
|
|
63
|
+
<AtomicIcon name="notifications-off" size="xl" color="secondary" />
|
|
64
|
+
</View>
|
|
65
|
+
<AtomicText type="bodyLarge" style={styles.emptyTitle}>{translations.emptyTitle}</AtomicText>
|
|
66
|
+
<AtomicText type="bodySmall" style={styles.emptyDescription}>{translations.emptyDescription}</AtomicText>
|
|
67
|
+
</View>
|
|
68
|
+
), [translations, styles]);
|
|
69
|
+
|
|
70
|
+
const keyExtractor = useCallback((item: Reminder) => item.id, []);
|
|
71
|
+
|
|
72
|
+
if (isLoading) {
|
|
73
|
+
return (
|
|
74
|
+
<View style={[styles.loadingContainer, { backgroundColor: tokens.colors.surface }]}>
|
|
75
|
+
<AtomicSpinner size="lg" color="primary" fullContainer />
|
|
76
|
+
</View>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<View style={{ flex: 1 }}>
|
|
82
|
+
<FlatList
|
|
83
|
+
data={reminders}
|
|
84
|
+
renderItem={renderItem}
|
|
85
|
+
keyExtractor={keyExtractor}
|
|
86
|
+
ListEmptyComponent={renderEmpty}
|
|
87
|
+
contentContainerStyle={[styles.listContent, { backgroundColor: tokens.colors.surface }]}
|
|
88
|
+
showsVerticalScrollIndicator={false}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
{canAddMore && (
|
|
92
|
+
<TouchableOpacity style={styles.fab} onPress={onAddPress} activeOpacity={0.8}>
|
|
93
|
+
<AtomicIcon name="add" size="md" color="onSurface" />
|
|
94
|
+
<AtomicText type="bodyMedium" style={styles.fabText}>{translations.addButtonLabel}</AtomicText>
|
|
95
|
+
</TouchableOpacity>
|
|
96
|
+
)}
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
102
|
+
StyleSheet.create({
|
|
103
|
+
loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
104
|
+
listContent: { padding: 16, paddingBottom: 100, flexGrow: 1 },
|
|
105
|
+
emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32 },
|
|
106
|
+
emptyIconContainer: {
|
|
107
|
+
width: 80,
|
|
108
|
+
height: 80,
|
|
109
|
+
borderRadius: 40,
|
|
110
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
marginBottom: 16,
|
|
114
|
+
},
|
|
115
|
+
emptyTitle: { color: tokens.colors.textPrimary, textAlign: 'center', marginBottom: 8 },
|
|
116
|
+
emptyDescription: { color: tokens.colors.textSecondary, textAlign: 'center' },
|
|
117
|
+
fab: {
|
|
118
|
+
position: 'absolute',
|
|
119
|
+
bottom: 24,
|
|
120
|
+
left: 16,
|
|
121
|
+
right: 16,
|
|
122
|
+
backgroundColor: tokens.colors.primary,
|
|
123
|
+
borderRadius: 12,
|
|
124
|
+
paddingVertical: 14,
|
|
125
|
+
flexDirection: 'row',
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
justifyContent: 'center',
|
|
128
|
+
gap: 8,
|
|
129
|
+
},
|
|
130
|
+
fabText: { color: tokens.colors.surface, fontWeight: '600' },
|
|
131
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications Package - Public API
|
|
3
|
+
* Offline-first local notifications using expo-notifications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// TYPES
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
NotificationTrigger,
|
|
12
|
+
ScheduleNotificationOptions,
|
|
13
|
+
ScheduledNotification,
|
|
14
|
+
TimePreset,
|
|
15
|
+
ReminderFrequency,
|
|
16
|
+
Reminder,
|
|
17
|
+
CreateReminderInput,
|
|
18
|
+
UpdateReminderInput,
|
|
19
|
+
QuietHoursConfig,
|
|
20
|
+
NotificationPreferences,
|
|
21
|
+
ReminderTranslations,
|
|
22
|
+
QuietHoursTranslations,
|
|
23
|
+
NotificationSettingsTranslations,
|
|
24
|
+
} from './infrastructure/services/types';
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// CONFIGURATION
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
export { notificationsConfig } from './infrastructure/config/notificationsConfig';
|
|
31
|
+
export type {
|
|
32
|
+
NotificationSetting,
|
|
33
|
+
NotificationSection,
|
|
34
|
+
NotificationsConfig,
|
|
35
|
+
} from './infrastructure/config/notificationsConfig';
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
DEFAULT_TIME_PRESETS,
|
|
39
|
+
FREQUENCY_OPTIONS,
|
|
40
|
+
WEEKDAY_OPTIONS,
|
|
41
|
+
getTimePresetById,
|
|
42
|
+
formatTime,
|
|
43
|
+
parseTime,
|
|
44
|
+
} from './domains/reminders/infrastructure/config/reminderPresets';
|
|
45
|
+
export type { FrequencyOption, WeekdayOption } from './domains/reminders/infrastructure/config/reminderPresets';
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// SERVICES
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
export { NotificationService, notificationService } from './infrastructure/services/NotificationService';
|
|
52
|
+
export { NotificationManager } from './infrastructure/services/NotificationManager';
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// STORES
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
export { useNotificationsStore, useNotifications } from './infrastructure/storage/NotificationsStore';
|
|
59
|
+
export {
|
|
60
|
+
useRemindersStore,
|
|
61
|
+
usePreferencesStore,
|
|
62
|
+
useReminders,
|
|
63
|
+
useEnabledReminders,
|
|
64
|
+
useReminderById,
|
|
65
|
+
useNotificationPreferences,
|
|
66
|
+
useQuietHours,
|
|
67
|
+
useRemindersLoading,
|
|
68
|
+
useRemindersInitialized,
|
|
69
|
+
} from './domains/reminders/infrastructure/storage/RemindersStore';
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// HOOKS
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
export { useNotificationSettings } from './infrastructure/hooks/useNotificationSettings';
|
|
76
|
+
export { useReminderActions } from './domains/reminders/infrastructure/hooks/useReminderActions';
|
|
77
|
+
export { useQuietHoursActions } from './domains/quietHours/infrastructure/hooks/useQuietHoursActions';
|
|
78
|
+
export { useNotificationSettingsUI } from './presentation/hooks/useNotificationSettingsUI';
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// SCREENS
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
export { NotificationsScreen } from './presentation/screens/NotificationsScreen';
|
|
85
|
+
export type { NotificationsScreenProps } from './presentation/screens/NotificationsScreen';
|
|
86
|
+
|
|
87
|
+
export { NotificationSettingsScreen } from './presentation/screens/NotificationSettingsScreen';
|
|
88
|
+
export type { NotificationSettingsScreenProps } from './presentation/screens/NotificationSettingsScreen';
|
|
89
|
+
|
|
90
|
+
export { ReminderListScreen } from './domains/reminders/presentation/screens/ReminderListScreen';
|
|
91
|
+
export type { ReminderListScreenProps } from './domains/reminders/presentation/screens/ReminderListScreen';
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// COMPONENTS
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
export { NotificationsSection } from './presentation/components/NotificationsSection';
|
|
98
|
+
export type { NotificationsSectionProps, NotificationsSectionConfig } from './presentation/components/NotificationsSection';
|
|
99
|
+
|
|
100
|
+
export { TimePresetSelector } from './domains/reminders/presentation/components/TimePresetSelector';
|
|
101
|
+
export type { TimePresetSelectorProps } from './domains/reminders/presentation/components/TimePresetSelector';
|
|
102
|
+
|
|
103
|
+
export { FrequencySelector } from './domains/reminders/presentation/components/FrequencySelector';
|
|
104
|
+
export type { FrequencySelectorProps } from './domains/reminders/presentation/components/FrequencySelector';
|
|
105
|
+
|
|
106
|
+
export { WeekdaySelector } from './domains/reminders/presentation/components/WeekdaySelector';
|
|
107
|
+
export type { WeekdaySelectorProps } from './domains/reminders/presentation/components/WeekdaySelector';
|
|
108
|
+
|
|
109
|
+
export { ReminderItem } from './domains/reminders/presentation/components/ReminderItem';
|
|
110
|
+
export type { ReminderItemProps, ReminderItemTranslations } from './domains/reminders/presentation/components/ReminderItem';
|
|
111
|
+
|
|
112
|
+
export { ReminderForm } from './domains/reminders/presentation/components/ReminderForm';
|
|
113
|
+
export type { ReminderFormProps, ReminderFormTranslations } from './domains/reminders/presentation/components/ReminderForm';
|
|
114
|
+
|
|
115
|
+
export { FormButton } from './domains/reminders/presentation/components/FormButton';
|
|
116
|
+
export type { FormButtonProps } from './domains/reminders/presentation/components/FormButton';
|
|
117
|
+
|
|
118
|
+
export { QuietHoursCard } from './domains/quietHours/presentation/components/QuietHoursCard';
|
|
119
|
+
export type { QuietHoursCardProps } from './domains/quietHours/presentation/components/QuietHoursCard';
|
|
120
|
+
|
|
121
|
+
export { RemindersNavRow } from './presentation/components/RemindersNavRow';
|
|
122
|
+
export type { RemindersNavRowProps } from './presentation/components/RemindersNavRow';
|
|
123
|
+
|
|
124
|
+
export { SettingRow } from './presentation/components/SettingRow';
|
|
125
|
+
export type { SettingRowProps } from './presentation/components/SettingRow';
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// UTILS
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
export { generateId, generateReminderId } from './infrastructure/utils/idGenerator';
|
|
132
|
+
export { buildTrigger } from './infrastructure/utils/triggerBuilder';
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// HOOKS - UTILS
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
export { useTimePicker } from './presentation/hooks/useTimePicker';
|
|
139
|
+
export type { PickerMode, UseTimePickerParams, TimePickerHandlers } from './presentation/hooks/useTimePicker';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications Configuration
|
|
3
|
+
* Defines notification settings structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface NotificationSetting {
|
|
7
|
+
id: string;
|
|
8
|
+
titleKey: string;
|
|
9
|
+
descKey: string;
|
|
10
|
+
icon: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NotificationSection {
|
|
14
|
+
id: string;
|
|
15
|
+
titleKey: string;
|
|
16
|
+
settings: NotificationSetting[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface NotificationsConfig {
|
|
20
|
+
sections: NotificationSection[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const notificationsConfig: NotificationsConfig = {
|
|
24
|
+
sections: [
|
|
25
|
+
{
|
|
26
|
+
id: 'channels',
|
|
27
|
+
titleKey: 'notifications.channels',
|
|
28
|
+
settings: [
|
|
29
|
+
{
|
|
30
|
+
id: 'pushNotifications',
|
|
31
|
+
titleKey: 'notifications.push',
|
|
32
|
+
descKey: 'notifications.pushDesc',
|
|
33
|
+
icon: 'notifications',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'emailNotifications',
|
|
37
|
+
titleKey: 'notifications.email',
|
|
38
|
+
descKey: 'notifications.emailDesc',
|
|
39
|
+
icon: 'email',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'smsNotifications',
|
|
43
|
+
titleKey: 'notifications.sms',
|
|
44
|
+
descKey: 'notifications.smsDesc',
|
|
45
|
+
icon: 'message',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'content',
|
|
51
|
+
titleKey: 'notifications.content',
|
|
52
|
+
settings: [
|
|
53
|
+
{
|
|
54
|
+
id: 'appUpdates',
|
|
55
|
+
titleKey: 'notifications.updates',
|
|
56
|
+
descKey: 'notifications.updatesDesc',
|
|
57
|
+
icon: 'update',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'features',
|
|
61
|
+
titleKey: 'notifications.features',
|
|
62
|
+
descKey: 'notifications.featuresDesc',
|
|
63
|
+
icon: 'new-releases',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'tips',
|
|
67
|
+
titleKey: 'notifications.tips',
|
|
68
|
+
descKey: 'notifications.tipsDesc',
|
|
69
|
+
icon: 'tips-and-updates',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'activity',
|
|
75
|
+
titleKey: 'notifications.activity',
|
|
76
|
+
settings: [
|
|
77
|
+
{
|
|
78
|
+
id: 'reminders',
|
|
79
|
+
titleKey: 'notifications.reminders',
|
|
80
|
+
descKey: 'notifications.remindersDesc',
|
|
81
|
+
icon: 'alarm',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'achievements',
|
|
85
|
+
titleKey: 'notifications.achievements',
|
|
86
|
+
descKey: 'notifications.achievementsDesc',
|
|
87
|
+
icon: 'emoji-events',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'social',
|
|
91
|
+
titleKey: 'notifications.social',
|
|
92
|
+
descKey: 'notifications.socialDesc',
|
|
93
|
+
icon: 'people',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple notification settings hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createStore } from '@umituz/react-native-storage';
|
|
6
|
+
|
|
7
|
+
interface NotificationSettingsState {
|
|
8
|
+
notificationsEnabled: boolean;
|
|
9
|
+
isLoading: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface NotificationSettingsActions {
|
|
13
|
+
setNotificationsEnabled: (value: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const useNotificationSettingsStore = createStore<NotificationSettingsState, NotificationSettingsActions>({
|
|
17
|
+
name: 'notification-settings-store',
|
|
18
|
+
initialState: {
|
|
19
|
+
notificationsEnabled: true,
|
|
20
|
+
isLoading: true,
|
|
21
|
+
},
|
|
22
|
+
persist: true,
|
|
23
|
+
actions: (set) => ({
|
|
24
|
+
setNotificationsEnabled: (value: boolean) => set({ notificationsEnabled: value }),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const useNotificationSettings = () => {
|
|
29
|
+
const store = useNotificationSettingsStore();
|
|
30
|
+
const { notificationsEnabled, isLoading, setNotificationsEnabled } = store;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
notificationsEnabled,
|
|
34
|
+
setNotificationsEnabled,
|
|
35
|
+
isLoading,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as Notifications from 'expo-notifications';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import { devError } from '../utils/dev';
|
|
4
|
+
|
|
5
|
+
export class NotificationBadgeManager {
|
|
6
|
+
async getBadgeCount(): Promise<number> {
|
|
7
|
+
try {
|
|
8
|
+
if (Platform.OS === 'ios') {
|
|
9
|
+
return await Notifications.getBadgeCountAsync();
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
devError('[NotificationBadgeManager] Get badge count failed:', error);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async setBadgeCount(count: number): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
if (Platform.OS === 'ios') {
|
|
21
|
+
await Notifications.setBadgeCountAsync(count);
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
devError('[NotificationBadgeManager] Set badge count failed:', error);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationManager - Core Notification Operations
|
|
3
|
+
*
|
|
4
|
+
* Offline-first notification system using expo-notifications.
|
|
5
|
+
* Works in ALL apps (offline, online, hybrid) - no backend required.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as Notifications from 'expo-notifications';
|
|
9
|
+
import * as Device from 'expo-device';
|
|
10
|
+
import { Platform } from 'react-native';
|
|
11
|
+
import { NotificationPermissions } from './NotificationPermissions';
|
|
12
|
+
import { NotificationScheduler } from './NotificationScheduler';
|
|
13
|
+
import { NotificationBadgeManager } from './NotificationBadgeManager';
|
|
14
|
+
import { devLog, devError } from '../utils/dev';
|
|
15
|
+
import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './types';
|
|
16
|
+
|
|
17
|
+
export class NotificationManager {
|
|
18
|
+
private permissions: NotificationPermissions;
|
|
19
|
+
private scheduler: NotificationScheduler;
|
|
20
|
+
private badgeManager: NotificationBadgeManager;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
this.permissions = new NotificationPermissions();
|
|
24
|
+
this.scheduler = new NotificationScheduler();
|
|
25
|
+
this.badgeManager = new NotificationBadgeManager();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static configure() {
|
|
29
|
+
Notifications.setNotificationHandler({
|
|
30
|
+
handleNotification: async () => ({
|
|
31
|
+
shouldShowAlert: true,
|
|
32
|
+
shouldPlaySound: true,
|
|
33
|
+
shouldSetBadge: true,
|
|
34
|
+
shouldShowBanner: true,
|
|
35
|
+
shouldShowList: true,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
devLog('[NotificationManager] Configured notification handler');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async requestPermissions(): Promise<boolean> {
|
|
43
|
+
try {
|
|
44
|
+
const result = await this.permissions.requestPermissions();
|
|
45
|
+
|
|
46
|
+
devLog('[NotificationManager] Permissions requested:', result);
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
devError('[NotificationManager] Permission request failed:', error);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async hasPermissions(): Promise<boolean> {
|
|
56
|
+
try {
|
|
57
|
+
return await this.permissions.hasPermissions();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
devError('[NotificationManager] Permission check failed:', error);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async scheduleNotification(options: ScheduleNotificationOptions): Promise<string> {
|
|
65
|
+
try {
|
|
66
|
+
const id = await this.scheduler.scheduleNotification(options);
|
|
67
|
+
|
|
68
|
+
devLog('[NotificationManager] Notification scheduled:', id);
|
|
69
|
+
|
|
70
|
+
return id;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
devError('[NotificationManager] Schedule notification failed:', error);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async cancelNotification(notificationId: string): Promise<void> {
|
|
78
|
+
try {
|
|
79
|
+
await this.scheduler.cancelNotification(notificationId);
|
|
80
|
+
|
|
81
|
+
devLog('[NotificationManager] Notification cancelled:', notificationId);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
devError('[NotificationManager] Cancel notification failed:', error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async cancelAllNotifications(): Promise<void> {
|
|
89
|
+
try {
|
|
90
|
+
await this.scheduler.cancelAllNotifications();
|
|
91
|
+
|
|
92
|
+
devLog('[NotificationManager] All notifications cancelled');
|
|
93
|
+
} catch (error) {
|
|
94
|
+
devError('[NotificationManager] Cancel all notifications failed:', error);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getScheduledNotifications(): Promise<ScheduledNotification[]> {
|
|
100
|
+
try {
|
|
101
|
+
return await this.scheduler.getScheduledNotifications();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
devError('[NotificationManager] Get scheduled notifications failed:', error);
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async dismissAllNotifications(): Promise<void> {
|
|
109
|
+
try {
|
|
110
|
+
await Notifications.dismissAllNotificationsAsync();
|
|
111
|
+
|
|
112
|
+
devLog('[NotificationManager] All notifications dismissed');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
devError('[NotificationManager] Dismiss all notifications failed:', error);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async getBadgeCount(): Promise<number> {
|
|
120
|
+
try {
|
|
121
|
+
return await this.badgeManager.getBadgeCount();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
devError('[NotificationManager] Get badge count failed:', error);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async setBadgeCount(count: number): Promise<void> {
|
|
129
|
+
try {
|
|
130
|
+
await this.badgeManager.setBadgeCount(count);
|
|
131
|
+
|
|
132
|
+
devLog('[NotificationManager] Badge count set:', count);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
devError('[NotificationManager] Set badge count failed:', error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|