@umituz/react-native-notifications 1.0.5 → 1.1.0

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 (72) hide show
  1. package/lib/index.d.ts +1 -0
  2. package/lib/index.d.ts.map +1 -1
  3. package/lib/index.js +5 -0
  4. package/lib/index.js.map +1 -1
  5. package/lib/infrastructure/hooks/actions/useNotificationActions.d.ts +4 -13
  6. package/lib/infrastructure/hooks/actions/useNotificationActions.d.ts.map +1 -1
  7. package/lib/infrastructure/hooks/actions/useNotificationActions.js +4 -70
  8. package/lib/infrastructure/hooks/actions/useNotificationActions.js.map +1 -1
  9. package/lib/infrastructure/hooks/actions/useNotificationManagementActions.d.ts +8 -0
  10. package/lib/infrastructure/hooks/actions/useNotificationManagementActions.d.ts.map +1 -0
  11. package/lib/infrastructure/hooks/actions/useNotificationManagementActions.js +78 -0
  12. package/lib/infrastructure/hooks/actions/useNotificationManagementActions.js.map +1 -0
  13. package/lib/infrastructure/hooks/state/useNotificationsState.d.ts +8 -8
  14. package/lib/infrastructure/hooks/useNotificationSettings.d.ts +2 -2
  15. package/lib/infrastructure/hooks/useNotifications.d.ts +21 -1
  16. package/lib/infrastructure/hooks/useNotifications.d.ts.map +1 -1
  17. package/lib/infrastructure/hooks/useNotifications.js +30 -9
  18. package/lib/infrastructure/hooks/useNotifications.js.map +1 -1
  19. package/lib/infrastructure/hooks/utils/useNotificationRefresh.d.ts +5 -10
  20. package/lib/infrastructure/hooks/utils/useNotificationRefresh.d.ts.map +1 -1
  21. package/lib/infrastructure/hooks/utils/useNotificationRefresh.js +32 -15
  22. package/lib/infrastructure/hooks/utils/useNotificationRefresh.js.map +1 -1
  23. package/lib/infrastructure/services/NotificationBadgeManager.d.ts +5 -0
  24. package/lib/infrastructure/services/NotificationBadgeManager.d.ts.map +1 -0
  25. package/lib/infrastructure/services/NotificationBadgeManager.js +29 -0
  26. package/lib/infrastructure/services/NotificationBadgeManager.js.map +1 -0
  27. package/lib/infrastructure/services/NotificationManager.d.ts +5 -84
  28. package/lib/infrastructure/services/NotificationManager.d.ts.map +1 -1
  29. package/lib/infrastructure/services/NotificationManager.js +36 -203
  30. package/lib/infrastructure/services/NotificationManager.js.map +1 -1
  31. package/lib/infrastructure/services/NotificationPermissions.d.ts +6 -0
  32. package/lib/infrastructure/services/NotificationPermissions.d.ts.map +1 -0
  33. package/lib/infrastructure/services/NotificationPermissions.js +75 -0
  34. package/lib/infrastructure/services/NotificationPermissions.js.map +1 -0
  35. package/lib/infrastructure/services/NotificationScheduler.d.ts +8 -0
  36. package/lib/infrastructure/services/NotificationScheduler.d.ts.map +1 -0
  37. package/lib/infrastructure/services/NotificationScheduler.js +72 -0
  38. package/lib/infrastructure/services/NotificationScheduler.js.map +1 -0
  39. package/lib/infrastructure/services/delivery/NotificationDelivery.d.ts +2 -8
  40. package/lib/infrastructure/services/delivery/NotificationDelivery.d.ts.map +1 -1
  41. package/lib/infrastructure/services/delivery/NotificationDelivery.js +27 -13
  42. package/lib/infrastructure/services/delivery/NotificationDelivery.js.map +1 -1
  43. package/lib/infrastructure/storage/NotificationsStore.d.ts +8 -1
  44. package/lib/infrastructure/storage/NotificationsStore.d.ts.map +1 -1
  45. package/lib/infrastructure/storage/NotificationsStore.js +2 -1
  46. package/lib/infrastructure/storage/NotificationsStore.js.map +1 -1
  47. package/lib/infrastructure/utils/dev.d.ts +5 -0
  48. package/lib/infrastructure/utils/dev.d.ts.map +1 -0
  49. package/lib/infrastructure/utils/dev.js +24 -0
  50. package/lib/infrastructure/utils/dev.js.map +1 -0
  51. package/lib/presentation/screens/NotificationsScreen.d.ts +14 -4
  52. package/lib/presentation/screens/NotificationsScreen.d.ts.map +1 -1
  53. package/lib/presentation/screens/NotificationsScreen.js +12 -15
  54. package/lib/presentation/screens/NotificationsScreen.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/__tests__/NotificationManager.test.ts +215 -0
  57. package/src/__tests__/useNotificationActions.test.ts +189 -0
  58. package/src/__tests__/useNotificationRefresh.test.ts +213 -0
  59. package/src/index.ts +7 -0
  60. package/src/infrastructure/hooks/actions/useNotificationActions.ts +8 -110
  61. package/src/infrastructure/hooks/actions/useNotificationManagementActions.ts +131 -0
  62. package/src/infrastructure/hooks/useNotifications.ts +37 -11
  63. package/src/infrastructure/hooks/utils/useNotificationRefresh.ts +40 -16
  64. package/src/infrastructure/services/NotificationBadgeManager.ts +28 -0
  65. package/src/infrastructure/services/NotificationManager.ts +51 -217
  66. package/src/infrastructure/services/NotificationPermissions.ts +80 -0
  67. package/src/infrastructure/services/NotificationScheduler.ts +77 -0
  68. package/src/infrastructure/services/delivery/NotificationDelivery.ts +32 -14
  69. package/src/infrastructure/storage/NotificationsStore.ts +3 -2
  70. package/src/infrastructure/utils/dev.ts +25 -0
  71. package/src/presentation/screens/NotificationsScreen.tsx +31 -18
  72. package/src/types/global.d.ts +255 -0
