import { Platform } from "react-native"; import * as Notifications from "expo-notifications"; import type { BirthdayEntry } from "@/context/birthdays-context"; const CHANNEL_ID = "birthdays"; const NOTIFICATION_HOUR = 9; const NOTIFICATION_MINUTE = 0; const IDENTIFIER_PREFIX = "birthday-"; Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldPlaySound: true, shouldSetBadge: false, shouldShowBanner: true, shouldShowList: true, }), }); function getNotificationIdentifier(id: string) { return `${IDENTIFIER_PREFIX}${id}`; } function parseBirthdayDate(date: string) { return new Date(`${date}T00:00:00`); } async function configureAndroidChannel() { if (Platform.OS !== "android") { return; } await Notifications.setNotificationChannelAsync(CHANNEL_ID, { name: "Birthdays", description: "Birthday reminders", importance: Notifications.AndroidImportance.DEFAULT, }); } async function hasNotificationPermissionAsync() { const settings = await Notifications.getPermissionsAsync(); return ( settings.granted || settings.ios?.status === Notifications.IosAuthorizationStatus.PROVISIONAL ); } async function ensureNotificationPermissionAsync() { await configureAndroidChannel(); if (await hasNotificationPermissionAsync()) { return true; } const settings = await Notifications.requestPermissionsAsync({ ios: { allowAlert: true, allowBadge: false, allowSound: true, }, }); return ( settings.granted || settings.ios?.status === Notifications.IosAuthorizationStatus.PROVISIONAL ); } async function clearStaleBirthdayNotifications(birthdays: BirthdayEntry[]) { const validIdentifiers = new Set( birthdays.map((birthday) => getNotificationIdentifier(birthday.id)) ); const scheduledNotifications = await Notifications.getAllScheduledNotificationsAsync(); await Promise.all( scheduledNotifications .filter( (notification) => notification.identifier.startsWith(IDENTIFIER_PREFIX) && !validIdentifiers.has(notification.identifier) ) .map((notification) => Notifications.cancelScheduledNotificationAsync(notification.identifier) ) ); } async function scheduleBirthdayNotification(birthday: BirthdayEntry) { const parsedDate = parseBirthdayDate(birthday.date); const month = parsedDate.getMonth(); const day = parsedDate.getDate(); await Notifications.cancelScheduledNotificationAsync( getNotificationIdentifier(birthday.id) ); await Notifications.scheduleNotificationAsync({ identifier: getNotificationIdentifier(birthday.id), content: { title: `Today is ${birthday.name}'s birthday`, body: `Wish ${birthday.name} a happy birthday!`, sound: true, }, trigger: { type: Notifications.SchedulableTriggerInputTypes.YEARLY, channelId: Platform.OS === "android" ? CHANNEL_ID : undefined, month, day, hour: NOTIFICATION_HOUR, minute: NOTIFICATION_MINUTE, }, }); } export async function syncBirthdayNotifications(birthdays: BirthdayEntry[]) { if (Platform.OS === "web") { return; } const permissionGranted = await ensureNotificationPermissionAsync(); if (!permissionGranted) { return; } await clearStaleBirthdayNotifications(birthdays); await Promise.all(birthdays.map(scheduleBirthdayNotification)); }