feat: 增强通知功能及用户体验

- 在 Bootstrapper 组件中新增通知服务初始化逻辑,注册每日午餐提醒
- 在 CoachScreen 中优化欢迎消息生成逻辑,整合用户配置文件数据
- 更新 GoalsScreen 组件,优化目标创建时的通知设置逻辑
- 在 NotificationTest 组件中添加调试通知状态功能,提升开发便利性
- 新增 NutritionNotificationHelpers 中的午餐提醒功能,支持每日提醒设置
- 更新相关文档,详细描述新功能和使用方法
This commit is contained in:
richarjiang
2025-08-26 09:56:23 +08:00
parent e6bbda9d0f
commit 7f2afdf671
8 changed files with 472 additions and 185 deletions

View File

@@ -1,5 +1,4 @@
import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
// 配置通知处理方式
Notifications.setNotificationHandler({
@@ -15,17 +14,50 @@ Notifications.setNotificationHandler({
export interface NotificationData {
title: string;
body: string;
data?: Record<string, any>;
data?: Record<string, unknown>;
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 constructor() {}
private constructor() { }
public static getInstance(): NotificationService {
if (!NotificationService.instance) {
@@ -44,28 +76,32 @@ export class NotificationService {
// 请求通知权限
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;
}
// 获取推送令牌(用于远程推送,本地推送不需要)
if (Platform.OS !== 'web') {
const token = await Notifications.getExpoPushTokenAsync({
projectId: 'your-project-id', // 需要替换为实际的Expo项目ID
});
console.log('推送令牌:', token.data);
}
// if (Platform.OS !== 'web') {
// const token = await Notifications.getExpoPushTokenAsync({
// projectId: 'your-project-id', // 需要替换为实际的Expo项目ID
// });
// console.log('推送令牌:', token.data);
// }
// 设置通知监听器
this.setupNotificationListeners();
// 检查已存在的通知
const existingNotifications = await this.getAllScheduledNotifications();
console.log('已存在的通知数量:', existingNotifications.length);
this.isInitialized = true;
console.log('推送通知服务初始化成功');
} catch (error) {
@@ -96,7 +132,7 @@ export class NotificationService {
private handleNotificationResponse(response: Notifications.NotificationResponse): void {
const { notification } = response;
const data = notification.request.content.data;
// 根据通知类型处理不同的逻辑
if (data?.type === 'workout_reminder') {
// 处理运动提醒
@@ -111,6 +147,11 @@ export class NotificationService {
// 处理目标提醒通知
console.log('用户点击了目标提醒通知', data);
// 这里可以添加导航到目标页面的逻辑
} else if (data?.type === 'lunch_reminder') {
// 处理午餐提醒通知
console.log('用户点击了午餐提醒通知', data);
// 这里可以添加导航到午餐记录页面的逻辑
}
}
@@ -133,7 +174,7 @@ export class NotificationService {
},
trigger: trigger || null, // null表示立即发送
});
console.log('本地通知已安排ID:', notificationId);
return notificationId;
} catch (error) {
@@ -167,10 +208,11 @@ export class NotificationService {
vibrate: notification.vibrate,
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.DATE,
date: date.getTime(),
} as any,
} as DateTrigger,
});
console.log('定时通知已安排ID:', notificationId);
return notificationId;
} catch (error) {
@@ -196,7 +238,7 @@ export class NotificationService {
): Promise<string> {
try {
// 计算总秒数
const totalSeconds =
const totalSeconds =
(interval.seconds || 0) +
(interval.minutes || 0) * 60 +
(interval.hours || 0) * 3600 +
@@ -219,11 +261,12 @@ export class NotificationService {
vibrate: notification.vibrate,
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
seconds: totalSeconds,
repeats: true,
} as any,
} as RepeatingTrigger,
});
console.log('重复通知已安排ID:', notificationId);
return notificationId;
} catch (error) {
@@ -238,7 +281,7 @@ export class NotificationService {
async scheduleCalendarRepeatingNotification(
notification: NotificationData,
options: {
type: 'daily' | 'weekly' | 'monthly';
type: Notifications.SchedulableTriggerInputTypes;
hour: number;
minute: number;
weekdays?: number[]; // 0-60为周日仅用于weekly类型
@@ -246,38 +289,38 @@ export class NotificationService {
}
): Promise<string> {
try {
let trigger: any;
let trigger: CalendarTrigger;
switch (options.type) {
case 'daily':
case Notifications.SchedulableTriggerInputTypes.DAILY:
trigger = {
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: options.hour,
minute: options.minute,
repeats: true,
};
break;
case 'weekly':
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
repeats: true,
};
} else {
trigger = {
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: options.hour,
minute: options.minute,
repeats: true,
};
}
break;
case 'monthly':
case Notifications.SchedulableTriggerInputTypes.MONTHLY:
trigger = {
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
hour: options.hour,
minute: options.minute,
day: options.dayOfMonth || 1,
repeats: true,
};
break;
default:
@@ -295,7 +338,7 @@ export class NotificationService {
},
trigger,
});
console.log(`${options.type}重复通知已安排ID:`, notificationId);
return notificationId;
} catch (error) {
@@ -336,6 +379,7 @@ export class NotificationService {
async getAllScheduledNotifications(): Promise<Notifications.NotificationRequest[]> {
try {
const notifications = await Notifications.getAllScheduledNotificationsAsync();
return notifications;
} catch (error) {
console.error('获取已安排通知失败:', error);
@@ -343,6 +387,7 @@ export class NotificationService {
}
}
/**
* 获取通知权限状态
*/
@@ -380,6 +425,7 @@ export const NotificationTypes = {
MOOD_CHECKIN: 'mood_checkin',
NUTRITION_REMINDER: 'nutrition_reminder',
PROGRESS_UPDATE: 'progress_update',
LUNCH_REMINDER: 'lunch_reminder',
} as const;
// 便捷方法
@@ -391,7 +437,7 @@ export const sendWorkoutReminder = (title: string, body: string, date?: Date) =>
sound: true,
priority: 'high',
};
if (date) {
return notificationService.scheduleNotificationAtDate(notification, date);
} else {
@@ -407,7 +453,7 @@ export const sendGoalAchievement = (title: string, body: string) => {
sound: true,
priority: 'high',
};
return notificationService.sendImmediateNotification(notification);
};
@@ -419,7 +465,7 @@ export const sendMoodCheckinReminder = (title: string, body: string, date?: Date
sound: true,
priority: 'normal',
};
if (date) {
return notificationService.scheduleNotificationAtDate(notification, date);
} else {