@@ -1,42 +1,24 @@
1
1
  /**
2
- * NotificationManager
2
+ * NotificationManager - Core Notification Operations
3
3
  *
4
4
  * Offline-first notification system using expo-notifications.
5
5
  * Works in ALL apps (offline, online, hybrid) - no backend required.
6
- *
7
- * Features:
8
- * - Schedule notifications for specific dates/times
9
- * - Repeating notifications (daily, weekly, monthly)
10
- * - Android notification channels
11
- * - Permission handling
12
- * - Cancel individual or all notifications
13
- * - Works completely offline
14
- *
15
- * Use Cases:
16
- * - Reminders (bills, tasks, appointments)
17
- * - Habit tracking (daily streaks)
18
- * - Medication reminders
19
- * - Any app needing local notifications
20
- *
21
- * @module NotificationManager
22
6
  */
23
7
 
24
8
  import * as Notifications from 'expo-notifications';
25
9
  import * as Device from 'expo-device';
26
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';
27
15
 
28
- /**
29
- * Trigger types for notifications
30
- */
31
16
  export type NotificationTrigger =
32
17
  | { type: 'date'; date: Date }
33
18
  | { type: 'daily'; hour: number; minute: number }
34
19
  | { type: 'weekly'; weekday: number; hour: number; minute: number }
35
20
  | { type: 'monthly'; day: number; hour: number; minute: number };
36
21
 
