import Constants from "expo-constants"; import * as Device from "expo-device"; import * as Notifications from "expo-notifications"; import React, { createContext, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { AppState, Platform } from "react-native"; import { useGlobal } from "reactn"; import firebase, { arrayUnion, notificationsRef, serverTimestamp, usersRef, } from "../config/firebase"; import useDataFromRef from "../hooks/useDataFromRef"; export const NotificationContext = createContext(null); export default function NotificationProvider({ children }) { const [user] = useGlobal("currentUserData"); const [uid] = useGlobal("currentUID"); const [pendingData, setPendingData] = useGlobal("pendingData"); const [notifInit, setNotifInit] = useState(false); const [allowNotifications, setAllowNotifications] = useState(false); const notificationListener = 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 clearBadgeCount = useCallback(async () => { if (Platform.OS === "web") { return; } try { await Notifications.setBadgeCountAsync(0); } catch (error) { console.log("clearBadgeCount error:", error); } }, []); 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(() => { if (!uid || !user || notifInit) { return; } registerForPushNotificationsAsync(); }, [user, uid, notifInit]); useEffect(() => { if (Platform.OS === "web") { return; } clearBadgeCount(); const handleAppStateChange = (state) => { if (state === "active") { clearBadgeCount(); } }; const subscription = AppState.addEventListener( "change", handleAppStateChange ); return () => { if (subscription?.remove) { subscription.remove(); } else { AppState.removeEventListener("change", handleAppStateChange); } }; }, [clearBadgeCount]); useEffect(() => { if (!allowNotifications) { return; } notificationListener.current = Notifications.addNotificationReceivedListener(async (notification) => { console.log("Notification received", notification); await Notifications.scheduleNotificationAsync(notification?.request); }); responseListener.current = Notifications.addNotificationResponseReceivedListener( async (response) => { console.log("Notification response", response); if ( response?.actionIdentifier === "expo.modules.notifications.actions.DEFAULT" ) { const { data } = response.notification.request.content; if (data) { console.log("Notification data", data); await setPendingData(data); } } } ); return () => { Notifications.removeNotificationSubscription( notificationListener.current ); Notifications.removeNotificationSubscription(responseListener.current); }; }, [allowNotifications, setPendingData]); async function registerForPushNotificationsAsync() { try { const isWeb = Platform.OS === "web"; if (!Device.isDevice && !isWeb) { console.log("Must use physical device for Push Notifications"); setAllowNotifications(false); setNotifInit(true); return; } let { status } = await Notifications.getPermissionsAsync(); if (status !== "granted") { const request = await Notifications.requestPermissionsAsync(); status = request.status; } if (status !== "granted") { console.log("Notification permissions denied"); setAllowNotifications(false); return; } setAllowNotifications(true); const projectId = Constants.expoConfig?.extra?.eas?.projectId || Constants.manifest2?.extra?.eas?.projectId || Constants.manifest?.extra?.eas?.projectId || ""; const pushToken = ( await Notifications.getExpoPushTokenAsync({ projectId, }) )?.data; if (!pushToken) { console.log("No push token retrieved"); return; } if (!!user && pushToken) { await usersRef.doc(uid).set( { pushTokens: arrayUnion(pushToken), }, { merge: true } ); } } catch (e) { console.log(e); } finally { setNotifInit(true); } } return ( {children} ); }