@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.
Files changed (47) hide show
  1. package/package.json +11 -2
  2. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +58 -85
  3. package/src/domains/gamification/components/index.ts +1 -0
  4. package/src/domains/gamification/index.ts +4 -11
  5. package/src/domains/gamification/types/index.ts +18 -0
  6. package/src/domains/notifications/index.ts +139 -0
  7. package/src/domains/notifications/infrastructure/config/notificationsConfig.ts +98 -0
  8. package/src/domains/notifications/infrastructure/hooks/useNotificationSettings.ts +37 -0
  9. package/src/domains/notifications/infrastructure/services/NotificationBadgeManager.ts +28 -0
  10. package/src/domains/notifications/infrastructure/services/NotificationManager.ts +138 -0
  11. package/src/domains/notifications/infrastructure/services/NotificationPermissions.ts +80 -0
  12. package/src/domains/notifications/infrastructure/services/NotificationScheduler.ts +77 -0
  13. package/src/domains/notifications/infrastructure/services/NotificationService.ts +50 -0
  14. package/src/domains/notifications/infrastructure/services/types.ts +176 -0
  15. package/src/domains/notifications/infrastructure/storage/NotificationsStore.ts +45 -0
  16. package/src/domains/notifications/infrastructure/utils/dev.ts +25 -0
  17. package/src/domains/notifications/infrastructure/utils/idGenerator.ts +14 -0
  18. package/src/domains/notifications/infrastructure/utils/triggerBuilder.ts +45 -0
  19. package/src/domains/notifications/presentation/components/NotificationsSection.tsx +84 -0
  20. package/src/domains/notifications/presentation/components/RemindersNavRow.styles.ts +38 -0
  21. package/src/domains/notifications/presentation/components/RemindersNavRow.tsx +51 -0
  22. package/src/domains/notifications/presentation/components/SettingRow.tsx +86 -0
  23. package/src/domains/notifications/presentation/hooks/useNotificationSettingsUI.ts +52 -0
  24. package/src/domains/notifications/presentation/hooks/useTimePicker.ts +71 -0
  25. package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
  26. package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.tsx +131 -0
  27. package/src/domains/notifications/presentation/screens/NotificationsScreen.tsx +107 -0
  28. package/src/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.ts +52 -0
  29. package/src/domains/notifications/quietHours/presentation/components/QuietHoursCard.tsx +112 -0
  30. package/src/domains/notifications/reminders/infrastructure/config/reminderPresets.ts +120 -0
  31. package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +106 -0
  32. package/src/domains/notifications/reminders/infrastructure/storage/RemindersStore.ts +148 -0
  33. package/src/domains/notifications/reminders/presentation/components/FormButton.tsx +66 -0
  34. package/src/domains/notifications/reminders/presentation/components/FrequencySelector.tsx +72 -0
  35. package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +169 -0
  36. package/src/domains/notifications/reminders/presentation/components/ReminderItem.tsx +130 -0
  37. package/src/domains/notifications/reminders/presentation/components/TimePresetSelector.tsx +100 -0
  38. package/src/domains/notifications/reminders/presentation/components/WeekdaySelector.tsx +61 -0
  39. package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +131 -0
  40. package/src/index.ts +3 -0
  41. package/src/presentation/navigation/SettingsStackNavigator.tsx +21 -11
  42. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +1 -1
  43. package/src/domains/gamification/README.md +0 -343
  44. package/src/domains/gamification/components/GamificationSettingsItem.tsx +0 -33
  45. package/src/domains/gamification/examples/gamification.config.example.ts +0 -70
  46. package/src/domains/gamification/examples/localization.example.json +0 -71
  47. package/src/domains/gamification/types/settings.ts +0 -28