37
- /**
38
- * Options for scheduling a notification
39
- */
40
22
  export interface ScheduleNotificationOptions {
41
23
  title: string;
42
24
  body: string;
@@ -47,9 +29,6 @@ export interface ScheduleNotificationOptions {
47
29
  categoryIdentifier?: string;
48
30
  }
49
31
 
50
- /**
51
- * Scheduled notification details
52
- */
53
32
  export interface ScheduledNotification {
54
33
  identifier: string;
55
34
  content: {
@@ -60,16 +39,17 @@ export interface ScheduledNotification {
60
39
  trigger: any;
61
40
  }
62
41
 
63
- /**
64
- * NotificationManager
65
- *
66
- * Handles all notification operations using expo-notifications.
67
- * Works completely offline - no backend, no user IDs, just device-local notifications.
68
- */
69
42
  export class NotificationManager {
70
- /**
71
- * Configure notification handler (how notifications appear when app is in foreground)
72
- */
43
+ private permissions: NotificationPermissions;
44
+ private scheduler: NotificationScheduler;
45
+ private badgeManager: NotificationBadgeManager;
46
+
47
+ constructor() {
48
+ this.permissions = new NotificationPermissions();
49
+ this.scheduler = new NotificationScheduler();
50
+ this.badgeManager = new NotificationBadgeManager();
51
+ }
52
+
73
53
  static configure() {
74
54
  Notifications.setNotificationHandler({
75
55
  handleNotification: async () => ({
@@ -78,250 +58,104 @@ export class NotificationManager {
78
58
  shouldSetBadge: true,
79
59
  }),
80
60
  });
61
+
62
+ devLog('[NotificationManager] Configured notification handler');
81
63
  }
82
64
 
83
- /**
84
- * Request notification permissions
85
- * iOS: Shows system permission dialog
86
- * Android: Permissions granted by default (Android 13+ requires runtime permission)
87
- */
88
65
  async requestPermissions(): Promise<boolean> {
89
66
  try {
90
- if (!Device.isDevice) {
91
- console.warn('[NotificationManager] Notifications only work on physical devices');
92
- return false;
93
- }
94
-
95
- const permissionsResponse = await Notifications.getPermissionsAsync();
96
- const existingStatus = (permissionsResponse as any).status || ((permissionsResponse as any).granted ? 'granted' : 'denied');
97
- let finalStatus = existingStatus;
98
-
99
- if (existingStatus !== 'granted') {
100
- const requestResponse = await Notifications.requestPermissionsAsync();
101
- finalStatus = (requestResponse as any).status || ((requestResponse as any).granted ? 'granted' : 'denied');
102
- }
103
-
104
- if (Platform.OS === 'android') {
105
- await this.createAndroidChannels();
106
- }
107
-
108
- return finalStatus === 'granted';
67
+ const result = await this.permissions.requestPermissions();
68
+
69
+ devLog('[NotificationManager] Permissions requested:', result);
70
+
71
+ return result;
109
72
  } catch (error) {
110
- console.error('[NotificationManager] Permission request failed:', error);
73
+ devError('[NotificationManager] Permission request failed:', error);
111
74
  return false;
112
75
  }
113
76
  }
114
77
 
115
- /**
116
- * Check if notification permissions are granted
117
- */
118
78
  async hasPermissions(): Promise<boolean> {
119
79
  try {
120
- if (!Device.isDevice) return false;
121
- const permissionsResponse = await Notifications.getPermissionsAsync();
122
- return (permissionsResponse as any).status === 'granted' || (permissionsResponse as any).granted === true;
80
+ return await this.permissions.hasPermissions();
123
81
  } catch (error) {
124
- console.error('[NotificationManager] Permission check failed:', error);
82
+ devError('[NotificationManager] Permission check failed:', error);
125
83
  return false;
126
84
  }
127
85
  }
128
86
 
129
- /**
130
- * Create Android notification channels (required for Android 8+)
131
- */
132
- private async createAndroidChannels(): Promise<void> {
133
- if (Platform.OS !== 'android') return;
134
-
135
- try {
136
- await Notifications.setNotificationChannelAsync('default', {
137
- name: 'Default',
138
- importance: Notifications.AndroidImportance.DEFAULT,
139
- vibrationPattern: [0, 250, 250, 250],
140
- sound: 'default',
141
- lightColor: '#3B82F6',
142
- });
143
-
144
- await Notifications.setNotificationChannelAsync('reminders', {
145
- name: 'Reminders',
146
- importance: Notifications.AndroidImportance.HIGH,
147
- vibrationPattern: [0, 250, 250, 250],
148
- sound: 'default',
149
- lightColor: '#3B82F6',
150
- enableVibrate: true,
151
- });
152
-
153
- await Notifications.setNotificationChannelAsync('urgent', {
154
- name: 'Urgent',
155
- importance: Notifications.AndroidImportance.MAX,
156
- vibrationPattern: [0, 500, 250, 500],
157
- sound: 'default',
158
- lightColor: '#EF4444',
159
- enableVibrate: true,
160
- });
161
- } catch (error) {
162
- console.error('[NotificationManager] Android channel creation failed:', error);
163
- }
164
- }
165
-
166
- /**
167
- * Schedule a notification
168
- *
169
- * @example
170
- * // Specific date
171
- * const id = await manager.scheduleNotification({
172
- * title: 'Bill Reminder',
173
- * body: 'Electricity bill due today',
174
- * trigger: { type: 'date', date: new Date('2025-01-15T09:00:00') }
175
- * });
176
- *
177
- * @example
178
- * // Daily reminder
179
- * const id = await manager.scheduleNotification({
180
- * title: 'Daily Workout',
181
- * body: 'Time for your morning workout!',
182
- * trigger: { type: 'daily', hour: 7, minute: 0 }
183
- * });
184
- */
185
87
  async scheduleNotification(options: ScheduleNotificationOptions): Promise<string> {
186
88
  try {
187
- const { title, body, data = {}, trigger, sound = true, badge, categoryIdentifier } = options;
188
-
189
- let notificationTrigger: any;
190
-
191
- if (trigger.type === 'date') {
192
- notificationTrigger = {
193
- date: trigger.date,
194
- channelId: categoryIdentifier || 'default',
195
- };
196
- } else if (trigger.type === 'daily') {
197
- notificationTrigger = {
198
- hour: trigger.hour,
199
- minute: trigger.minute,
200
- repeats: true,
201
- channelId: categoryIdentifier || 'reminders',
202
- };
203
- } else if (trigger.type === 'weekly') {
204
- notificationTrigger = {
205
- weekday: trigger.weekday,
206
- hour: trigger.hour,
207
- minute: trigger.minute,
208
- repeats: true,
209
- channelId: categoryIdentifier || 'reminders',
210
- };
211
- } else if (trigger.type === 'monthly') {
212
- notificationTrigger = {
213
- day: trigger.day,
214
- hour: trigger.hour,
215
- minute: trigger.minute,
216
- repeats: true,
217
- channelId: categoryIdentifier || 'reminders',
218
- };
219
- }
220
-
221
- const notificationId = await Notifications.scheduleNotificationAsync({
222
- content: {
223
- title,
224
- body,
225
- data,
226
- sound: sound === true ? 'default' : sound || undefined,
227
- badge,
228
- categoryIdentifier,
229
- priority: Notifications.AndroidNotificationPriority.HIGH,
230
- vibrate: [0, 250, 250, 250],
231
- },
232
- trigger: notificationTrigger,
233
- });
234
-
235
- return notificationId;
89
+ const id = await this.scheduler.scheduleNotification(options);
90
+
91
+ devLog('[NotificationManager] Notification scheduled:', id);
92
+
93
+ return id;
236
94
  } catch (error) {
237
- console.error('[NotificationManager] Schedule notification failed:', error);
95
+ devError('[NotificationManager] Schedule notification failed:', error);
238
96
  throw error;
239
97
  }
240
98
  }
241
99
 
242
- /**
243
- * Cancel a scheduled notification
244
- */
245
100
  async cancelNotification(notificationId: string): Promise<void> {
246
101
  try {
247
- await Notifications.cancelScheduledNotificationAsync(notificationId);
102
+ await this.scheduler.cancelNotification(notificationId);
103
+
104
+ devLog('[NotificationManager] Notification cancelled:', notificationId);
248
105
  } catch (error) {
249
- console.error('[NotificationManager] Cancel notification failed:', error);
106
+ devError('[NotificationManager] Cancel notification failed:', error);
250
107
  throw error;
251
108
  }
252
109
  }
253
110
 
254
- /**
255
- * Cancel all scheduled notifications
256
- */
257
111
  async cancelAllNotifications(): Promise<void> {
258
112
  try {
259
- await Notifications.cancelAllScheduledNotificationsAsync();
113
+ await this.scheduler.cancelAllNotifications();
114
+
115
+ devLog('[NotificationManager] All notifications cancelled');
260
116
  } catch (error) {
261
- console.error('[NotificationManager] Cancel all notifications failed:', error);
117
+ devError('[NotificationManager] Cancel all notifications failed:', error);
262
118
  throw error;
263
119
  }
264
120
  }
265
121
 
266
- /**
267
- * Get all scheduled notifications
268
- */
269
122
  async getScheduledNotifications(): Promise<ScheduledNotification[]> {
270
123
  try {
271
- const notifications = await Notifications.getAllScheduledNotificationsAsync();
272
- return notifications.map(notification => ({
273
- identifier: notification.identifier,
274
- content: {
275
- title: notification.content.title || '',
276
- body: notification.content.body || '',
277
- data: notification.content.data as Record<string, any>,
278
- },
279
- trigger: notification.trigger,
280
- }));
124
+ return await this.scheduler.getScheduledNotifications();
281
125
  } catch (error) {
282
- console.error('[NotificationManager] Get scheduled notifications failed:', error);
126
+ devError('[NotificationManager] Get scheduled notifications failed:', error);
283
127
  return [];
284
128
  }
285
129
  }
286
130
 
287
- /**
288
- * Dismiss all delivered notifications (clear from notification center)
289
- */
290
131
  async dismissAllNotifications(): Promise<void> {
291
132
  try {
292
133
  await Notifications.dismissAllNotificationsAsync();
134
+
135
+ devLog('[NotificationManager] All notifications dismissed');
293
136
  } catch (error) {
294
- console.error('[NotificationManager] Dismiss all notifications failed:', error);
137
+ devError('[NotificationManager] Dismiss all notifications failed:', error);
295
138
  throw error;
296
139
  }
297
140
  }
298
141
 
299
- /**
300
- * Get notification badge count (iOS only)
301
- */
302
142
  async getBadgeCount(): Promise<number> {
303
143
  try {
304
- if (Platform.OS === 'ios') {
305
- return await Notifications.getBadgeCountAsync();
306
- }
307
- return 0;
144
+ return await this.badgeManager.getBadgeCount();
308
145
  } catch (error) {
309
- console.error('[NotificationManager] Get badge count failed:', error);
146
+ devError('[NotificationManager] Get badge count failed:', error);
310
147
  return 0;
311
148
  }
312
149
  }
313
150
 
314
- /**
315
- * Set notification badge count (iOS only)
316
- */
317
151
  async setBadgeCount(count: number): Promise<void> {
318
152
  try {
319
- if (Platform.OS === 'ios') {
320
- await Notifications.setBadgeCountAsync(count);
321
- }
153
+ await this.badgeManager.setBadgeCount(count);
154
+
155
+ devLog('[NotificationManager] Badge count set:', count);
322
156
  } catch (error) {
323
- console.error('[NotificationManager] Set badge count failed:', error);
157
+ devError('[NotificationManager] Set badge count failed:', error);
324
158
  throw error;
325
159
  }
326
160
  }
327
- }
161
+ }
@@ -0,0 +1,80 @@
1
+ import * as Notifications from 'expo-notifications';
2
+ import * as Device from 'expo-device';
3
+ import { Platform } from 'react-native';
4
+ import { devWarn, devError, devLog } from '../utils/dev';
5
+
6
+ export class NotificationPermissions {
7
+ async requestPermissions(): Promise<boolean> {
8
+ try {
9
+ if (!Device.isDevice) {
10
+ devWarn('[NotificationPermissions] Notifications only work on physical devices');
11
+ return false;
12
+ }
13
+
14
+ const permissionsResponse = await Notifications.getPermissionsAsync();
15
+ const existingStatus = (permissionsResponse as any).status || ((permissionsResponse as any).granted ? 'granted' : 'denied');
16
+ let finalStatus = existingStatus;
17
+
18
+ if (existingStatus !== 'granted') {
19
+ const requestResponse = await Notifications.requestPermissionsAsync();
20
+ finalStatus = (requestResponse as any).status || ((requestResponse as any).granted ? 'granted' : 'denied');
21
+ }
22
+
23
+ if (Platform.OS === 'android') {
24
+ await this.createAndroidChannels();
25
+ }
26
+
27
+ return finalStatus === 'granted';
28
+ } catch (error) {
29
+ devError('[NotificationPermissions] Permission request failed:', error);
30
+ return false;
31
+ }
32
+ }
33
+
34
+ async hasPermissions(): Promise<boolean> {
35
+ try {
36
+ if (!Device.isDevice) return false;
37
+ const permissionsResponse = await Notifications.getPermissionsAsync();
38
+ return (permissionsResponse as any).status === 'granted' || (permissionsResponse as any).granted === true;
39
+ } catch (error) {
40
+ devError('[NotificationPermissions] Permission check failed:', error);
41
+ return false;
42
+ }
43
+ }
44
+
45
+ private async createAndroidChannels(): Promise<void> {
46
+ if (Platform.OS !== 'android') return;
47
+
48
+ try {
49
+ await Notifications.setNotificationChannelAsync('default', {
50
+ name: 'Default',
51
+ importance: Notifications.AndroidImportance.DEFAULT,
52
+ vibrationPattern: [0, 250, 250, 250],
53
+ sound: 'default',
54
+ lightColor: '#3B82F6',
55
+ });
56
+
57
+ await Notifications.setNotificationChannelAsync('reminders', {
58
+ name: 'Reminders',
59
+ importance: Notifications.AndroidImportance.HIGH,
60
+ vibrationPattern: [0, 250, 250, 250],
61
+ sound: 'default',
62
+ lightColor: '#3B82F6',
63
+ enableVibrate: true,
64
+ });
65
+
66
+ await Notifications.setNotificationChannelAsync('urgent', {
67
+ name: 'Urgent',
68
+ importance: Notifications.AndroidImportance.MAX,
69
+ vibrationPattern: [0, 500, 250, 500],
70
+ sound: 'default',
71
+ lightColor: '#EF4444',
72
+ enableVibrate: true,
73
+ });
74
+
75
+ devLog('[NotificationPermissions] Android channels created');
76
+ } catch (error) {
77
+ devError('[NotificationPermissions] Android channel creation failed:', error);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,77 @@
1
+ import * as Notifications from 'expo-notifications';
2
+ import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './NotificationManager';
3
+
4
+ export class NotificationScheduler {
5
+ async scheduleNotification(options: ScheduleNotificationOptions): Promise<string> {
6
+ const { title, body, data = {}, trigger, sound = true, badge, categoryIdentifier } = options;
7
+
8
+ let notificationTrigger: any;
9
+
10
+ if (trigger.type === 'date') {
11
+ notificationTrigger = {
12
+ date: trigger.date,
13
+ channelId: categoryIdentifier || 'default',
14
+ };
15
+ } else if (trigger.type === 'daily') {
16
+ notificationTrigger = {
17
+ hour: trigger.hour,
18
+ minute: trigger.minute,
19
+ repeats: true,
20
+ channelId: categoryIdentifier || 'reminders',
21
+ };
22
+ } else if (trigger.type === 'weekly') {
23
+ notificationTrigger = {
24
+ weekday: trigger.weekday,
25
+ hour: trigger.hour,
26
+ minute: trigger.minute,
27
+ repeats: true,
28
+ channelId: categoryIdentifier || 'reminders',
29
+ };
30
+ } else if (trigger.type === 'monthly') {
31
+ notificationTrigger = {
32
+ day: trigger.day,
33
+ hour: trigger.hour,
34
+ minute: trigger.minute,
35
+ repeats: true,
36
+ channelId: categoryIdentifier || 'reminders',
37
+ };
38
+ }
39
+
40
+ const notificationId = await Notifications.scheduleNotificationAsync({
41
+ content: {
42
+ title,
43
+ body,
44
+ data,
45
+ sound: sound === true ? 'default' : sound || undefined,
46
+ badge,
47
+ categoryIdentifier,
48
+ priority: Notifications.AndroidNotificationPriority.HIGH,
49
+ vibrate: [0, 250, 250, 250],
50
+ },
51
+ trigger: notificationTrigger,
52
+ });
53
+
54
+ return notificationId;
55
+ }
56
+
57
+ async cancelNotification(notificationId: string): Promise<void> {
58
+ await Notifications.cancelScheduledNotificationAsync(notificationId);
59
+ }
60
+
61
+ async cancelAllNotifications(): Promise<void> {
62
+ await Notifications.cancelAllScheduledNotificationsAsync();
63
+ }
64
+
65
+ async getScheduledNotifications(): Promise<ScheduledNotification[]> {
66
+ const notifications = await Notifications.getAllScheduledNotificationsAsync();
67
+ return notifications.map(notification => ({
68
+ identifier: notification.identifier,
69
+ content: {
70
+ title: notification.content.title || '',
71
+ body: notification.content.body || '',
72
+ data: notification.content.data as Record<string, any>,
73
+ },
74
+ trigger: notification.trigger,
75
+ }));
76
+ }
77
+ }
@@ -1,21 +1,13 @@
1
1
  import * as Notifications from 'expo-notifications';
2
2
  import AsyncStorage from '@react-native-async-storage/async-storage';
3
3
  import type { Notification } from '../types';
4
+ import { devLog, devError } from '../../utils/dev';
4
5
 
5
- /**
6
- * NotificationDelivery - Offline Local Notification Delivery
7
- *
8
- * Uses expo-notifications for local push notifications only.
9
- * All data stored in AsyncStorage (offline-capable).
10
- *
11
- * NO backend, NO email/SMS - pure offline local notifications.
12
- */
13
6
  export class NotificationDelivery {
14
7
  private static STORAGE_KEY = '@notifications:delivered';
15
8
 
16
9
  async deliver(notification: Notification): Promise<void> {
17
10
  try {
18
- // Schedule local notification using expo-notifications
19
11
  await Notifications.scheduleNotificationAsync({
20
12
  content: {
21
13
  title: notification.title,
@@ -24,14 +16,17 @@ export class NotificationDelivery {
24
16
  },
25
17
  trigger: notification.scheduled_for
26
18
  ? { date: new Date(notification.scheduled_for) }
27
- : null, // null = immediate delivery
19
+ : null,
28
20
  });
29
21
 
30
- // Update status in AsyncStorage (offline storage)
31
22
  await this.updateStatus(notification.id, 'delivered');
23
+
24
+ devLog('[NotificationDelivery] Notification delivered:', notification.id);
32
25
  } catch (error) {
33
- // Silent failure - update status to failed
34
26
  await this.updateStatus(notification.id, 'failed');
27
+
28
+ devError('[NotificationDelivery] Delivery failed:', notification.id, error);
29
+ throw error;
35
30
  }
36
31
  }
37
32
 
@@ -49,8 +44,10 @@ export class NotificationDelivery {
49
44
  NotificationDelivery.STORAGE_KEY,
50
45
  JSON.stringify(delivered)
51
46
  );
47
+
48
+ devLog('[NotificationDelivery] Status updated:', notificationId, status);
52
49
  } catch (error) {
53
- // Silent failure
50
+ devError('[NotificationDelivery] Status update failed:', notificationId, error);
54
51
  }
55
52
  }
56
53
 
@@ -59,7 +56,28 @@ export class NotificationDelivery {
59
56
  const data = await AsyncStorage.getItem(NotificationDelivery.STORAGE_KEY);
60
57
  return data ? JSON.parse(data) : {};
61
58
  } catch (error) {
59
+ devError('[NotificationDelivery] Failed to get delivered notifications:', error);
62
60
  return {};
63
61
  }
64
62
  }
65
- }
63
+
64
+ async getDeliveryStatus(notificationId: string): Promise<string | null> {
65
+ try {
66
+ const delivered = await this.getDelivered();
67
+ return delivered[notificationId]?.status || null;
68
+ } catch (error) {
69
+ devError('[NotificationDelivery] Failed to get delivery status:', notificationId, error);
70
+ return null;
71
+ }
72
+ }
73
+
74
+ async clearDeliveryHistory(): Promise<void> {
75
+ try {
76
+ await AsyncStorage.removeItem(NotificationDelivery.STORAGE_KEY);
77
+
78
+ devLog('[NotificationDelivery] Delivery history cleared');
79
+ } catch (error) {
80
+ devError('[NotificationDelivery] Failed to clear delivery history:', error);
81
+ }
82
+ }
83
+ }
@@ -28,7 +28,8 @@ export const useNotificationsStore = create<NotificationsStore>((set) => ({
28
28
  * Hook for accessing notifications state
29
29
  */
30
30
  export const useNotifications = () => {
31
- const { hasPermissions, isInitialized, setPermissions, setInitialized } = useNotificationsStore();
31
+ const store = useNotificationsStore();
32
+ const { hasPermissions, isInitialized, setPermissions, setInitialized } = store;
32
33
 
33
34
  return {
34
35
  hasPermissions,
@@ -36,4 +37,4 @@ export const useNotifications = () => {
36
37
  setPermissions,
37
38
  setInitialized,
38
39
  };
39
- };
40
+ };
@@ -0,0 +1,25 @@
1
+ export const isDev = () => {
2
+ try {
3
+ return typeof __DEV__ !== 'undefined' && __DEV__;
4
+ } catch {
5
+ return false;
6
+ }
7
+ };
8
+
9
+ export const devLog = (message: string, ...args: any[]) => {
10
+ if (isDev()) {
11
+ console.log(message, ...args);
12
+ }
13
+ };
14
+
15
+ export const devError = (message: string, ...args: any[]) => {
16
+ if (isDev()) {
17
+ console.error(message, ...args);
18
+ }
19
+ };
20
+
21
+ export const devWarn = (message: string, ...args: any[]) => {
22
+ if (isDev()) {
23
+ console.warn(message, ...args);
24
+ }
25
+ };