notifications ok
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
|||||||
@@ -60,7 +60,8 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"android.permission.RECORD_AUDIO",
|
"android.permission.RECORD_AUDIO",
|
||||||
"android.permission.CAMERA",
|
"android.permission.CAMERA",
|
||||||
"android.permission.MODIFY_AUDIO_SETTINGS"
|
"android.permission.MODIFY_AUDIO_SETTINGS",
|
||||||
|
"android.permission.POST_NOTIFICATIONS"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
|
|||||||
@@ -89,20 +89,26 @@ const sendNotification = async ({
|
|||||||
if (!receiver || !message) {
|
if (!receiver || !message) {
|
||||||
throw new Error("Receiver and message are required");
|
throw new Error("Receiver and message are required");
|
||||||
}
|
}
|
||||||
const { id } = await refList.notifications.add({
|
const payload = {
|
||||||
sender,
|
sender,
|
||||||
receiver,
|
receiver,
|
||||||
receiverCollection,
|
receiverCollection,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
time: new Date(),
|
time: admin.firestore.FieldValue.serverTimestamp(),
|
||||||
|
read: false,
|
||||||
|
readAt: null,
|
||||||
data,
|
data,
|
||||||
});
|
};
|
||||||
console.log(`Notification ${id} created`);
|
const { id } = await refList.notifications.add(payload);
|
||||||
|
console.log(
|
||||||
|
"[sendNotification] Notification created",
|
||||||
|
JSON.stringify({ id, receiver, receiverCollection }, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log("[sendNotification] Error creating notification:", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ import { Routes } from "../navigation";
|
|||||||
import { navigate } from "../navigation/NavigationService";
|
import { navigate } from "../navigation/NavigationService";
|
||||||
|
|
||||||
import useLayoutType, { sidebarWidth } from "../hooks/useLayoutType.js";
|
import useLayoutType, { sidebarWidth } from "../hooks/useLayoutType.js";
|
||||||
|
import useNotifications from "../hooks/useNotifications";
|
||||||
|
|
||||||
export default ({ title = "", containerStyle = {} }) => {
|
export default ({ title = "", containerStyle = {} }) => {
|
||||||
const { isDesktop } = useLayoutType();
|
const { isDesktop } = useLayoutType();
|
||||||
|
const notificationsContext = useNotifications();
|
||||||
|
const unreadCount = notificationsContext?.unreadCount || 0;
|
||||||
|
const hasUnread = unreadCount > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -49,11 +53,13 @@ export default ({ title = "", containerStyle = {} }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{hasUnread ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
...Style.itemBadge,
|
...Style.itemBadge,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const projectsRef = firestore.collection("projects");
|
|||||||
export const tasksRef = firestore.collection("tasks");
|
export const tasksRef = firestore.collection("tasks");
|
||||||
export const playlistsRef = firestore.collection("playlists");
|
export const playlistsRef = firestore.collection("playlists");
|
||||||
export const chatsRef = firestore.collection("chats");
|
export const chatsRef = firestore.collection("chats");
|
||||||
|
export const notificationsRef = firestore.collection("notifications");
|
||||||
|
|
||||||
export const { arrayUnion, arrayRemove, increment, serverTimestamp } =
|
export const { arrayUnion, arrayRemove, increment, serverTimestamp } =
|
||||||
firebase.firestore.FieldValue;
|
firebase.firestore.FieldValue;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export default {
|
|||||||
currentUserRoles: [],
|
currentUserRoles: [],
|
||||||
|
|
||||||
activeRouteName: null,
|
activeRouteName: null,
|
||||||
|
pendingData: null,
|
||||||
|
|
||||||
webBackgroundImg: null,
|
webBackgroundImg: null,
|
||||||
webLayoutMode: "default",
|
webLayoutMode: "default",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { NotificationContext } from "../providers/NotificationProvider";
|
||||||
|
|
||||||
|
export default function useNotifications() {
|
||||||
|
return useContext(NotificationContext);
|
||||||
|
}
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
import Constants from "expo-constants";
|
import Constants from "expo-constants";
|
||||||
import * as Device from "expo-device";
|
import * as Device from "expo-device";
|
||||||
import * as Notifications from "expo-notifications";
|
import * as Notifications from "expo-notifications";
|
||||||
import React, { createContext, useEffect, useRef, useState } from "react";
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { useGlobal } from "reactn";
|
import { useGlobal } from "reactn";
|
||||||
import { usersRef } from "../config/firebase";
|
import firebase, {
|
||||||
|
notificationsRef,
|
||||||
|
serverTimestamp,
|
||||||
|
usersRef,
|
||||||
|
} from "../config/firebase";
|
||||||
|
import useDataFromRef from "../hooks/useDataFromRef";
|
||||||
|
|
||||||
export const NotificationContext = createContext(null);
|
export const NotificationContext = createContext(null);
|
||||||
|
|
||||||
@@ -18,10 +30,95 @@ export default function NotificationProvider({ children }) {
|
|||||||
const notificationListener = useRef();
|
const notificationListener = useRef();
|
||||||
const responseListener = useRef();
|
const responseListener = useRef();
|
||||||
|
|
||||||
|
const notificationsQuery = useMemo(() => {
|
||||||
|
if (!uid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return notificationsRef
|
||||||
|
.where("receiver", "==", uid)
|
||||||
|
.orderBy("time", "desc");
|
||||||
|
} catch (error) {
|
||||||
|
console.log("notificationsQuery error:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [uid]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: notifications = [],
|
||||||
|
loading: loadingNotifications,
|
||||||
|
setData: setNotifications,
|
||||||
|
} = useDataFromRef({
|
||||||
|
ref: notificationsQuery,
|
||||||
|
initialState: [],
|
||||||
|
refreshArray: [uid],
|
||||||
|
listener: true,
|
||||||
|
condition: !!notificationsQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
const unreadCount = useMemo(() => {
|
||||||
|
try {
|
||||||
|
return notifications.filter((notif) => !notif?.read).length;
|
||||||
|
} catch (_error) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}, [notifications]);
|
||||||
|
|
||||||
|
const markNotificationAsRead = useCallback(async (notificationId) => {
|
||||||
|
try {
|
||||||
|
if (!notificationId) return;
|
||||||
|
await notificationsRef.doc(notificationId).set(
|
||||||
|
{
|
||||||
|
read: true,
|
||||||
|
readAt: serverTimestamp(),
|
||||||
|
},
|
||||||
|
{ merge: true }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("markNotificationAsRead error:", error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const markAllNotificationsAsRead = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
if (!notifications?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unread = notifications.filter((notif) => !notif?.read);
|
||||||
|
if (!unread.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const batch = firebase.firestore().batch();
|
||||||
|
unread.forEach((notif) => {
|
||||||
|
if (!notif?.id) return;
|
||||||
|
batch.set(
|
||||||
|
notificationsRef.doc(notif.id),
|
||||||
|
{
|
||||||
|
read: true,
|
||||||
|
readAt: serverTimestamp(),
|
||||||
|
},
|
||||||
|
{ merge: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await batch.commit();
|
||||||
|
} catch (error) {
|
||||||
|
console.log("markAllNotificationsAsRead error:", error);
|
||||||
|
}
|
||||||
|
}, [notifications]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!!uid && !!user && !notifInit) {
|
if (!uid || !user || notifInit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
registerForPushNotificationsAsync();
|
registerForPushNotificationsAsync();
|
||||||
} else if (notifInit && allowNotifications) {
|
}, [user, uid, notifInit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!allowNotifications) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
notificationListener.current =
|
notificationListener.current =
|
||||||
Notifications.addNotificationReceivedListener(async (notification) => {
|
Notifications.addNotificationReceivedListener(async (notification) => {
|
||||||
console.log("Notification received", notification);
|
console.log("Notification received", notification);
|
||||||
@@ -44,28 +141,29 @@ export default function NotificationProvider({ children }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (notifInit && allowNotifications) {
|
|
||||||
Notifications.removeNotificationSubscription(
|
Notifications.removeNotificationSubscription(
|
||||||
notificationListener.current
|
notificationListener.current
|
||||||
);
|
);
|
||||||
Notifications.removeNotificationSubscription(responseListener.current);
|
Notifications.removeNotificationSubscription(responseListener.current);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [user, uid, notifInit]);
|
}, [allowNotifications, setPendingData]);
|
||||||
|
|
||||||
async function registerForPushNotificationsAsync() {
|
async function registerForPushNotificationsAsync() {
|
||||||
try {
|
try {
|
||||||
if (Device.isDevice) {
|
if (Device.isDevice) {
|
||||||
const { status } = await Notifications.requestPermissionsAsync();
|
let { status } = await Notifications.getPermissionsAsync();
|
||||||
|
if (status !== "granted") {
|
||||||
|
const request = await Notifications.requestPermissionsAsync();
|
||||||
|
status = request.status;
|
||||||
|
}
|
||||||
if (status !== "granted") {
|
if (status !== "granted") {
|
||||||
console.log("Notification permissions denied");
|
console.log("Notification permissions denied");
|
||||||
|
setAllowNotifications(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setAllowNotifications(true);
|
setAllowNotifications(true);
|
||||||
setNotifInit(true);
|
|
||||||
|
|
||||||
// Get the push token
|
// Get the push token
|
||||||
const pushToken = (
|
const pushToken = (
|
||||||
@@ -86,6 +184,8 @@ export default function NotificationProvider({ children }) {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
setNotifInit(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +195,12 @@ export default function NotificationProvider({ children }) {
|
|||||||
allowNotifications,
|
allowNotifications,
|
||||||
pendingData,
|
pendingData,
|
||||||
setPendingData,
|
setPendingData,
|
||||||
|
notifications,
|
||||||
|
setNotifications,
|
||||||
|
loadingNotifications,
|
||||||
|
unreadCount,
|
||||||
|
markNotificationAsRead,
|
||||||
|
markAllNotificationsAsRead,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Reference in New Issue
Block a user