import { ROUTES } from '@/constants/Routes'; import { getNotificationEnabled } from '@/utils/userPreferences'; import * as Notifications from 'expo-notifications'; import { router } from 'expo-router'; import { pushNotificationManager } from './pushNotificationManager'; // 配置通知处理方式 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldPlaySound: true, shouldSetBadge: true, shouldShowBanner: true, shouldShowList: true, }), }); export interface NotificationData { title: string; body: string; data?: Record; sound?: boolean; priority?: 'default' | 'normal' | 'high'; vibrate?: number[]; } interface DateTrigger { type: Notifications.SchedulableTriggerInputTypes.DATE; date: number; } interface RepeatingTrigger { type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL; seconds: number; repeats?: boolean; } interface DailyTrigger { type: Notifications.SchedulableTriggerInputTypes.DAILY; hour: number; minute: number; } interface WeeklyTrigger { type: Notifications.SchedulableTriggerInputTypes.WEEKLY; hour: number; minute: number; weekday: number; } interface MonthlyTrigger { type: Notifications.SchedulableTriggerInputTypes.MONTHLY; hour: number; minute: number; day: number; } type CalendarTrigger = DailyTrigger | WeeklyTrigger | MonthlyTrigger; export class NotificationService { private static instance: NotificationService; private isInitialized = false; private isIniting = false private constructor() { } public static getInstance(): NotificationService { if (!NotificationService.instance) { NotificationService.instance = new NotificationService(); } return NotificationService.instance; } /** * 初始化推送通知服务 */ async initialize(): Promise { if (this.isInitialized || this.isIniting) return; try { this.isIniting = true // 初始化推送通知管理器(包含设备令牌管理) const pushManagerInitialized = await pushNotificationManager.initialize({ onTokenReceived: (token) => { console.log('设备令牌已接收:', token.substring(0, 20) + '...'); }, onTokenRefresh: (token) => { console.log('设备令牌已刷新:', token.substring(0, 20) + '...'); }, onError: (error) => { console.error('推送通知管理器错误:', error); } }); if (!pushManagerInitialized) { console.warn('推送通知管理器初始化失败,但本地通知功能仍可用'); } // 请求通知权限 const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.warn('推送通知权限未授予'); return; } // 设置通知监听器 this.setupNotificationListeners(); // 检查已存在的通知 await this.getAllScheduledNotifications(); this.isInitialized = true; console.log('推送通知服务初始化成功'); } catch (error) { console.error('推送通知服务初始化失败:', error); } finally { this.isIniting = false } } /** * 设置通知监听器 */ private setupNotificationListeners(): void { // 监听通知接收 Notifications.addNotificationReceivedListener((notification) => { console.log('收到通知:', notification); }); // 监听通知点击 Notifications.addNotificationResponseReceivedListener((response) => { console.log('用户点击了通知:', response); // 这里可以处理通知点击后的逻辑 this.handleNotificationResponse(response); }); } /** * 处理通知响应 */ private handleNotificationResponse(response: Notifications.NotificationResponse): void { const { notification } = response; const data = notification.request.content.data; console.log('处理通知点击:', data); // 根据通知类型处理不同的逻辑 if (data?.type === 'workout_reminder') { // 处理运动提醒 console.log('用户点击了运动提醒通知'); } else if (data?.type === 'goal_achievement') { // 处理目标达成通知 console.log('用户点击了目标达成通知'); } else if (data?.type === 'mood_checkin') { // 处理心情打卡提醒 console.log('用户点击了心情打卡提醒'); } else if (data?.type === 'goal_reminder') { // 处理目标提醒通知 console.log('用户点击了目标提醒通知', data); // 这里可以添加导航到目标页面的逻辑 } else if (data?.type === 'lunch_reminder') { // 处理午餐提醒通知 console.log('用户点击了午餐提醒通知', data); // 跳转到营养记录页面 if (data?.url) { router.push(data.url as any); } } else if (data?.type === 'dinner_reminder') { // 处理晚餐提醒通知 console.log('用户点击了晚餐提醒通知', data); // 跳转到营养记录页面 if (data?.url) { router.push(data.url as any); } } else if (data?.type === NotificationTypes.CHALLENGE_ENCOURAGEMENT) { console.log('用户点击了挑战提醒通知', data); const targetUrl = (data?.url as string) || '/(tabs)/challenges'; router.push(targetUrl as any); } else if (data?.type === 'mood_reminder') { // 处理心情提醒通知 console.log('用户点击了心情提醒通知', data); // 跳转到心情页面 if (data?.url) { router.push(data.url as any); } } else if (data?.type === 'water_reminder' || data?.type === 'regular_water_reminder') { // 处理喝水提醒通知 console.log('用户点击了喝水提醒通知', data); // 跳转到统计页面查看喝水进度 if (data?.url) { router.push(data.url as any); } } else if (data?.type === NotificationTypes.FASTING_START || data?.type === NotificationTypes.FASTING_END) { router.push(ROUTES.TAB_FASTING as any); } else if (data?.type === NotificationTypes.WORKOUT_COMPLETION) { // 处理锻炼完成通知 console.log('用户点击了锻炼完成通知', data); // 跳转到锻炼历史页面 router.push('/workout/history' as any); } } /** * 检查用户是否允许推送通知(不包括系统权限检查,仅检查用户偏好) */ private async isNotificationAllowed(): Promise { try { // 检查用户偏好设置中的推送开关 const userPreferenceEnabled = await getNotificationEnabled(); if (!userPreferenceEnabled) { console.log('用户已在偏好设置中关闭推送通知'); return false; } return true; } catch (error) { console.error('检查推送权限失败:', error); // 如果检查失败,默认允许(避免阻塞重要的健康提醒) return true; } } /** * 完整的权限检查(包括系统权限和用户偏好) */ private async hasFullNotificationPermission(): Promise { try { // 检查系统权限 const permissionStatus = await this.getPermissionStatus(); if (permissionStatus !== 'granted') { console.log('系统推送权限未授予:', permissionStatus); return false; } // 检查用户偏好 const userPreferenceEnabled = await this.isNotificationAllowed(); if (!userPreferenceEnabled) { return false; } return true; } catch (error) { console.error('完整权限检查失败:', error); return false; } } /** * 发送本地推送通知 */ async scheduleLocalNotification( notification: NotificationData, trigger?: Notifications.NotificationTriggerInput ): Promise { try { // 检查完整权限(系统权限 + 用户偏好) const hasPermission = await this.hasFullNotificationPermission(); if (!hasPermission) { console.log('推送通知被系统权限或用户偏好设置阻止,跳过发送'); return 'blocked_by_permission_or_preference'; } const notificationId = await Notifications.scheduleNotificationAsync({ content: { title: notification.title, body: notification.body, data: notification.data || {}, sound: notification.sound ? 'default' : undefined, priority: notification.priority || 'default', vibrate: notification.vibrate, }, trigger: trigger || null, // null表示立即发送 }); console.log('✅ 本地通知已安排,ID:', notificationId); return notificationId; } catch (error) { console.error('❌ 安排本地通知失败:', error); throw error; } } /** * 发送立即通知 */ async sendImmediateNotification(notification: NotificationData): Promise { console.log('📱 准备发送立即通知:', notification.title); return this.scheduleLocalNotification(notification); } /** * 安排定时通知 */ async scheduleNotificationAtDate( notification: NotificationData, date: Date ): Promise { try { const notificationId = await Notifications.scheduleNotificationAsync({ content: { title: notification.title, body: notification.body, data: notification.data || {}, sound: notification.sound ? 'default' : undefined, priority: notification.priority || 'default', vibrate: notification.vibrate, }, trigger: { type: Notifications.SchedulableTriggerInputTypes.DATE, date: date.getTime(), } as DateTrigger, }); console.log('定时通知已安排,ID:', notificationId); return notificationId; } catch (error) { console.error('安排定时通知失败:', error); throw error; } } /** * 安排重复通知(仅支持秒级别) */ async scheduleRepeatingNotification( notification: NotificationData, interval: { seconds?: number; minutes?: number; hours?: number; days?: number; weeks?: number; months?: number; years?: number; } ): Promise { try { // 计算总秒数 const totalSeconds = (interval.seconds || 0) + (interval.minutes || 0) * 60 + (interval.hours || 0) * 3600 + (interval.days || 0) * 86400 + (interval.weeks || 0) * 604800 + (interval.months || 0) * 2592000 + (interval.years || 0) * 31536000; if (totalSeconds <= 0) { throw new Error('重复间隔必须大于0'); } const notificationId = await Notifications.scheduleNotificationAsync({ content: { title: notification.title, body: notification.body, data: notification.data || {}, sound: notification.sound ? 'default' : undefined, priority: notification.priority || 'default', vibrate: notification.vibrate, }, trigger: { type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL, seconds: totalSeconds, repeats: true, } as RepeatingTrigger, }); console.log('重复通知已安排,ID:', notificationId); return notificationId; } catch (error) { console.error('安排重复通知失败:', error); throw error; } } /** * 安排日历重复通知(支持每日、每周、每月) */ async scheduleCalendarRepeatingNotification( notification: NotificationData, options: { type: Notifications.SchedulableTriggerInputTypes; hour: number; minute: number; weekdays?: number[]; // 0-6,0为周日,仅用于weekly类型 dayOfMonth?: number; // 1-31,仅用于monthly类型 } ): Promise { try { let trigger: CalendarTrigger; switch (options.type) { case Notifications.SchedulableTriggerInputTypes.DAILY: trigger = { type: Notifications.SchedulableTriggerInputTypes.DAILY, hour: options.hour, minute: options.minute, }; break; case Notifications.SchedulableTriggerInputTypes.WEEKLY: if (options.weekdays && options.weekdays.length > 0) { trigger = { type: Notifications.SchedulableTriggerInputTypes.WEEKLY, hour: options.hour, minute: options.minute, weekday: options.weekdays[0], // Expo只支持单个weekday }; } else { trigger = { type: Notifications.SchedulableTriggerInputTypes.DAILY, hour: options.hour, minute: options.minute, }; } break; case Notifications.SchedulableTriggerInputTypes.MONTHLY: trigger = { type: Notifications.SchedulableTriggerInputTypes.MONTHLY, hour: options.hour, minute: options.minute, day: options.dayOfMonth || 1, }; break; default: throw new Error('不支持的重复类型'); } const notificationId = await Notifications.scheduleNotificationAsync({ content: { title: notification.title, body: notification.body, data: notification.data || {}, sound: notification.sound ? 'default' : undefined, priority: notification.priority || 'default', vibrate: notification.vibrate, }, trigger, }); console.log(`${options.type}重复通知已安排,ID:`, notificationId); return notificationId; } catch (error) { console.error('安排日历重复通知失败:', error); throw error; } } /** * 取消特定通知 */ async cancelNotification(notificationId: string): Promise { try { await Notifications.cancelScheduledNotificationAsync(notificationId); console.log('通知已取消:', notificationId); } catch (error) { console.error('取消通知失败:', error); throw error; } } /** * 取消所有通知 */ async cancelAllNotifications(): Promise { try { await Notifications.cancelAllScheduledNotificationsAsync(); console.log('所有通知已取消'); } catch (error) { console.error('取消所有通知失败:', error); throw error; } } /** * 获取所有已安排的通知 */ async getAllScheduledNotifications(): Promise { try { const notifications = await Notifications.getAllScheduledNotificationsAsync(); return notifications; } catch (error) { console.error('获取已安排通知失败:', error); throw error; } } /** * 获取通知权限状态 */ async getPermissionStatus(): Promise { try { const { status } = await Notifications.getPermissionsAsync(); return status; } catch (error) { console.error('获取通知权限状态失败:', error); throw error; } } /** * 请求通知权限 */ async requestPermission(): Promise { try { const { status } = await Notifications.requestPermissionsAsync(); return status; } catch (error) { console.error('请求通知权限失败:', error); throw error; } } } // 导出单例实例 export const notificationService = NotificationService.getInstance(); // 预定义的推送通知类型 export const NotificationTypes = { WORKOUT_REMINDER: 'workout_reminder', MOOD_CHECKIN: 'mood_checkin', NUTRITION_REMINDER: 'nutrition_reminder', PROGRESS_UPDATE: 'progress_update', LUNCH_REMINDER: 'lunch_reminder', DINNER_REMINDER: 'dinner_reminder', MOOD_REMINDER: 'mood_reminder', WATER_REMINDER: 'water_reminder', REGULAR_WATER_REMINDER: 'regular_water_reminder', CHALLENGE_ENCOURAGEMENT: 'challenge_encouragement', WORKOUT_COMPLETION: 'workout_completion', FASTING_START: 'fasting_start', FASTING_END: 'fasting_end', } as const; // 便捷方法 export const sendWorkoutReminder = (title: string, body: string, date?: Date) => { const notification: NotificationData = { title, body, data: { type: NotificationTypes.WORKOUT_REMINDER }, sound: true, priority: 'high', }; if (date) { return notificationService.scheduleNotificationAtDate(notification, date); } else { return notificationService.sendImmediateNotification(notification); } }; // sendGoalAchievement 函数已删除,因为目标功能已移除 export const sendMoodCheckinReminder = (title: string, body: string, date?: Date) => { const notification: NotificationData = { title, body, data: { type: NotificationTypes.MOOD_CHECKIN }, sound: true, priority: 'normal', }; if (date) { return notificationService.scheduleNotificationAtDate(notification, date); } else { return notificationService.sendImmediateNotification(notification); } };