@@ -0,0 +1,169 @@
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';
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: {
154
+ flex: 1,
155
+ padding: 16,
156
+ backgroundColor: tokens.colors.surface,
157
+ },
158
+ section: { marginBottom: 20 },
159
+ label: { color: tokens.colors.textPrimary, marginBottom: 8 },
160
+ input: {
161
+ backgroundColor: tokens.colors.surfaceSecondary,
162
+ borderRadius: 8,
163
+ padding: 12,
164
+ fontSize: 16,
165
+ color: tokens.colors.textPrimary,
166
+ },
167
+ multilineInput: { minHeight: 80, textAlignVertical: 'top' },
168
+ buttonRow: { flexDirection: 'row', gap: 12, marginTop: 24 },
169
+ });
@@ -0,0 +1,130 @@
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 } from '@umituz/react-native-design-system';
9
+ import { Switch } from 'react-native';
10
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
11
+ import type { Reminder, ReminderFrequency } from '../../../infrastructure/services/types';
12
+
13
+ export interface ReminderItemTranslations {
14
+ frequencyOnce: string;
15
+ frequencyDaily: string;
16
+ frequencyWeekly: string;
17
+ frequencyMonthly: string;
18
+ }
19
+
20
+ export interface ReminderItemProps {
21
+ reminder: Reminder;
22
+ translations: ReminderItemTranslations;
23
+ onToggle: (id: string) => void;
24
+ onEdit: (reminder: Reminder) => void;
25
+ onDelete: (id: string) => void;
26
+ }
27
+
28
+ const formatTime = (hour: number, minute: number): string => {
29
+ return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
30
+ };
31
+
32
+ const getFrequencyIcon = (frequency: ReminderFrequency): string => {
33
+ const icons: Record<ReminderFrequency, string> = {
34
+ once: 'calendar',
35
+ daily: 'repeat',
36
+ weekly: 'calendar',
37
+ monthly: 'calendar',
38
+ };
39
+ return icons[frequency] || 'notifications';
40
+ };
41
+
42
+ export const ReminderItem: React.FC<ReminderItemProps> = ({
43
+ reminder,
44
+ translations,
45
+ onToggle,
46
+ onEdit,
47
+ onDelete,
48
+ }) => {
49
+ const tokens = useAppDesignTokens();
50
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
51
+
52
+ const getFrequencyLabel = (frequency: ReminderFrequency): string => {
53
+ const labels: Record<ReminderFrequency, string> = {
54
+ once: translations.frequencyOnce,
55
+ daily: translations.frequencyDaily,
56
+ weekly: translations.frequencyWeekly,
57
+ monthly: translations.frequencyMonthly,
58
+ };
59
+ return labels[frequency] || '';
60
+ };
61
+
62
+ return (
63
+ <View style={[styles.container, !reminder.enabled ? styles.disabled : undefined]}>
64
+ <TouchableOpacity style={styles.content} onPress={() => onEdit(reminder)} activeOpacity={0.7}>
65
+ <View style={styles.iconContainer}>
66
+ <AtomicIcon
67
+ name={getFrequencyIcon(reminder.frequency)}
68
+ size="md"
69
+ color={reminder.enabled ? 'primary' : 'secondary'}
70
+ />
71
+ </View>
72
+ <View style={styles.textContainer}>
73
+ <AtomicText type="bodyLarge" style={!reminder.enabled ? styles.disabledText : undefined}>
74
+ {reminder.title}
75
+ </AtomicText>
76
+ <View style={styles.metaRow}>
77
+ <AtomicText type="bodySmall" style={styles.time}>
78
+ {formatTime(reminder.hour, reminder.minute)}
79
+ </AtomicText>
80
+ <AtomicText type="bodySmall" style={styles.frequency}>
81
+ {getFrequencyLabel(reminder.frequency)}
82
+ </AtomicText>
83
+ </View>
84
+ </View>
85
+ </TouchableOpacity>
86
+
87
+ <View style={styles.actions}>
88
+ <TouchableOpacity style={styles.deleteButton} onPress={() => onDelete(reminder.id)}>
89
+ <AtomicIcon name="trash" size="sm" color="error" />
90
+ </TouchableOpacity>
91
+ <Switch
92
+ value={reminder.enabled}
93
+ onValueChange={() => onToggle(reminder.id)}
94
+ trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
95
+ thumbColor={tokens.colors.surface}
96
+ />
97
+ </View>
98
+ </View>
99
+ );
100
+ };
101
+
102
+ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
103
+ StyleSheet.create({
104
+ container: {
105
+ flexDirection: 'row',
106
+ alignItems: 'center',
107
+ padding: 16,
108
+ backgroundColor: tokens.colors.surface,
109
+ borderRadius: 12,
110
+ marginBottom: 8,
111
+ },
112
+ disabled: { opacity: 0.6 },
113
+ content: { flex: 1, flexDirection: 'row', alignItems: 'center' },
114
+ iconContainer: {
115
+ width: 44,
116
+ height: 44,
117
+ borderRadius: 22,
118
+ backgroundColor: tokens.colors.surfaceSecondary,
119
+ justifyContent: 'center',
120
+ alignItems: 'center',
121
+ marginRight: 12,
122
+ },
123
+ textContainer: { flex: 1 },
124
+ disabledText: { color: tokens.colors.textSecondary },
125
+ metaRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 4 },
126
+ time: { color: tokens.colors.primary, fontWeight: '600' },
127
+ frequency: { color: tokens.colors.textSecondary },
128
+ actions: { flexDirection: 'row', alignItems: 'center', gap: 8 },
129
+ deleteButton: { padding: 4 },
130
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * TimePresetSelector Component
3
+ * Allows selection of preset times or custom time
4
+ */
5
+
6
+ import React, { useMemo } from 'react';
7
+ import { View, TouchableOpacity, StyleSheet } from 'react-native';
8
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
9
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
10
+ import type { TimePreset } from '../../../infrastructure/services/types';
11
+
12
+ export interface TimePresetSelectorProps {
13
+ presets: TimePreset[];
14
+ selectedPresetId?: string;
15
+ customTime?: { hour: number; minute: number };
16
+ onSelectPreset: (preset: TimePreset) => void;
17
+ onSelectCustom: () => void;
18
+ getPresetLabel: (labelKey: string) => string;
19
+ customLabel: string;
20
+ isCustomSelected: boolean;
21
+ }
22
+
23
+ const formatTime = (hour: number, minute: number): string => {
24
+ return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
25
+ };
26
+
27
+ export const TimePresetSelector: React.FC<TimePresetSelectorProps> = ({
28
+ presets,
29
+ selectedPresetId,
30
+ customTime,
31
+ onSelectPreset,
32
+ onSelectCustom,
33
+ getPresetLabel,
34
+ customLabel,
35
+ isCustomSelected,
36
+ }) => {
37
+ const tokens = useAppDesignTokens();
38
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
39
+
40
+ return (
41
+ <View style={styles.container}>
42
+ {presets.map(preset => {
43
+ const isSelected = selectedPresetId === preset.id && !isCustomSelected;
44
+ return (
45
+ <TouchableOpacity
46
+ key={preset.id}
47
+ style={[styles.button, isSelected ? styles.selectedButton : undefined]}
48
+ onPress={() => onSelectPreset(preset)}
49
+ activeOpacity={0.7}
50
+ >
51
+ <AtomicIcon
52
+ name={preset.iconName}
53
+ size="md"
54
+ color={isSelected ? 'onSurface' : 'secondary'}
55
+ />
56
+ <AtomicText type="bodySmall" style={isSelected ? styles.selectedText : styles.text}>
57
+ {getPresetLabel(preset.labelKey)}
58
+ </AtomicText>
59
+ <AtomicText type="bodySmall" style={isSelected ? styles.selectedText : styles.subText}>
60
+ {formatTime(preset.hour, preset.minute)}
61
+ </AtomicText>
62
+ </TouchableOpacity>
63
+ );
64
+ })}
65
+
66
+ <TouchableOpacity
67
+ style={[styles.button, isCustomSelected ? styles.selectedButton : undefined]}
68
+ onPress={onSelectCustom}
69
+ activeOpacity={0.7}
70
+ >
71
+ <AtomicIcon name="time" size="md" color={isCustomSelected ? 'onSurface' : 'secondary'} />
72
+ <AtomicText type="bodySmall" style={isCustomSelected ? styles.selectedText : styles.text}>
73
+ {customLabel}
74
+ </AtomicText>
75
+ {customTime ? (
76
+ <AtomicText type="bodySmall" style={isCustomSelected ? styles.selectedText : styles.subText}>
77
+ {formatTime(customTime.hour, customTime.minute)}
78
+ </AtomicText>
79
+ ) : null}
80
+ </TouchableOpacity>
81
+ </View>
82
+ );
83
+ };
84
+
85
+ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
86
+ StyleSheet.create({
87
+ container: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
88
+ button: {
89
+ paddingVertical: 8,
90
+ paddingHorizontal: 12,
91
+ borderRadius: 8,
92
+ backgroundColor: tokens.colors.surfaceSecondary,
93
+ alignItems: 'center',
94
+ minWidth: 72,
95
+ },
96
+ selectedButton: { backgroundColor: tokens.colors.primary },
97
+ text: { color: tokens.colors.textSecondary, marginTop: 4 },
98
+ subText: { color: tokens.colors.textSecondary, marginTop: 2, opacity: 0.7 },
99
+ selectedText: { color: tokens.colors.surface, marginTop: 4 },
100
+ });
@@ -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
+ });
package/src/index.ts CHANGED
@@ -128,6 +128,9 @@ export * from "./domains/gamification";
128
128
  // PRESENTATION LAYER - Config Creator Utilities
