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,3 +1,4 @@
import * as Notifications from 'expo-notifications';
import { NotificationData, notificationService } from '../services/notifications';
/**
@@ -36,7 +37,7 @@ export class WorkoutNotificationHelpers {
static async scheduleDailyWorkoutReminder(userName: string, hour: number = 9, minute: number = 0) {
const reminderTime = new Date();
reminderTime.setHours(hour, minute, 0, 0);
// 如果今天的时间已经过了,设置为明天
if (reminderTime.getTime() <= Date.now()) {
reminderTime.setDate(reminderTime.getDate() + 1);
@@ -105,12 +106,12 @@ export class GoalNotificationHelpers {
);
}
/**
* 根据目标设置创建定时推送
* @param goalData 目标数据
* @param userName 用户名
* @returns 通知ID数组
*/
/**
* 根据目标设置创建定时推送
* @param goalData 目标数据
* @param userName 用户名
* @returns 通知ID数组
*/
static async scheduleGoalNotifications(
goalData: {
title: string;
@@ -137,13 +138,13 @@ export class GoalNotificationHelpers {
try {
// 解析提醒时间
const [hours, minutes] = goalData.reminderTime.split(':').map(Number);
// 创建通知内容
const notification: NotificationData = {
title: '目标提醒',
body: `${userName},该完成您的目标"${goalData.title}"了!`,
data: {
type: 'goal_reminder',
data: {
type: 'goal_reminder',
goalTitle: goalData.title,
repeatType: goalData.repeatType,
frequency: goalData.frequency
@@ -159,7 +160,7 @@ export class GoalNotificationHelpers {
const dailyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: 'daily',
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hours,
minute: minutes,
}
@@ -175,7 +176,7 @@ export class GoalNotificationHelpers {
const weeklyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: 'weekly',
type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
hour: hours,
minute: minutes,
weekdays: [weekday],
@@ -189,7 +190,7 @@ export class GoalNotificationHelpers {
const weeklyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: 'weekly',
type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
hour: hours,
minute: minutes,
}
@@ -206,7 +207,7 @@ export class GoalNotificationHelpers {
const monthlyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: 'monthly',
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
hour: hours,
minute: minutes,
dayOfMonth: dayOfMonth,
@@ -220,7 +221,7 @@ export class GoalNotificationHelpers {
const monthlyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: 'monthly',
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
hour: hours,
minute: minutes,
dayOfMonth: 1,
@@ -249,10 +250,10 @@ export class GoalNotificationHelpers {
static async cancelGoalNotifications(goalTitle: string): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'goal_reminder' &&
notification.content.data?.goalTitle === goalTitle) {
if (notification.content.data?.type === 'goal_reminder' &&
notification.content.data?.goalTitle === goalTitle) {
await notificationService.cancelNotification(notification.identifier);
console.log(`已取消目标"${goalTitle}"的通知:${notification.identifier}`);
}
@@ -287,7 +288,7 @@ export class MoodNotificationHelpers {
static async scheduleDailyMoodReminder(userName: string, hour: number = 20, minute: number = 0) {
const reminderTime = new Date();
reminderTime.setHours(hour, minute, 0, 0);
// 如果今天的时间已经过了,设置为明天
if (reminderTime.getTime() <= Date.now()) {
reminderTime.setDate(reminderTime.getDate() + 1);
@@ -323,6 +324,96 @@ export class NutritionNotificationHelpers {
});
}
/**
* 安排每日午餐提醒
* @param userName 用户名
* @param hour 小时 (默认12点)
* @param minute 分钟 (默认0分)
* @returns 通知ID
*/
static async scheduleDailyLunchReminder(
userName: string,
hour: number = 12,
minute: number = 0
): Promise<string | null> {
try {
// 检查是否已经存在午餐提醒
const existingNotifications = await notificationService.getAllScheduledNotifications();
console.log('existingNotifications', existingNotifications);
const existingLunchReminder = existingNotifications.find(
notification =>
notification.content.data?.type === 'lunch_reminder' &&
notification.content.data?.isDailyReminder === true
);
if (existingLunchReminder) {
console.log('午餐提醒已存在,跳过重复注册:', existingLunchReminder.identifier);
return existingLunchReminder.identifier;
}
// 创建午餐提醒通知
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
{
title: '午餐记录提醒',
body: `${userName},记得记录今天的午餐情况哦!`,
data: {
type: 'lunch_reminder',
isDailyReminder: true,
meal: '午餐'
},
sound: true,
priority: 'normal',
},
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hour,
minute: minute,
}
);
console.log('每日午餐提醒已安排ID:', notificationId);
return notificationId;
} catch (error) {
console.error('安排每日午餐提醒失败:', error);
throw error;
}
}
/**
* 发送午餐记录提醒
*/
static async sendLunchReminder(userName: string) {
return notificationService.sendImmediateNotification({
title: '午餐记录提醒',
body: `${userName},记得记录今天的午餐情况哦!`,
data: { type: 'lunch_reminder', meal: '午餐' },
sound: true,
priority: 'normal',
});
}
/**
* 取消午餐提醒
*/
static async cancelLunchReminder(): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'lunch_reminder' &&
notification.content.data?.isDailyReminder === true) {
await notificationService.cancelNotification(notification.identifier);
console.log('已取消午餐提醒:', notification.identifier);
}
}
} catch (error) {
console.error('取消午餐提醒失败:', error);
throw error;
}
}
/**
* 安排营养记录提醒
*/
@@ -339,7 +430,7 @@ export class NutritionNotificationHelpers {
for (const mealTime of mealTimes) {
const reminderTime = new Date();
reminderTime.setHours(mealTime.hour, mealTime.minute, 0, 0);
// 如果今天的时间已经过了,设置为明天
if (reminderTime.getTime() <= Date.now()) {
reminderTime.setDate(reminderTime.getDate() + 1);
@@ -411,7 +502,7 @@ export class GeneralNotificationHelpers {
*/
static async cancelNotificationsByType(type: string) {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === type) {
await notificationService.cancelNotification(notification.identifier);
@@ -424,7 +515,7 @@ export class GeneralNotificationHelpers {
*/
static async sendBatchNotifications(notifications: NotificationData[]) {
const results = [];
for (const notification of notifications) {
try {
const id = await notificationService.sendImmediateNotification(notification);
@@ -433,7 +524,7 @@ export class GeneralNotificationHelpers {
results.push({ success: false, error, notification });
}
}
return results;
}
}
@@ -491,5 +582,12 @@ export const NotificationTemplates = {
sound: true,
priority: 'normal' as const,
}),
lunch: (userName: string) => ({
title: '午餐记录提醒',
body: `${userName},记得记录今天的午餐情况哦!`,
data: { type: 'lunch_reminder', meal: '午餐' },
sound: true,
priority: 'normal' as const,
}),
},
};