@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.
Files changed (46) hide show
  1. package/package.json +11 -2
  2. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +42 -87
  3. package/src/domains/gamification/components/index.ts +1 -0
  4. package/src/domains/gamification/index.ts +3 -11
  5. package/src/index.ts +3 -0
  6. package/src/notifications/domains/quietHours/infrastructure/hooks/useQuietHoursActions.ts +52 -0
  7. package/src/notifications/domains/quietHours/presentation/components/QuietHoursCard.tsx +112 -0
  8. package/src/notifications/domains/reminders/infrastructure/config/reminderPresets.ts +120 -0
  9. package/src/notifications/domains/reminders/infrastructure/hooks/useReminderActions.ts +106 -0
  10. package/src/notifications/domains/reminders/infrastructure/storage/RemindersStore.ts +148 -0
  11. package/src/notifications/domains/reminders/presentation/components/FormButton.tsx +66 -0
  12. package/src/notifications/domains/reminders/presentation/components/FrequencySelector.tsx +72 -0
  13. package/src/notifications/domains/reminders/presentation/components/ReminderForm.tsx +169 -0
  14. package/src/notifications/domains/reminders/presentation/components/ReminderItem.tsx +130 -0
  15. package/src/notifications/domains/reminders/presentation/components/TimePresetSelector.tsx +100 -0
  16. package/src/notifications/domains/reminders/presentation/components/WeekdaySelector.tsx +61 -0
  17. package/src/notifications/domains/reminders/presentation/screens/ReminderListScreen.tsx +131 -0
  18. package/src/notifications/index.ts +139 -0
  19. package/src/notifications/infrastructure/config/notificationsConfig.ts +98 -0
  20. package/src/notifications/infrastructure/hooks/useNotificationSettings.ts +37 -0
  21. package/src/notifications/infrastructure/services/NotificationBadgeManager.ts +28 -0
  22. package/src/notifications/infrastructure/services/NotificationManager.ts +138 -0
  23. package/src/notifications/infrastructure/services/NotificationPermissions.ts +80 -0
  24. package/src/notifications/infrastructure/services/NotificationScheduler.ts +77 -0
  25. package/src/notifications/infrastructure/services/NotificationService.ts +50 -0
  26. package/src/notifications/infrastructure/services/types.ts +176 -0
  27. package/src/notifications/infrastructure/storage/NotificationsStore.ts +45 -0
  28. package/src/notifications/infrastructure/utils/dev.ts +25 -0
  29. package/src/notifications/infrastructure/utils/idGenerator.ts +14 -0
  30. package/src/notifications/infrastructure/utils/triggerBuilder.ts +45 -0
  31. package/src/notifications/presentation/components/NotificationsSection.tsx +84 -0
  32. package/src/notifications/presentation/components/RemindersNavRow.styles.ts +38 -0
  33. package/src/notifications/presentation/components/RemindersNavRow.tsx +51 -0
  34. package/src/notifications/presentation/components/SettingRow.tsx +86 -0
  35. package/src/notifications/presentation/hooks/useNotificationSettingsUI.ts +52 -0
  36. package/src/notifications/presentation/hooks/useTimePicker.ts +71 -0
  37. package/src/notifications/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
  38. package/src/notifications/presentation/screens/NotificationSettingsScreen.tsx +131 -0
  39. package/src/notifications/presentation/screens/NotificationsScreen.tsx +107 -0
  40. package/src/presentation/navigation/SettingsStackNavigator.tsx +21 -11
  41. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +1 -1
  42. package/src/domains/gamification/README.md +0 -343
  43. package/src/domains/gamification/components/GamificationSettingsItem.tsx +0 -33
  44. package/src/domains/gamification/examples/gamification.config.example.ts +0 -70
  45. package/src/domains/gamification/examples/localization.example.json +0 -71
  46. package/src/domains/gamification/types/settings.ts +0 -28
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Reminders Store - Zustand State Management
3
+ * Manages reminder state with AsyncStorage persistence
4
+ */
5
+
6
+ import { createStore } from '@umituz/react-native-storage';
7
+ import type { Reminder, QuietHoursConfig, NotificationPreferences } from '../../../../infrastructure/services/types';
8
+
9
+ // ============================================================================
10
+ // REMINDERS STORE
11
+ // ============================================================================
12
+
13
+ interface RemindersState {
14
+ reminders: Reminder[];
15
+ }
16
+
17
+ interface RemindersActions {
18
+ addReminder: (reminder: Reminder) => void;
19
+ updateReminder: (id: string, updates: Partial<Reminder>) => void;
20
+ deleteReminder: (id: string) => void;
21
+ toggleReminder: (id: string) => void;
22
+ resetReminders: () => void;
23
+ }
24
+
25
+ const DEFAULT_REMINDERS_STATE: RemindersState = {
26
+ reminders: [],
27
+ };
28
+
29
+ export const useRemindersStore = createStore<RemindersState, RemindersActions>({
30
+ name: 'reminders-store',
31
+ initialState: DEFAULT_REMINDERS_STATE,
32
+ persist: true,
33
+ actions: (set, get) => ({
34
+ addReminder: (reminder: Reminder) => {
35
+ const { reminders } = get();
36
+ set({ reminders: [...reminders, reminder] });
37
+ },
38
+
39
+ updateReminder: (id: string, updates: Partial<Reminder>) => {
40
+ const { reminders } = get();
41
+ set({
42
+ reminders: reminders.map(r =>
43
+ r.id === id ? { ...r, ...updates, updatedAt: new Date().toISOString() } : r
44
+ ),
45
+ });
46
+ },
47
+
48
+ deleteReminder: (id: string) => {
49
+ const { reminders } = get();
50
+ set({ reminders: reminders.filter(r => r.id !== id) });
51
+ },
52
+
53
+ toggleReminder: (id: string) => {
54
+ const { reminders } = get();
55
+ set({
56
+ reminders: reminders.map(r =>
57
+ r.id === id ? { ...r, enabled: !r.enabled, updatedAt: new Date().toISOString() } : r
58
+ ),
59
+ });
60
+ },
61
+
62
+ resetReminders: () => {
63
+ set({ reminders: [] });
64
+ },
65
+ }),
66
+ });
67
+
68
+ // ============================================================================
69
+ // PREFERENCES STORE
70
+ // ============================================================================
71
+
72
+ interface PreferencesState {
73
+ preferences: NotificationPreferences;
74
+ isLoading: boolean;
75
+ isInitialized: boolean;
76
+ }
77
+
78
+ interface PreferencesActions {
79
+ initialize: () => Promise<void>;
80
+ updatePreferences: (updates: Partial<NotificationPreferences>) => void;
81
+ updateQuietHours: (quietHours: QuietHoursConfig) => void;
82
+ reset: () => void;
83
+ }
84
+
85
+ // ============================================================================
86
+ // DEFAULT VALUES
87
+ // ============================================================================
88
+
89
+ const DEFAULT_PREFERENCES: NotificationPreferences = {
90
+ enabled: true,
91
+ sound: true,
92
+ vibration: true,
93
+ quietHours: {
94
+ enabled: false,
95
+ startHour: 22,
96
+ startMinute: 0,
97
+ endHour: 7,
98
+ endMinute: 0,
99
+ },
100
+ };
101
+
102
+ const initialPreferencesState: PreferencesState = {
103
+ preferences: DEFAULT_PREFERENCES,
104
+ isLoading: true,
105
+ isInitialized: false,
106
+ };
107
+
108
+ export const usePreferencesStore = createStore<PreferencesState, PreferencesActions>({
109
+ name: 'preferences-store',
110
+ initialState: initialPreferencesState,
111
+ persist: true,
112
+ actions: (set, get) => ({
113
+ initialize: async () => {
114
+ set({ isLoading: false, isInitialized: true });
115
+ },
116
+
117
+ updatePreferences: (updates: Partial<NotificationPreferences>) => {
118
+ const { preferences } = get();
119
+ set({ preferences: { ...preferences, ...updates } });
120
+ },
121
+
122
+ updateQuietHours: (quietHours: QuietHoursConfig) => {
123
+ const { preferences } = get();
124
+ set({ preferences: { ...preferences, quietHours } });
125
+ },
126
+
127
+ reset: () => {
128
+ set({ preferences: DEFAULT_PREFERENCES });
129
+ },
130
+ }),
131
+ });
132
+
133
+ // ============================================================================
134
+ // SELECTOR HOOKS - REMINDERS
135
+ // ============================================================================
136
+
137
+ export const useReminders = () => useRemindersStore(state => state.reminders);
138
+ export const useEnabledReminders = () => useRemindersStore(state => state.reminders.filter(r => r.enabled));
139
+ export const useReminderById = (id: string) => useRemindersStore(state => state.reminders.find(r => r.id === id));
140
+
141
+ // ============================================================================
142
+ // SELECTOR HOOKS - PREFERENCES
143
+ // ============================================================================
144
+
145
+ export const useNotificationPreferences = () => usePreferencesStore(state => state.preferences);
146
+ export const useQuietHours = () => usePreferencesStore(state => state.preferences.quietHours);
147
+ export const useRemindersLoading = () => usePreferencesStore(state => state.isLoading);
148
+ export const useRemindersInitialized = () => usePreferencesStore(state => state.isInitialized);
@@ -0,0 +1,66 @@
1
+ /**
2
+ * FormButton Component
3
+ * Simple button for forms
4
+ */
5
+
6
+ import React, { useMemo } from 'react';
7
+ import { TouchableOpacity, StyleSheet } from 'react-native';
8
+ import { AtomicText } from '@umituz/react-native-design-system';
9
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
10
+
11
+ export interface FormButtonProps {
12
+ label: string;
13
+ onPress: () => void;
14
+ variant?: 'primary' | 'secondary';
15
+ disabled?: boolean;
16
+ }
17
+
18
+ export const FormButton: React.FC<FormButtonProps> = ({
19
+ label,
20
+ onPress,
21
+ variant = 'primary',
22
+ disabled = false,
23
+ }) => {
24
+ const tokens = useAppDesignTokens();
25
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
26
+
27
+ const isPrimary = variant === 'primary';
28
+
29
+ return (
30
+ <TouchableOpacity
31
+ style={[
32
+ styles.button,
33
+ isPrimary ? styles.primaryButton : styles.secondaryButton,
34
+ disabled ? styles.disabled : undefined,
35
+ ]}
36
+ onPress={onPress}
37
+ disabled={disabled}
38
+ activeOpacity={0.7}
39
+ >
40
+ <AtomicText
41
+ type="bodyMedium"
42
+ style={[styles.label, isPrimary ? styles.primaryLabel : styles.secondaryLabel]}
43
+ >
44
+ {label}
45
+ </AtomicText>
46
+ </TouchableOpacity>
47
+ );
48
+ };
49
+
50
+ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
51
+ StyleSheet.create({
52
+ button: {
53
+ flex: 1,
54
+ paddingVertical: 14,
55
+ paddingHorizontal: 16,
56
+ borderRadius: 10,
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ },
60
+ primaryButton: { backgroundColor: tokens.colors.primary },
61
+ secondaryButton: { backgroundColor: tokens.colors.surfaceSecondary },
62
+ disabled: { opacity: 0.5 },
63
+ label: { fontWeight: '600' },
64
+ primaryLabel: { color: tokens.colors.surface },
65
+ secondaryLabel: { color: tokens.colors.textPrimary },
66
+ });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * FrequencySelector Component
3
+ * Allows selection of reminder frequency
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 { ReminderFrequency } from '../../../../infrastructure/services/types';
11
+ import type { FrequencyOption } from '../../infrastructure/config/reminderPresets';
12
+
13
+ export interface FrequencySelectorProps {
14
+ options: FrequencyOption[];
15
+ selectedFrequency: ReminderFrequency;
16
+ onSelect: (frequency: ReminderFrequency) => void;
17
+ getLabel: (labelKey: string) => string;
18
+ }
19
+
20
+ export const FrequencySelector: React.FC<FrequencySelectorProps> = ({
21
+ options,
22
+ selectedFrequency,
23
+ onSelect,
24
+ getLabel,
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
28
+
29
+ return (
30
+ <View style={styles.container}>
31
+ {options.map(option => {
32
+ const isSelected = selectedFrequency === option.id;
33
+ return (
34
+ <TouchableOpacity
35
+ key={option.id}
36
+ style={[styles.button, isSelected ? styles.selectedButton : undefined]}
37
+ onPress={() => onSelect(option.id)}
38
+ activeOpacity={0.7}
39
+ >
40
+ <AtomicIcon
41
+ name={option.iconName}
42
+ size="sm"
43
+ color={isSelected ? 'onSurface' : 'secondary'}
44
+ />
45
+ <AtomicText type="bodySmall" style={isSelected ? styles.selectedText : styles.text}>
46
+ {getLabel(option.labelKey)}
47
+ </AtomicText>
48
+ </TouchableOpacity>
49
+ );
50
+ })}
51
+ </View>
52
+ );
53
+ };
54
+
55
+ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
56
+ StyleSheet.create({
57
+ container: { flexDirection: 'row', gap: 8 },
58
+ button: {
59
+ flex: 1,
60
+ flexDirection: 'row',
61
+ alignItems: 'center',
62
+ justifyContent: 'center',
63
+ paddingVertical: 10,
64
+ paddingHorizontal: 8,
65
+ borderRadius: 8,
66
+ backgroundColor: tokens.colors.surfaceSecondary,
67
+ gap: 6,
68
+ },
69
+ selectedButton: { backgroundColor: tokens.colors.primary },
70
+ text: { color: tokens.colors.textSecondary },
71
+ selectedText: { color: tokens.colors.surface },
72
+ });
@@ -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
+ });