129
129
  // =============================================================================
130
130
 
131
+ // Notifications Domain
132
+ export * from "./domains/notifications";
133
+
131
134
  export {
132
135
  createAppearanceConfig,
133
136
  createLanguageConfig,
@@ -8,9 +8,13 @@
8
8
  import React from "react";
9
9
  import { createStackNavigator } from "@react-navigation/stack";
10
10
  import { useLocalization, LanguageSelectionScreen } from "@umituz/react-native-localization";
11
- import { NotificationSettingsScreen } from "@umituz/react-native-notifications";
11
+ import { NotificationSettingsScreen } from "@notifications";
12
12
  import { AccountScreen } from "@umituz/react-native-auth";
13
- import { useAppDesignTokens, createScreenOptions } from "@umituz/react-native-design-system";
13
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
14
+
15
+ // ...
16
+
17
+
14
18
  import { AppearanceScreen } from "../screens/AppearanceScreen";
15
19
  import { FAQScreen } from "../../domains/faqs";
16
20
  import { useNavigationHandlers } from "./hooks";
@@ -49,15 +53,21 @@ export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = ({
49
53
  useNavigationHandlers(appInfo, legalUrls);
50
54
 
51
55
  const screenOptions = React.useMemo(
52
- () =>
53
- createScreenOptions({
54
- colors: {
55
- surface: tokens.colors.surface,
56
- textPrimary: tokens.colors.textPrimary,
57
- borderLight: tokens.colors.borderLight,
58
- },
59
- backTitle: t("settings.title"),
60
- }),
56
+ () => ({
57
+ headerStyle: {
58
+ backgroundColor: tokens.colors.surface,
59
+ borderBottomColor: tokens.colors.borderLight,
60
+ borderBottomWidth: 1,
61
+ elevation: 0,
62
+ shadowOpacity: 0,
63
+ },
64
+ headerTintColor: tokens.colors.textPrimary,
65
+ headerTitleStyle: {
66
+ color: tokens.colors.textPrimary,
67
+ fontWeight: "600" as const,
68
+ },
69
+ headerBackTitle: t("settings.title"),
70
+ }),
61
71
  [tokens, t]
62
72
  );
63
73
  const notificationTranslations = React.useMemo(() => createNotificationTranslations(t), [t]);
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { useNavigation } from "@react-navigation/native";
3
3
  import { AppearanceSection } from "../../../../domains/appearance/presentation/components/AppearanceSection";
4
- import { NotificationsSection } from "@umituz/react-native-notifications";
4
+ import { NotificationsSection } from "@notifications";
5
5
  import { useLocalization, getLanguageByCode } from "@umituz/react-native-localization";
6
6
  import { SettingsItemCard } from "../../../components/SettingsItemCard";
7
7
  import type { NormalizedConfig } from "../../utils/normalizeConfig";