From 1fb639539343f7775d150a990eb7e96ef3f67bd0 Mon Sep 17 00:00:00 2001 From: andrewcaplan1 Date: Tue, 19 Mar 2024 13:21:18 -0400 Subject: [PATCH 1/2] feat(notifications): notification stuffs no --- client/app.json | 8 +- client/package.json | 2 + client/screens/LoginPage.tsx | 16 ++- client/services/notifications.tsx | 166 ++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 client/services/notifications.tsx diff --git a/client/app.json b/client/app.json index 0cab358..a1e5910 100644 --- a/client/app.json +++ b/client/app.json @@ -1,7 +1,7 @@ { "expo": { "name": "client", - "slug": "client", + "slug": "care-wallet-generate", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", @@ -23,6 +23,12 @@ }, "web": { "favicon": "./assets/favicon.png" + }, + "extra": { + "eas": { + "projectId": "c66c08fd-ea3e-4a67-84da-77ef27bb3c23" + } } + // "owner": "andrew10" } } diff --git a/client/package.json b/client/package.json index 378c94e..6222cbb 100644 --- a/client/package.json +++ b/client/package.json @@ -27,8 +27,10 @@ "date-fns": "^3.3.1", "expo": "50.0.6", "expo": "^50.0.11", + "expo-device": "~5.4.0", "expo-document-picker": "~11.10.1", "expo-file-system": "~16.0.6", + "expo-notifications": "~0.20.1", "expo-status-bar": "~1.11.1", "firebase": "^10.7.2", "lodash": "^4.17.21", diff --git a/client/screens/LoginPage.tsx b/client/screens/LoginPage.tsx index e23a132..5751ea9 100644 --- a/client/screens/LoginPage.tsx +++ b/client/screens/LoginPage.tsx @@ -1,20 +1,34 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Alert, Pressable, Text, TextInput, View } from 'react-native'; import { onAuthStateChanged } from '@firebase/auth'; import { useNavigation } from '@react-navigation/native'; +import Constants from 'expo-constants'; import { auth } from '../firebase.config'; import { AppStackNavigation } from '../navigation/types'; import { useAuth } from '../services/auth'; +import { registerForPushNotificationsAsync } from '../services/notifications'; export default function LoginPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const { logInMutation, signUpMutation } = useAuth(); + const [expoPushToken, setExpoPushToken] = useState(''); const navigation = useNavigation(); + useEffect(() => { + console.log(Constants.easConfig?.projectId); // --> undefined + console.log(Constants.expoConfig?.extra?.eas.projectId); // --> my project id + + registerForPushNotificationsAsync().then((token) => + setExpoPushToken(token!) + ); + + console.log(expoPushToken); + }, []); + onAuthStateChanged(auth, (user) => { if (user) { navigation.navigate('Main'); diff --git a/client/services/notifications.tsx b/client/services/notifications.tsx new file mode 100644 index 0000000..a780aa8 --- /dev/null +++ b/client/services/notifications.tsx @@ -0,0 +1,166 @@ +import { Platform } from 'react-native'; + +import Constants from 'expo-constants'; +import { isDevice } from 'expo-device'; +import { + AndroidImportance, + cancelScheduledNotificationAsync, + getExpoPushTokenAsync, + requestPermissionsAsync, + scheduleNotificationAsync, + setNotificationChannelAsync +} from 'expo-notifications'; + +export async function registerForPushNotificationsAsync() { + // checks that this is a physical device + if (!isDevice) { + alert( + 'Must use physical device for Push Notifications. Must be ios or android.' + ); + return null; + } + + // ask user for notification permissions + const { status } = await requestPermissionsAsync(); + if (status !== 'granted') { + alert('Permission to receive push notifications was denied'); + return null; + } + + // android needs notification channel with highest importance so notificaiton goes through always + if (Platform.OS === 'android') { + setNotificationChannelAsync('default', { + name: 'default', + importance: AndroidImportance.MAX + }); + } + + const carewalletProjectId = Constants.easConfig?.projectId; + + // gets push notification token + const token = ( + await getExpoPushTokenAsync({ + projectId: carewalletProjectId + }) + ).data; + console.log('ExpoPushToken: ', token); + + return token; +} + +export async function scheduleCalendarPushNotification( + title: string, + body: string, + repeat: boolean, + date: Date, + typeOfTrigger: string + // OPTIONS for typeOfTrigger: + // NOTE: IN UTC TIME (Must convert time zone you are in to UTC) + // 1. Yearly: { repeats: true, month: [0-11], day: [1-31], hour: [0-23], minute: [0-59] } + // - Repeats annually on the specified month, day, hour, and minute. + // 2. Weekly: { weekday: [1-7], hour: [0-23], minute: [0-59] } + // - Repeats weekly on the specified weekday, hour, and minute. (1 for Sunday, 2 for Monday, ..., 7 for Saturday) + // 3. Daily: { repeats: true, hour: [0-23], minute: [0-59] } + // - Repeats daily at the specified hour and minute. +) { + try { + const triggerDate = new Date(date); + + let Trigger; + + if (repeat) { + if (typeOfTrigger === 'yearly') { + Trigger = { + repeats: true, + month: triggerDate.getUTCMonth(), + day: triggerDate.getUTCDate(), + hour: triggerDate.getUTCHours(), + minute: triggerDate.getUTCMinutes() + }; + } else if (typeOfTrigger === 'weekly') { + Trigger = { + weekday: triggerDate.getUTCDay() === 0 ? 7 : triggerDate.getUTCDay(), + hour: triggerDate.getUTCHours(), + minute: triggerDate.getUTCMinutes() + }; + } else if (typeOfTrigger === 'daily') { + Trigger = { + repeats: true, + hour: triggerDate.getUTCHours(), + minute: triggerDate.getUTCMinutes() + }; + } else { + // will default to daily + Trigger = { + repeats: true, + hour: triggerDate.getUTCHours(), + minute: triggerDate.getUTCMinutes() + }; + } + } else { + // One-time notification trigger + Trigger = new Date(triggerDate); + } + + await scheduleNotificationAsync({ + content: { + title: title, + body: body + }, + trigger: Trigger + }); + + console.log('Notification scheduled successfully'); + } catch (error) { + console.error('Error scheduling notification:', error); + alert('Failed to schedule notification. Please try again later.'); + } +} + +// INSTANT push notification - when this function is called with intended title and body, +// a notificaiton will be sent to the user right away (in 1 second) +export async function scheduleInstantPushNotification( + title: string, + body: string +) { + scheduleNotificationAsync({ + content: { + title: title, + body: body, + data: {} + }, + trigger: { + seconds: 1 + } + }); +} + +export async function cancelScheduleNotificationID(id: string) { + cancelScheduledNotificationAsync(id); +} + +// this function can be used schedule a daily notification +// takes in title, body, the hour and minute to be repeated at, and date to begin repeating +export async function scheduleDailyNotification( + title: string, + body: string, + hour: number, + minutes: number, + dateToStart: Date +) { + // Get the current date + const currentDate = dateToStart; + + // Set the time + currentDate.setHours(hour); + currentDate.setMinutes(minutes); + + // Schedule the daily notification + await scheduleCalendarPushNotification( + title, + body, + true, // repeat daily + currentDate, + 'daily' // specify the type of trigger as 'daily' + ); +} From 988841148d4f4c470693a8611d4f35e1c278f30e Mon Sep 17 00:00:00 2001 From: andrewcaplan1 Date: Tue, 19 Mar 2024 14:20:12 -0400 Subject: [PATCH 2/2] fix(semantics): notification smol change --- client/screens/LoginPage.tsx | 2 +- client/services/notifications.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/screens/LoginPage.tsx b/client/screens/LoginPage.tsx index 5751ea9..eff22b8 100644 --- a/client/screens/LoginPage.tsx +++ b/client/screens/LoginPage.tsx @@ -26,7 +26,7 @@ export default function LoginPage() { setExpoPushToken(token!) ); - console.log(expoPushToken); + console.log(expoPushToken); // this is here until we send to backend }, []); onAuthStateChanged(auth, (user) => { diff --git a/client/services/notifications.tsx b/client/services/notifications.tsx index a780aa8..9cc6ee8 100644 --- a/client/services/notifications.tsx +++ b/client/services/notifications.tsx @@ -53,7 +53,7 @@ export async function scheduleCalendarPushNotification( body: string, repeat: boolean, date: Date, - typeOfTrigger: string + typeOfTrigger?: string // OPTIONS for typeOfTrigger: // NOTE: IN UTC TIME (Must convert time zone you are in to UTC) // 1. Yearly: { repeats: true, month: [0-11], day: [1-31], hour: [0-23], minute: [0-59] } @@ -102,7 +102,7 @@ export async function scheduleCalendarPushNotification( Trigger = new Date(triggerDate); } - await scheduleNotificationAsync({ + const notificationID = await scheduleNotificationAsync({ content: { title: title, body: body @@ -110,7 +110,8 @@ export async function scheduleCalendarPushNotification( trigger: Trigger }); - console.log('Notification scheduled successfully'); + // notification ID is returned, so you can use it to cancel the notification + console.log('Notification scheduled successfully, %s', notificationID); } catch (error) { console.error('Error scheduling notification:', error); alert('Failed to schedule notification. Please try again later.'); @@ -118,7 +119,7 @@ export async function scheduleCalendarPushNotification( } // INSTANT push notification - when this function is called with intended title and body, -// a notificaiton will be sent to the user right away (in 1 second) +// a notification will be sent to the user right away (in 1 second) export async function scheduleInstantPushNotification( title: string, body: string