@umituz/react-native-notifications 1.5.10 → 1.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-notifications",
3
- "version": "1.5.10",
3
+ "version": "1.5.11",
4
4
  "description": "Offline-first local notifications system for React Native apps using expo-notifications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -6,55 +6,19 @@
6
6
  import { useCallback } from 'react';
7
7
  import { useRemindersStore } from '../storage/RemindersStore';
8
8
  import { NotificationScheduler } from '../../../../infrastructure/services/NotificationScheduler';
9
- import type { Reminder, CreateReminderInput, UpdateReminderInput, NotificationTrigger } from '../../../../infrastructure/services/types';
9
+ import { generateReminderId } from '../../../../infrastructure/utils/idGenerator';
10
+ import { buildTrigger } from '../../../../infrastructure/utils/triggerBuilder';
11
+ import type { Reminder, CreateReminderInput, UpdateReminderInput } from '../../../../infrastructure/services/types';
10
12
 
11
13
  const scheduler = new NotificationScheduler();
12
14
 
13
- const generateId = (): string => {
14
- return `reminder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
15
- };
16
-
17
- const buildTrigger = (reminder: Reminder): NotificationTrigger => {
18
- switch (reminder.frequency) {
19
- case 'once':
20
- const date = new Date();
21
- date.setHours(reminder.hour, reminder.minute, 0, 0);
22
- if (date <= new Date()) {
23
- date.setDate(date.getDate() + 1);
24
- }
25
- return { type: 'date', date };
26
-
27
- case 'daily':
28
- return { type: 'daily', hour: reminder.hour, minute: reminder.minute };
29
-
30
- case 'weekly':
31
- return {
32
- type: 'weekly',
33
- weekday: reminder.weekday || 2,
34
- hour: reminder.hour,
35
- minute: reminder.minute,
36
- };
37
-
38
- case 'monthly':
39
- return {
40
- type: 'monthly',
41
- day: reminder.dayOfMonth || 1,
42
- hour: reminder.hour,
43
- minute: reminder.minute,
44
- };
45
-
46
- default:
47
- return { type: 'daily', hour: reminder.hour, minute: reminder.minute };
48
- }
49
- };
50
-
51
15
  export const useReminderActions = () => {
52
16
  const { addReminder, updateReminder, deleteReminder, toggleReminder } = useRemindersStore();
53
17
 
54
18
  const createReminder = useCallback(async (input: CreateReminderInput): Promise<Reminder> => {
55
19
  const now = new Date().toISOString();
56
20
  const reminder: Reminder = {
57
- id: generateId(),
21
+ id: generateReminderId(),
58
22
  ...input,
59
23
  enabled: true,
60
24
  createdAt: now,
@@ -4,37 +4,83 @@
4
4
  */
5
5
 
6
6
  import { createStore } from '@umituz/react-native-storage';
7
- import AsyncStorage from '@react-native-async-storage/async-storage';
8
7
  import type { Reminder, QuietHoursConfig, NotificationPreferences } from '../../../../infrastructure/services/types';
9
8
 
10
- const STORAGE_KEYS = {
11
- REMINDERS: '@notifications:reminders',
12
- PREFERENCES: '@notifications:preferences',
13
- QUIET_HOURS: '@notifications:quiet_hours',
14
- } as const;
15
-
16
9
  // ============================================================================
17
- // STORE INTERFACE
10
+ // REMINDERS STORE
18
11
  // ============================================================================
19
12
 
20
13
  interface RemindersState {
21
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 {
22
73
  preferences: NotificationPreferences;
23
74
  isLoading: boolean;
24
75
  isInitialized: boolean;
25
76
  }
26
77
 
27
- interface RemindersActions {
78
+ interface PreferencesActions {
28
79
  initialize: () => Promise<void>;
29
- loadReminders: () => Promise<void>;
30
- addReminder: (reminder: Reminder) => Promise<void>;
31
- updateReminder: (id: string, updates: Partial<Reminder>) => Promise<void>;
32
- deleteReminder: (id: string) => Promise<void>;
33
- toggleReminder: (id: string) => Promise<void>;
34
80
  loadPreferences: () => Promise<void>;
35
- updatePreferences: (updates: Partial<NotificationPreferences>) => Promise<void>;
36
- updateQuietHours: (quietHours: QuietHoursConfig) => Promise<void>;
37
- reset: () => Promise<void>;
81
+ updatePreferences: (updates: Partial<NotificationPreferences>) => void;
82
+ updateQuietHours: (quietHours: QuietHoursConfig) => void;
83
+ reset: () => void;
38
84
  }
39
85
 
40
86
  // ============================================================================
@@ -54,124 +100,63 @@ const DEFAULT_PREFERENCES: NotificationPreferences = {
54
100
  },
55
101
  };
56
102
 
57
- const initialRemindersState: RemindersState = {
58
- reminders: [],
103
+ const initialPreferencesState: PreferencesState = {
59
104
  preferences: DEFAULT_PREFERENCES,
60
105
  isLoading: true,
61
106
  isInitialized: false,
62
107
  };
63
108
 
64
- // ============================================================================
65
- // STORE IMPLEMENTATION
66
- // ============================================================================
67
-
68
- export const useRemindersStore = createStore<RemindersState, RemindersActions>({
69
- name: 'reminders-store',
70
- initialState: initialRemindersState,
71
- persist: false, // Manual persistence via AsyncStorage
109
+ export const usePreferencesStore = createStore<PreferencesState, PreferencesActions>({
110
+ name: 'preferences-store',
111
+ initialState: initialPreferencesState,
112
+ persist: true,
72
113
  actions: (set, get) => ({
73
114
  initialize: async () => {
74
- try {
75
- const [remindersData, preferencesData] = await Promise.all([
76
- AsyncStorage.getItem(STORAGE_KEYS.REMINDERS),
77
- AsyncStorage.getItem(STORAGE_KEYS.PREFERENCES)
78
- ]);
79
-
80
- const reminders = remindersData ? JSON.parse(remindersData) : [];
81
- let preferences = DEFAULT_PREFERENCES;
82
-
83
- if (preferencesData) {
84
- const parsed = JSON.parse(preferencesData);
85
- preferences = { ...DEFAULT_PREFERENCES, ...parsed };
86
- }
87
-
88
- set({ reminders, preferences, isLoading: false, isInitialized: true });
89
- } catch {
90
- set({ reminders: [], preferences: DEFAULT_PREFERENCES, isLoading: false, isInitialized: true });
91
- }
92
- },
93
-
94
- loadReminders: async () => {
95
- try {
96
- const data = await AsyncStorage.getItem(STORAGE_KEYS.REMINDERS);
97
- const reminders = data ? JSON.parse(data) : [];
98
- set({ reminders });
99
- } catch {
100
- set({ reminders: [] });
101
- }
102
- },
103
-
104
- addReminder: async (reminder: Reminder) => {
105
- const { reminders } = get();
106
- const updated = [...reminders, reminder];
107
- set({ reminders: updated });
108
- await AsyncStorage.setItem(STORAGE_KEYS.REMINDERS, JSON.stringify(updated));
109
- },
110
-
111
- updateReminder: async (id: string, updates: Partial<Reminder>) => {
112
- const { reminders } = get();
113
- const updated = reminders.map(r =>
114
- r.id === id ? { ...r, ...updates, updatedAt: new Date().toISOString() } : r
115
- );
116
- set({ reminders: updated });
117
- await AsyncStorage.setItem(STORAGE_KEYS.REMINDERS, JSON.stringify(updated));
118
- },
119
-
120
- deleteReminder: async (id: string) => {
121
- const { reminders } = get();
122
- const updated = reminders.filter(r => r.id !== id);
123
- set({ reminders: updated });
124
- await AsyncStorage.setItem(STORAGE_KEYS.REMINDERS, JSON.stringify(updated));
125
- },
126
-
127
- toggleReminder: async (id: string) => {
128
- const { reminders } = get();
129
- const updated = reminders.map(r =>
130
- r.id === id ? { ...r, enabled: !r.enabled, updatedAt: new Date().toISOString() } : r
131
- );
132
- set({ reminders: updated });
133
- await AsyncStorage.setItem(STORAGE_KEYS.REMINDERS, JSON.stringify(updated));
115
+ set({ isLoading: false, isInitialized: true });
134
116
  },
135
117
 
136
118
  loadPreferences: async () => {
137
- try {
138
- const data = await AsyncStorage.getItem(STORAGE_KEYS.PREFERENCES);
139
- const preferences = data ? { ...DEFAULT_PREFERENCES, ...JSON.parse(data) } : DEFAULT_PREFERENCES;
140
- set({ preferences });
141
- } catch {
142
- set({ preferences: DEFAULT_PREFERENCES });
143
- }
119
+ // Data loaded automatically by persist
144
120
  },
145
121
 
146
- updatePreferences: async (updates: Partial<NotificationPreferences>) => {
122
+ updatePreferences: (updates: Partial<NotificationPreferences>) => {
147
123
  const { preferences } = get();
148
- const updated = { ...preferences, ...updates };
149
- set({ preferences: updated });
150
- await AsyncStorage.setItem(STORAGE_KEYS.PREFERENCES, JSON.stringify(updated));
124
+ set({ preferences: { ...preferences, ...updates } });
151
125
  },
152
126
 
153
- updateQuietHours: async (quietHours: QuietHoursConfig) => {
127
+ updateQuietHours: (quietHours: QuietHoursConfig) => {
154
128
  const { preferences } = get();
155
- const updated = { ...preferences, quietHours };
156
- set({ preferences: updated });
157
- await AsyncStorage.setItem(STORAGE_KEYS.PREFERENCES, JSON.stringify(updated));
129
+ set({ preferences: { ...preferences, quietHours } });
158
130
  },
159
131
 
160
- reset: async () => {
161
- set({ reminders: [], preferences: DEFAULT_PREFERENCES });
162
- await AsyncStorage.multiRemove([STORAGE_KEYS.REMINDERS, STORAGE_KEYS.PREFERENCES]);
132
+ reset: () => {
133
+ set({ preferences: DEFAULT_PREFERENCES });
163
134
  },
164
135
  }),
165
136
  });
166
137
 
167
138
  // ============================================================================
168
- // SELECTOR HOOKS
139
+ // SELECTOR HOOKS - REMINDERS
169
140
  // ============================================================================
170
141
 
171
142
  export const useReminders = () => useRemindersStore(state => state.reminders);
172
143
  export const useEnabledReminders = () => useRemindersStore(state => state.reminders.filter(r => r.enabled));
173
144
  export const useReminderById = (id: string) => useRemindersStore(state => state.reminders.find(r => r.id === id));
174
- export const useNotificationPreferences = () => useRemindersStore(state => state.preferences);
175
- export const useQuietHours = () => useRemindersStore(state => state.preferences.quietHours);
176
- export const useRemindersLoading = () => useRemindersStore(state => state.isLoading);
177
- export const useRemindersInitialized = () => useRemindersStore(state => state.isInitialized);
145
+
146
+ // ============================================================================
147
+ // SELECTOR HOOKS - PREFERENCES
148
+ // ============================================================================
149
+
150
+ export const useNotificationPreferences = () => usePreferencesStore(state => state.preferences);
151
+ export const useQuietHours = () => usePreferencesStore(state => state.preferences.quietHours);
152
+ export const useRemindersLoading = () => usePreferencesStore(state => state.isLoading);
153
+ export const useRemindersInitialized = () => usePreferencesStore(state => state.isInitialized);
154
+
155
+ // ============================================================================
156
+ // LEGACY EXPORTS - DEPRECATED
157
+ // ============================================================================
158
+
159
+ /**
160
+ * @deprecated Use specific store hooks instead
161
+ */
162
+ export const useRemindersStoreLegacy = useRemindersStore;
@@ -30,13 +30,8 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
30
30
 
31
31
  const reminders = useReminders();
32
32
  const isLoading = useRemindersLoading();
33
- const { loadReminders } = useRemindersStore();
34
33
  const { toggleReminderEnabled, removeReminder } = useReminderActions();
35
34
 
36
- useEffect(() => {
37
- loadReminders();
38
- }, [loadReminders]);
39
-
40
35
  const handleToggle = useCallback(async (id: string) => {
41
36
  await toggleReminderEnabled(id);
42
37
  }, [toggleReminderEnabled]);
package/src/index.ts CHANGED
@@ -58,6 +58,7 @@ export { NotificationManager } from './infrastructure/services/NotificationManag
58
58
  export { useNotificationsStore, useNotifications } from './infrastructure/storage/NotificationsStore';
59
59
  export {
60
60
  useRemindersStore,
61
+ usePreferencesStore,
61
62
  useReminders,
62
63
  useEnabledReminders,
63
64
  useReminderById,
@@ -117,5 +118,22 @@ export type { FormButtonProps } from './domains/reminders/presentation/component
117
118
  export { QuietHoursCard } from './domains/quietHours/presentation/components/QuietHoursCard';
118
119
  export type { QuietHoursCardProps } from './domains/quietHours/presentation/components/QuietHoursCard';
119
120
 
121
+ export { RemindersNavRow } from './presentation/components/RemindersNavRow';
122
+ export type { RemindersNavRowProps } from './presentation/components/RemindersNavRow';
123
+
120
124
  export { SettingRow } from './presentation/components/SettingRow';
121
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';
@@ -1,45 +1,41 @@
1
- import { useState, useEffect } from 'react';
2
- import AsyncStorage from '@react-native-async-storage/async-storage';
3
-
4
- const STORAGE_KEY = 'notifications_enabled';
5
-
6
1
  /**
7
2
  * Simple notification settings hook
8
- * Manages a single toggle for enabling/disabling notifications
3
+ * @deprecated Use usePreferencesStore and useNotificationPreferences instead
9
4
  */
10
- export const useNotificationSettings = () => {
11
- const [notificationsEnabled, setNotificationsEnabled] = useState(true);
12
- const [isLoading, setIsLoading] = useState(true);
13
5
 
14
- useEffect(() => {
15
- const loadSettings = async () => {
16
- try {
17
- const value = await AsyncStorage.getItem(STORAGE_KEY);
18
- if (value !== null) {
19
- setNotificationsEnabled(value === 'true');
20
- }
21
- } catch (error) {
22
- // Silent failure - use default value
23
- } finally {
24
- setIsLoading(false);
25
- }
26
- };
6
+ import { createStore } from '@umituz/react-native-storage';
27
7
 
28
- loadSettings();
29
- }, []);
8
+ interface NotificationSettingsState {
9
+ notificationsEnabled: boolean;
10
+ isLoading: boolean;
11
+ }
30
12
 
31
- const toggleNotifications = async (value: boolean) => {
32
- setNotificationsEnabled(value);
33
- try {
34
- await AsyncStorage.setItem(STORAGE_KEY, value.toString());
35
- } catch (error) {
36
- // Silent failure
37
- }
38
- };
13
+ interface NotificationSettingsActions {
14
+ setNotificationsEnabled: (value: boolean) => void;
15
+ }
16
+
17
+ const useNotificationSettingsStore = createStore<NotificationSettingsState, NotificationSettingsActions>({
18
+ name: 'notification-settings-store',
19
+ initialState: {
20
+ notificationsEnabled: true,
21
+ isLoading: true,
22
+ },
23
+ persist: true,
24
+ actions: (set) => ({
25
+ setNotificationsEnabled: (value: boolean) => set({ notificationsEnabled: value }),
26
+ }),
27
+ });
28
+
29
+ /**
30
+ * @deprecated Use usePreferencesStore and useNotificationPreferences from RemindersStore instead
31
+ */
32
+ export const useNotificationSettings = () => {
33
+ const store = useNotificationSettingsStore();
34
+ const { notificationsEnabled, isLoading, setNotificationsEnabled } = store;
39
35
 
40
36
  return {
41
37
  notificationsEnabled,
42
- setNotificationsEnabled: toggleNotifications,
38
+ setNotificationsEnabled,
43
39
  isLoading,
44
40
  };
45
41
  };
@@ -12,32 +12,7 @@ import { NotificationPermissions } from './NotificationPermissions';
12
12
  import { NotificationScheduler } from './NotificationScheduler';
13
13
  import { NotificationBadgeManager } from './NotificationBadgeManager';
14
14
  import { devLog, devError } from '../utils/dev';
15
-
16
- export type NotificationTrigger =
17
- | { type: 'date'; date: Date }
18
- | { type: 'daily'; hour: number; minute: number }
19
- | { type: 'weekly'; weekday: number; hour: number; minute: number }
20
- | { type: 'monthly'; day: number; hour: number; minute: number };
21
-
22
- export interface ScheduleNotificationOptions {
23
- title: string;
24
- body: string;
25
- data?: Record<string, any>;
26
- trigger: NotificationTrigger;
27
- sound?: boolean | string;
28
- badge?: number;
29
- categoryIdentifier?: string;
30
- }
31
-
32
- export interface ScheduledNotification {
33
- identifier: string;
34
- content: {
35
- title: string;
36
- body: string;
37
- data: Record<string, any>;
38
- };
39
- trigger: any;
40
- }
15
+ import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './types';
41
16
 
42
17
  export class NotificationManager {
43
18
  private permissions: NotificationPermissions;
@@ -1,5 +1,5 @@
1
1
  import * as Notifications from 'expo-notifications';
2
- import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './NotificationManager';
2
+ import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './types';
3
3
 
4
4
  export class NotificationScheduler {
5
5
  async scheduleNotification(options: ScheduleNotificationOptions): Promise<string> {
@@ -3,6 +3,8 @@
3
3
  * Uses expo-notifications for local device notifications
4
4
  */
5
5
 
6
+ import * as Notifications from 'expo-notifications';
7
+
6
8
  // ============================================================================
7
9
  // NOTIFICATION CHANNEL TYPES
8
10
  // ============================================================================
@@ -44,7 +46,7 @@ export interface ScheduledNotification {
44
46
  body: string;
45
47
  data: Record<string, unknown>;
46
48
  };
47
- trigger: unknown;
49
+ trigger: Notifications.NotificationTrigger;
48
50
  }
49
51
 
50
52
  // ============================================================================
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ID Generator Utility
3
+ * Generates unique IDs for entities
4
+ */
5
+
6
+ export const generateId = (prefix: string = 'id'): string => {
7
+ const timestamp = Date.now();
8
+ const random = Math.random().toString(36).substring(2, 11);
9
+ return `${prefix}_${timestamp}_${random}`;
10
+ };
11
+
12
+ export const generateReminderId = (): string => {
13
+ return generateId('reminder');
14
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Trigger Builder Utility
3
+ * Builds notification triggers from reminder data
4
+ */
5
+
6
+ import type { NotificationTrigger, Reminder } from '../services/types';
7
+
8
+ export const buildTrigger = (reminder: Reminder): NotificationTrigger => {
9
+ const { frequency, hour, minute, weekday, dayOfMonth } = reminder;
10
+
11
+ switch (frequency) {
12
+ case 'once': {
13
+ const date = new Date();
14
+ date.setHours(hour, minute, 0, 0);
15
+
16
+ if (date <= new Date()) {
17
+ date.setDate(date.getDate() + 1);
18
+ }
19
+
20
+ return { type: 'date', date };
21
+ }
22
+
23
+ case 'daily':
24
+ return { type: 'daily', hour, minute };
25
+
26
+ case 'weekly':
27
+ return {
28
+ type: 'weekly',
29
+ weekday: weekday || 1,
30
+ hour,
31
+ minute,
32
+ };
33
+
34
+ case 'monthly':
35
+ return {
36
+ type: 'monthly',
37
+ day: dayOfMonth || 1,
38
+ hour,
39
+ minute,
40
+ };
41
+
42
+ default:
43
+ return { type: 'daily', hour, minute };
44
+ }
45
+ };
@@ -4,13 +4,14 @@
4
4
  */
5
5
 
6
6
  import { useEffect, useCallback } from 'react';
7
- import { useNotificationPreferences, useQuietHours, useRemindersStore } from '../../domains/reminders/infrastructure/storage/RemindersStore';
7
+ import { useNotificationPreferences, useQuietHours, usePreferencesStore, useRemindersLoading } from '../../domains/reminders/infrastructure/storage/RemindersStore';
8
8
  import { notificationService } from '../../infrastructure/services/NotificationService';
9
9
 
10
10
  export const useNotificationSettingsUI = () => {
11
11
  const preferences = useNotificationPreferences();
12
12
  const quietHours = useQuietHours();
13
- const { initialize, updatePreferences, updateQuietHours, isLoading } = useRemindersStore();
13
+ const { initialize, updatePreferences, updateQuietHours } = usePreferencesStore();
14
+ const isLoading = useRemindersLoading();
14
15
 
15
16
  useEffect(() => {
16
17
  initialize();