@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.
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/hooks/actions/useNotificationActions.d.ts +4 -13
- package/lib/infrastructure/hooks/actions/useNotificationActions.d.ts.map +1 -1
- package/lib/infrastructure/hooks/actions/useNotificationActions.js +4 -70
- package/lib/infrastructure/hooks/actions/useNotificationActions.js.map +1 -1
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.d.ts +8 -0
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.d.ts.map +1 -0
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.js +78 -0
- package/lib/infrastructure/hooks/actions/useNotificationManagementActions.js.map +1 -0
- package/lib/infrastructure/hooks/state/useNotificationsState.d.ts +8 -8
- package/lib/infrastructure/hooks/useNotificationSettings.d.ts +2 -2
- package/lib/infrastructure/hooks/useNotifications.d.ts +21 -1
- package/lib/infrastructure/hooks/useNotifications.d.ts.map +1 -1
- package/lib/infrastructure/hooks/useNotifications.js +30 -9
- package/lib/infrastructure/hooks/useNotifications.js.map +1 -1
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.d.ts +5 -10
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.d.ts.map +1 -1
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.js +32 -15
- package/lib/infrastructure/hooks/utils/useNotificationRefresh.js.map +1 -1
- package/lib/infrastructure/services/NotificationBadgeManager.d.ts +5 -0
- package/lib/infrastructure/services/NotificationBadgeManager.d.ts.map +1 -0
- package/lib/infrastructure/services/NotificationBadgeManager.js +29 -0
- package/lib/infrastructure/services/NotificationBadgeManager.js.map +1 -0
- package/lib/infrastructure/services/NotificationManager.d.ts +5 -84
- package/lib/infrastructure/services/NotificationManager.d.ts.map +1 -1
- package/lib/infrastructure/services/NotificationManager.js +36 -203
- package/lib/infrastructure/services/NotificationManager.js.map +1 -1
- package/lib/infrastructure/services/NotificationPermissions.d.ts +6 -0
- package/lib/infrastructure/services/NotificationPermissions.d.ts.map +1 -0
- package/lib/infrastructure/services/NotificationPermissions.js +75 -0
- package/lib/infrastructure/services/NotificationPermissions.js.map +1 -0
- package/lib/infrastructure/services/NotificationScheduler.d.ts +8 -0
- package/lib/infrastructure/services/NotificationScheduler.d.ts.map +1 -0
- package/lib/infrastructure/services/NotificationScheduler.js +72 -0
- package/lib/infrastructure/services/NotificationScheduler.js.map +1 -0
- package/lib/infrastructure/services/delivery/NotificationDelivery.d.ts +2 -8
- package/lib/infrastructure/services/delivery/NotificationDelivery.d.ts.map +1 -1
- package/lib/infrastructure/services/delivery/NotificationDelivery.js +27 -13
- package/lib/infrastructure/services/delivery/NotificationDelivery.js.map +1 -1
- package/lib/infrastructure/storage/NotificationsStore.d.ts +8 -1
- package/lib/infrastructure/storage/NotificationsStore.d.ts.map +1 -1
- package/lib/infrastructure/storage/NotificationsStore.js +2 -1
- package/lib/infrastructure/storage/NotificationsStore.js.map +1 -1
- package/lib/infrastructure/utils/dev.d.ts +5 -0
- package/lib/infrastructure/utils/dev.d.ts.map +1 -0
- package/lib/infrastructure/utils/dev.js +24 -0
- package/lib/infrastructure/utils/dev.js.map +1 -0
- package/lib/presentation/screens/NotificationsScreen.d.ts +14 -4
- package/lib/presentation/screens/NotificationsScreen.d.ts.map +1 -1
- package/lib/presentation/screens/NotificationsScreen.js +12 -15
- package/lib/presentation/screens/NotificationsScreen.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/NotificationManager.test.ts +215 -0
- package/src/__tests__/useNotificationActions.test.ts +189 -0
- package/src/__tests__/useNotificationRefresh.test.ts +213 -0
- package/src/index.ts +7 -0
- package/src/infrastructure/hooks/actions/useNotificationActions.ts +8 -110
- package/src/infrastructure/hooks/actions/useNotificationManagementActions.ts +131 -0
- package/src/infrastructure/hooks/useNotifications.ts +37 -11
- package/src/infrastructure/hooks/utils/useNotificationRefresh.ts +40 -16
- package/src/infrastructure/services/NotificationBadgeManager.ts +28 -0
- package/src/infrastructure/services/NotificationManager.ts +51 -217
- package/src/infrastructure/services/NotificationPermissions.ts +80 -0
- package/src/infrastructure/services/NotificationScheduler.ts +77 -0
- package/src/infrastructure/services/delivery/NotificationDelivery.ts +32 -14
- package/src/infrastructure/storage/NotificationsStore.ts +3 -2
- package/src/infrastructure/utils/dev.ts +25 -0
- package/src/presentation/screens/NotificationsScreen.tsx +31 -18
- package/src/types/global.d.ts +255 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as Notifications from 'expo-notifications';
|
|
2
|
+
export class NotificationScheduler {
|
|
3
|
+
async scheduleNotification(options) {
|
|
4
|
+
const { title, body, data = {}, trigger, sound = true, badge, categoryIdentifier } = options;
|
|
5
|
+
let notificationTrigger;
|
|
6
|
+
if (trigger.type === 'date') {
|
|
7
|
+
notificationTrigger = {
|
|
8
|
+
date: trigger.date,
|
|
9
|
+
channelId: categoryIdentifier || 'default',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
else if (trigger.type === 'daily') {
|
|
13
|
+
notificationTrigger = {
|
|
14
|
+
hour: trigger.hour,
|
|
15
|
+
minute: trigger.minute,
|
|
16
|
+
repeats: true,
|
|
17
|
+
channelId: categoryIdentifier || 'reminders',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
else if (trigger.type === 'weekly') {
|
|
21
|
+
notificationTrigger = {
|
|
22
|
+
weekday: trigger.weekday,
|
|
23
|
+
hour: trigger.hour,
|
|
24
|
+
minute: trigger.minute,
|
|
25
|
+
repeats: true,
|
|
26
|
+
channelId: categoryIdentifier || 'reminders',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
else if (trigger.type === 'monthly') {
|
|
30
|
+
notificationTrigger = {
|
|
31
|
+
day: trigger.day,
|
|
32
|
+
hour: trigger.hour,
|
|
33
|
+
minute: trigger.minute,
|
|
34
|
+
repeats: true,
|
|
35
|
+
channelId: categoryIdentifier || 'reminders',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const notificationId = await Notifications.scheduleNotificationAsync({
|
|
39
|
+
content: {
|
|
40
|
+
title,
|
|
41
|
+
body,
|
|
42
|
+
data,
|
|
43
|
+
sound: sound === true ? 'default' : sound || undefined,
|
|
44
|
+
badge,
|
|
45
|
+
categoryIdentifier,
|
|
46
|
+
priority: Notifications.AndroidNotificationPriority.HIGH,
|
|
47
|
+
vibrate: [0, 250, 250, 250],
|
|
48
|
+
},
|
|
49
|
+
trigger: notificationTrigger,
|
|
50
|
+
});
|
|
51
|
+
return notificationId;
|
|
52
|
+
}
|
|
53
|
+
async cancelNotification(notificationId) {
|
|
54
|
+
await Notifications.cancelScheduledNotificationAsync(notificationId);
|
|
55
|
+
}
|
|
56
|
+
async cancelAllNotifications() {
|
|
57
|
+
await Notifications.cancelAllScheduledNotificationsAsync();
|
|
58
|
+
}
|
|
59
|
+
async getScheduledNotifications() {
|
|
60
|
+
const notifications = await Notifications.getAllScheduledNotificationsAsync();
|
|
61
|
+
return notifications.map(notification => ({
|
|
62
|
+
identifier: notification.identifier,
|
|
63
|
+
content: {
|
|
64
|
+
title: notification.content.title || '',
|
|
65
|
+
body: notification.content.body || '',
|
|
66
|
+
data: notification.content.data,
|
|
67
|
+
},
|
|
68
|
+
trigger: notification.trigger,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=NotificationScheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationScheduler.js","sourceRoot":"","sources":["../../../src/infrastructure/services/NotificationScheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import type { Notification } from '../types';
|
|
2
|
-
/**
|
|
3
|
-
* NotificationDelivery - Offline Local Notification Delivery
|
|
4
|
-
*
|
|
5
|
-
* Uses expo-notifications for local push notifications only.
|
|
6
|
-
* All data stored in AsyncStorage (offline-capable).
|
|
7
|
-
*
|
|
8
|
-
* NO backend, NO email/SMS - pure offline local notifications.
|
|
9
|
-
*/
|
|
10
2
|
export declare class NotificationDelivery {
|
|
11
3
|
private static STORAGE_KEY;
|
|
12
4
|
deliver(notification: Notification): Promise<void>;
|
|
13
5
|
private updateStatus;
|
|
14
6
|
private getDelivered;
|
|
7
|
+
getDeliveryStatus(notificationId: string): Promise<string | null>;
|
|
8
|
+
clearDeliveryHistory(): Promise<void>;
|
|
15
9
|
}
|
|
16
10
|
//# sourceMappingURL=NotificationDelivery.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationDelivery.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/delivery/NotificationDelivery.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"NotificationDelivery.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/delivery/NotificationDelivery.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAC,WAAW,CAA8B;IAElD,OAAO,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAwB1C,YAAY;YAqBZ,YAAY;IAUpB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUjE,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5C"}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import * as Notifications from 'expo-notifications';
|
|
2
2
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
|
-
|
|
4
|
-
* NotificationDelivery - Offline Local Notification Delivery
|
|
5
|
-
*
|
|
6
|
-
* Uses expo-notifications for local push notifications only.
|
|
7
|
-
* All data stored in AsyncStorage (offline-capable).
|
|
8
|
-
*
|
|
9
|
-
* NO backend, NO email/SMS - pure offline local notifications.
|
|
10
|
-
*/
|
|
3
|
+
import { devLog, devError } from '../../utils/dev';
|
|
11
4
|
export class NotificationDelivery {
|
|
12
5
|
async deliver(notification) {
|
|
13
6
|
try {
|
|
14
|
-
// Schedule local notification using expo-notifications
|
|
15
7
|
await Notifications.scheduleNotificationAsync({
|
|
16
8
|
content: {
|
|
17
9
|
title: notification.title,
|
|
@@ -20,14 +12,15 @@ export class NotificationDelivery {
|
|
|
20
12
|
},
|
|
21
13
|
trigger: notification.scheduled_for
|
|
22
14
|
? { date: new Date(notification.scheduled_for) }
|
|
23
|
-
: null,
|
|
15
|
+
: null,
|
|
24
16
|
});
|
|
25
|
-
// Update status in AsyncStorage (offline storage)
|
|
26
17
|
await this.updateStatus(notification.id, 'delivered');
|
|
18
|
+
devLog('[NotificationDelivery] Notification delivered:', notification.id);
|
|
27
19
|
}
|
|
28
20
|
catch (error) {
|
|
29
|
-
// Silent failure - update status to failed
|
|
30
21
|
await this.updateStatus(notification.id, 'failed');
|
|
22
|
+
devError('[NotificationDelivery] Delivery failed:', notification.id, error);
|
|
23
|
+
throw error;
|
|
31
24
|
}
|
|
32
25
|
}
|
|
33
26
|
async updateStatus(notificationId, status) {
|
|
@@ -38,9 +31,10 @@ export class NotificationDelivery {
|
|
|
38
31
|
delivered_at: new Date().toISOString(),
|
|
39
32
|
};
|
|
40
33
|
await AsyncStorage.setItem(NotificationDelivery.STORAGE_KEY, JSON.stringify(delivered));
|
|
34
|
+
devLog('[NotificationDelivery] Status updated:', notificationId, status);
|
|
41
35
|
}
|
|
42
36
|
catch (error) {
|
|
43
|
-
|
|
37
|
+
devError('[NotificationDelivery] Status update failed:', notificationId, error);
|
|
44
38
|
}
|
|
45
39
|
}
|
|
46
40
|
async getDelivered() {
|
|
@@ -49,9 +43,29 @@ export class NotificationDelivery {
|
|
|
49
43
|
return data ? JSON.parse(data) : {};
|
|
50
44
|
}
|
|
51
45
|
catch (error) {
|
|
46
|
+
devError('[NotificationDelivery] Failed to get delivered notifications:', error);
|
|
52
47
|
return {};
|
|
53
48
|
}
|
|
54
49
|
}
|
|
50
|
+
async getDeliveryStatus(notificationId) {
|
|
51
|
+
try {
|
|
52
|
+
const delivered = await this.getDelivered();
|
|
53
|
+
return delivered[notificationId]?.status || null;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
devError('[NotificationDelivery] Failed to get delivery status:', notificationId, error);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async clearDeliveryHistory() {
|
|
61
|
+
try {
|
|
62
|
+
await AsyncStorage.removeItem(NotificationDelivery.STORAGE_KEY);
|
|
63
|
+
devLog('[NotificationDelivery] Delivery history cleared');
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
devError('[NotificationDelivery] Failed to clear delivery history:', error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
55
69
|
}
|
|
56
70
|
NotificationDelivery.STORAGE_KEY = '@notifications:delivered';
|
|
57
71
|
//# sourceMappingURL=NotificationDelivery.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationDelivery.js","sourceRoot":"","sources":["../../../../src/infrastructure/services/delivery/NotificationDelivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAC;AACpD,OAAO,YAAY,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"NotificationDelivery.js","sourceRoot":"","sources":["../../../../src/infrastructure/services/delivery/NotificationDelivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* Simple offline-first notification state
|
|
4
4
|
* NO backend, NO user IDs, NO notification history
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
interface NotificationsStore {
|
|
7
|
+
hasPermissions: boolean;
|
|
8
|
+
isInitialized: boolean;
|
|
9
|
+
setPermissions: (granted: boolean) => void;
|
|
10
|
+
setInitialized: (initialized: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const useNotificationsStore: import("zustand").StoreApi<NotificationsStore> & ((selector?: ((state: NotificationsStore) => any) | undefined) => any);
|
|
7
13
|
/**
|
|
8
14
|
* Hook for accessing notifications state
|
|
9
15
|
*/
|
|
@@ -13,4 +19,5 @@ export declare const useNotifications: () => {
|
|
|
13
19
|
setPermissions: any;
|
|
14
20
|
setInitialized: any;
|
|
15
21
|
};
|
|
22
|
+
export {};
|
|
16
23
|
//# sourceMappingURL=NotificationsStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationsStore.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/storage/NotificationsStore.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"NotificationsStore.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/storage/NotificationsStore.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,UAAU,kBAAkB;IAE1B,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IAGvB,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,cAAc,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD;AAED,eAAO,MAAM,qBAAqB,yHAM/B,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;CAU5B,CAAC"}
|
|
@@ -14,7 +14,8 @@ export const useNotificationsStore = create((set) => ({
|
|
|
14
14
|
* Hook for accessing notifications state
|
|
15
15
|
*/
|
|
16
16
|
export const useNotifications = () => {
|
|
17
|
-
const
|
|
17
|
+
const store = useNotificationsStore();
|
|
18
|
+
const { hasPermissions, isInitialized, setPermissions, setInitialized } = store;
|
|
18
19
|
return {
|
|
19
20
|
hasPermissions,
|
|
20
21
|
isInitialized,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationsStore.js","sourceRoot":"","sources":["../../../src/infrastructure/storage/NotificationsStore.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAYjC,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAqB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACxE,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;IAEpB,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;IAC7D,cAAc,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;CACrE,CAAC,CAAC,CAAC;AAEJ;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"NotificationsStore.js","sourceRoot":"","sources":["../../../src/infrastructure/storage/NotificationsStore.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAYjC,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAqB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACxE,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;IAEpB,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;IAC7D,cAAc,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;CACrE,CAAC,CAAC,CAAC;AAEJ;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAC;IACtC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC;IAEhF,OAAO;QACL,cAAc;QACd,aAAa;QACb,cAAc;QACd,cAAc;KACf,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const isDev: () => boolean;
|
|
2
|
+
export declare const devLog: (message: string, ...args: any[]) => void;
|
|
3
|
+
export declare const devError: (message: string, ...args: any[]) => void;
|
|
4
|
+
export declare const devWarn: (message: string, ...args: any[]) => void;
|
|
5
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/utils/dev.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,eAMjB,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,EAAE,SAIrD,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,EAAE,SAIvD,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,EAAE,SAItD,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const isDev = () => {
|
|
2
|
+
try {
|
|
3
|
+
return typeof __DEV__ !== 'undefined' && __DEV__;
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
export const devLog = (message, ...args) => {
|
|
10
|
+
if (isDev()) {
|
|
11
|
+
console.log(message, ...args);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const devError = (message, ...args) => {
|
|
15
|
+
if (isDev()) {
|
|
16
|
+
console.error(message, ...args);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const devWarn = (message, ...args) => {
|
|
20
|
+
if (isDev()) {
|
|
21
|
+
console.warn(message, ...args);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../../src/infrastructure/utils/dev.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,EAAE;IACxB,IAAI,CAAC;QACH,OAAO,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAE,GAAG,IAAW,EAAE,EAAE;IACxD,IAAI,KAAK,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IAChC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,GAAG,IAAW,EAAE,EAAE;IAC1D,IAAI,KAAK,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IAClC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,GAAG,IAAW,EAAE,EAAE;IACzD,IAAI,KAAK,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACjC,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Notifications Screen -
|
|
2
|
+
* Notifications Screen - Dynamic and Reusable
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* A clean notification settings screen that accepts all text and configuration
|
|
5
|
+
* as props to make it completely reusable across different applications.
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react';
|
|
8
|
-
export
|
|
8
|
+
export interface NotificationsScreenProps {
|
|
9
|
+
translations: {
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
loadingText?: string;
|
|
13
|
+
};
|
|
14
|
+
iconName?: string;
|
|
15
|
+
iconColor?: string;
|
|
16
|
+
testID?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const NotificationsScreen: React.FC<NotificationsScreenProps>;
|
|
9
19
|
export default NotificationsScreen;
|
|
10
20
|
//# sourceMappingURL=NotificationsScreen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationsScreen.d.ts","sourceRoot":"","sources":["../../../src/presentation/screens/NotificationsScreen.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"NotificationsScreen.d.ts","sourceRoot":"","sources":["../../../src/presentation/screens/NotificationsScreen.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAkB,MAAM,OAAO,CAAC;AAOvC,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAkDlE,CAAC;AA+BF,eAAe,mBAAmB,CAAC"}
|
|
@@ -1,43 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Notifications Screen -
|
|
2
|
+
* Notifications Screen - Dynamic and Reusable
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* A clean notification settings screen that accepts all text and configuration
|
|
5
|
+
* as props to make it completely reusable across different applications.
|
|
6
6
|
*/
|
|
7
7
|
import React, { useMemo } from 'react';
|
|
8
8
|
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
|
9
9
|
import { AtomicIcon, AtomicSwitch, AtomicCard, AtomicText, ScreenLayout, STATIC_TOKENS } from '@umituz/react-native-design-system';
|
|
10
10
|
import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
|
|
11
11
|
import { useNotificationSettings } from '../../infrastructure/hooks/useNotificationSettings';
|
|
12
|
-
|
|
13
|
-
// This is a placeholder - apps should wrap this component with their i18n provider
|
|
14
|
-
const t = (key) => {
|
|
15
|
-
// Return key as fallback - apps should provide translation function
|
|
16
|
-
return key;
|
|
17
|
-
};
|
|
18
|
-
export const NotificationsScreen = () => {
|
|
12
|
+
export const NotificationsScreen = ({ translations, iconName = 'Bell', iconColor = 'primary', testID = 'notifications-screen', }) => {
|
|
19
13
|
const tokens = useAppDesignTokens();
|
|
20
14
|
const styles = useMemo(() => getStyles(tokens), [tokens]);
|
|
21
15
|
const { notificationsEnabled, setNotificationsEnabled, isLoading } = useNotificationSettings();
|
|
22
16
|
if (isLoading) {
|
|
23
|
-
return (<ScreenLayout testID=
|
|
17
|
+
return (<ScreenLayout testID={testID}>
|
|
24
18
|
<View style={styles.loadingContainer}>
|
|
25
19
|
<ActivityIndicator size="large" color={tokens.colors.primary}/>
|
|
20
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary, marginTop: STATIC_TOKENS.spacing.md }}>
|
|
21
|
+
{translations.loadingText || 'Loading...'}
|
|
22
|
+
</AtomicText>
|
|
26
23
|
</View>
|
|
27
24
|
</ScreenLayout>);
|
|
28
25
|
}
|
|
29
|
-
return (<ScreenLayout testID=
|
|
26
|
+
return (<ScreenLayout testID={testID} hideScrollIndicator>
|
|
30
27
|
<AtomicCard style={styles.card}>
|
|
31
28
|
<View style={styles.settingItem}>
|
|
32
29
|
<View style={styles.iconContainer}>
|
|
33
|
-
<AtomicIcon name=
|
|
30
|
+
<AtomicIcon name={iconName} size="lg" color={iconColor}/>
|
|
34
31
|
</View>
|
|
35
32
|
<View style={styles.textContainer}>
|
|
36
33
|
<AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
|
|
37
|
-
{
|
|
34
|
+
{translations.title}
|
|
38
35
|
</AtomicText>
|
|
39
36
|
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: STATIC_TOKENS.spacing.xs }}>
|
|
40
|
-
{
|
|
37
|
+
{translations.description}
|
|
41
38
|
</AtomicText>
|
|
42
39
|
</View>
|
|
43
40
|
<AtomicSwitch value={notificationsEnabled} onValueChange={setNotificationsEnabled} testID="notifications-toggle"/>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationsScreen.js","sourceRoot":"","sources":["../../../src/presentation/screens/NotificationsScreen.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"NotificationsScreen.js","sourceRoot":"","sources":["../../../src/presentation/screens/NotificationsScreen.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-notifications",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Offline-first local notifications system for React Native apps using expo-notifications",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@umituz/react-native-design-system": "*",
|
|
36
36
|
"@umituz/react-native-design-system-theme": "*",
|
|
37
37
|
"expo-device": "~6.0.2",
|
|
38
|
+
"expo-notifications": "~0.28.0",
|
|
38
39
|
"react": ">=18.2.0",
|
|
39
40
|
"react-native": ">=0.74.0",
|
|
40
41
|
"zustand": "^5.0.2"
|
|
@@ -67,7 +68,6 @@
|
|
|
67
68
|
"@react-navigation/native": "^7.1.19",
|
|
68
69
|
"@umituz/react-native-design-system-atoms": "*",
|
|
69
70
|
"expo-linear-gradient": "^15.0.7",
|
|
70
|
-
"expo-notifications": "~0.28.0",
|
|
71
71
|
"react-native-safe-area-context": "^5.6.2"
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { NotificationManager } from '../src/infrastructure/services/NotificationManager';
|
|
2
|
+
import type { ScheduleNotificationOptions } from '../src/infrastructure/services/NotificationManager';
|
|
3
|
+
|
|
4
|
+
// Mock expo-notifications
|
|
5
|
+
jest.mock('expo-notifications', () => ({
|
|
6
|
+
setNotificationHandler: jest.fn(),
|
|
7
|
+
scheduleNotificationAsync: jest.fn(),
|
|
8
|
+
cancelScheduledNotificationAsync: jest.fn(),
|
|
9
|
+
cancelAllScheduledNotificationsAsync: jest.fn(),
|
|
10
|
+
getAllScheduledNotificationsAsync: jest.fn(),
|
|
11
|
+
dismissAllNotificationsAsync: jest.fn(),
|
|
12
|
+
getBadgeCountAsync: jest.fn(),
|
|
13
|
+
setBadgeCountAsync: jest.fn(),
|
|
14
|
+
getPermissionsAsync: jest.fn(),
|
|
15
|
+
requestPermissionsAsync: jest.fn(),
|
|
16
|
+
setNotificationChannelAsync: jest.fn(),
|
|
17
|
+
AndroidImportance: {
|
|
18
|
+
DEFAULT: 'default',
|
|
19
|
+
HIGH: 'high',
|
|
20
|
+
MAX: 'max',
|
|
21
|
+
},
|
|
22
|
+
AndroidNotificationPriority: {
|
|
23
|
+
DEFAULT: 'default',
|
|
24
|
+
HIGH: 'high',
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock expo-device
|
|
29
|
+
jest.mock('expo-device', () => ({
|
|
30
|
+
isDevice: true,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Mock react-native
|
|
34
|
+
jest.mock('react-native', () => ({
|
|
35
|
+
Platform: {
|
|
36
|
+
OS: 'ios',
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
describe('NotificationManager', () => {
|
|
41
|
+
let manager: NotificationManager;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
manager = new NotificationManager();
|
|
45
|
+
jest.clearAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('scheduleNotification', () => {
|
|
49
|
+
it('should schedule a notification with date trigger', async () => {
|
|
50
|
+
const mockSchedule = require('expo-notifications').scheduleNotificationAsync;
|
|
51
|
+
mockSchedule.mockResolvedValue('test-id');
|
|
52
|
+
|
|
53
|
+
const options: ScheduleNotificationOptions = {
|
|
54
|
+
title: 'Test Notification',
|
|
55
|
+
body: 'Test body',
|
|
56
|
+
trigger: { type: 'date', date: new Date('2025-01-15T09:00:00') },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = await manager.scheduleNotification(options);
|
|
60
|
+
|
|
61
|
+
expect(result).toBe('test-id');
|
|
62
|
+
expect(mockSchedule).toHaveBeenCalledWith({
|
|
63
|
+
content: {
|
|
64
|
+
title: 'Test Notification',
|
|
65
|
+
body: 'Test body',
|
|
66
|
+
data: {},
|
|
67
|
+
sound: 'default',
|
|
68
|
+
priority: 'HIGH',
|
|
69
|
+
vibrate: [0, 250, 250, 250],
|
|
70
|
+
},
|
|
71
|
+
trigger: {
|
|
72
|
+
date: new Date('2025-01-15T09:00:00'),
|
|
73
|
+
channelId: 'default',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should schedule a daily notification', async () => {
|
|
79
|
+
const mockSchedule = require('expo-notifications').scheduleNotificationAsync;
|
|
80
|
+
mockSchedule.mockResolvedValue('daily-id');
|
|
81
|
+
|
|
82
|
+
const options: ScheduleNotificationOptions = {
|
|
83
|
+
title: 'Daily Reminder',
|
|
84
|
+
body: 'Time for daily task',
|
|
85
|
+
trigger: { type: 'daily', hour: 9, minute: 0 },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const result = await manager.scheduleNotification(options);
|
|
89
|
+
|
|
90
|
+
expect(result).toBe('daily-id');
|
|
91
|
+
expect(mockSchedule).toHaveBeenCalledWith({
|
|
92
|
+
content: expect.objectContaining({
|
|
93
|
+
title: 'Daily Reminder',
|
|
94
|
+
body: 'Time for daily task',
|
|
95
|
+
}),
|
|
96
|
+
trigger: {
|
|
97
|
+
hour: 9,
|
|
98
|
+
minute: 0,
|
|
99
|
+
repeats: true,
|
|
100
|
+
channelId: 'reminders',
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle scheduling errors', async () => {
|
|
106
|
+
const mockSchedule = require('expo-notifications').scheduleNotificationAsync;
|
|
107
|
+
mockSchedule.mockRejectedValue(new Error('Scheduling failed'));
|
|
108
|
+
|
|
109
|
+
const options: ScheduleNotificationOptions = {
|
|
110
|
+
title: 'Test',
|
|
111
|
+
body: 'Test',
|
|
112
|
+
trigger: { type: 'date', date: new Date() },
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
await expect(manager.scheduleNotification(options)).rejects.toThrow('Scheduling failed');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('cancelNotification', () => {
|
|
120
|
+
it('should cancel a scheduled notification', async () => {
|
|
121
|
+
const mockCancel = require('expo-notifications').cancelScheduledNotificationAsync;
|
|
122
|
+
mockCancel.mockResolvedValue(undefined);
|
|
123
|
+
|
|
124
|
+
await manager.cancelNotification('test-id');
|
|
125
|
+
|
|
126
|
+
expect(mockCancel).toHaveBeenCalledWith('test-id');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle cancellation errors', async () => {
|
|
130
|
+
const mockCancel = require('expo-notifications').cancelScheduledNotificationAsync;
|
|
131
|
+
mockCancel.mockRejectedValue(new Error('Cancel failed'));
|
|
132
|
+
|
|
133
|
+
await expect(manager.cancelNotification('test-id')).rejects.toThrow('Cancel failed');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('getScheduledNotifications', () => {
|
|
138
|
+
it('should return scheduled notifications', async () => {
|
|
139
|
+
const mockGetAll = require('expo-notifications').getAllScheduledNotificationsAsync;
|
|
140
|
+
mockGetAll.mockResolvedValue([
|
|
141
|
+
{
|
|
142
|
+
identifier: 'test-id',
|
|
143
|
+
content: {
|
|
144
|
+
title: 'Test',
|
|
145
|
+
body: 'Test body',
|
|
146
|
+
data: { key: 'value' },
|
|
147
|
+
},
|
|
148
|
+
trigger: { type: 'date' },
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
const result = await manager.getScheduledNotifications();
|
|
153
|
+
|
|
154
|
+
expect(result).toHaveLength(1);
|
|
155
|
+
expect(result[0]).toEqual({
|
|
156
|
+
identifier: 'test-id',
|
|
157
|
+
content: {
|
|
158
|
+
title: 'Test',
|
|
159
|
+
body: 'Test body',
|
|
160
|
+
data: { key: 'value' },
|
|
161
|
+
},
|
|
162
|
+
trigger: { type: 'date' },
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle empty notifications list', async () => {
|
|
167
|
+
const mockGetAll = require('expo-notifications').getAllScheduledNotificationsAsync;
|
|
168
|
+
mockGetAll.mockResolvedValue([]);
|
|
169
|
+
|
|
170
|
+
const result = await manager.getScheduledNotifications();
|
|
171
|
+
|
|
172
|
+
expect(result).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle errors gracefully', async () => {
|
|
176
|
+
const mockGetAll = require('expo-notifications').getAllScheduledNotificationsAsync;
|
|
177
|
+
mockGetAll.mockRejectedValue(new Error('Fetch failed'));
|
|
178
|
+
|
|
179
|
+
const result = await manager.getScheduledNotifications();
|
|
180
|
+
|
|
181
|
+
expect(result).toHaveLength(0);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('badge management', () => {
|
|
186
|
+
it('should get badge count on iOS', async () => {
|
|
187
|
+
const mockGetBadge = require('expo-notifications').getBadgeCountAsync;
|
|
188
|
+
mockGetBadge.mockResolvedValue(5);
|
|
189
|
+
|
|
190
|
+
const result = await manager.getBadgeCount();
|
|
191
|
+
|
|
192
|
+
expect(result).toBe(5);
|
|
193
|
+
expect(mockGetBadge).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should return 0 on Android', async () => {
|
|
197
|
+
jest.doMock('react-native', () => ({
|
|
198
|
+
Platform: { OS: 'android' },
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
const result = await manager.getBadgeCount();
|
|
202
|
+
|
|
203
|
+
expect(result).toBe(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should set badge count on iOS', async () => {
|
|
207
|
+
const mockSetBadge = require('expo-notifications').setBadgeCountAsync;
|
|
208
|
+
mockSetBadge.mockResolvedValue(undefined);
|
|
209
|
+
|
|
210
|
+
await manager.setBadgeCount(3);
|
|
211
|
+
|
|
212
|
+
expect(mockSetBadge).toHaveBeenCalledWith(3);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|