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..eff22b8 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); // this is here until we send to backend + }, []); + 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..9cc6ee8 --- /dev/null +++ b/client/services/notifications.tsx @@ -0,0 +1,167 @@ +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); + } + + const notificationID = await scheduleNotificationAsync({ + content: { + title: title, + body: body + }, + trigger: Trigger + }); + + // 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.'); + } +} + +// INSTANT push notification - when this function is called with intended title and body, +// a notification 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' + ); +}