feat: 增强通知功能及用户体验
- 在 Bootstrapper 组件中新增通知服务初始化逻辑,注册每日午餐提醒 - 在 CoachScreen 中优化欢迎消息生成逻辑,整合用户配置文件数据 - 更新 GoalsScreen 组件,优化目标创建时的通知设置逻辑 - 在 NotificationTest 组件中添加调试通知状态功能,提升开发便利性 - 新增 NutritionNotificationHelpers 中的午餐提醒功能,支持每日提醒设置 - 更新相关文档,详细描述新功能和使用方法
This commit is contained in:
@@ -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,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
142
utils/welcomeMessage.ts
Normal file
142
utils/welcomeMessage.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 用户配置文件类型
|
||||
type UserProfile = {
|
||||
name?: string;
|
||||
weight?: number;
|
||||
height?: number;
|
||||
pilatesPurposes?: string[];
|
||||
};
|
||||
|
||||
// 参数类型
|
||||
type GenerateWelcomeMessageParams = {
|
||||
userProfile?: UserProfile;
|
||||
hasRecordedMoodToday?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成个性化欢迎消息
|
||||
* @param params 参数对象,包含用户配置和今日是否已记录心情
|
||||
* @returns 个性化欢迎消息字符串
|
||||
*/
|
||||
export function generateWelcomeMessage(params: GenerateWelcomeMessageParams): string {
|
||||
const { userProfile, hasRecordedMoodToday = false } = params;
|
||||
const hour = new Date().getHours();
|
||||
const name = userProfile?.name || '朋友';
|
||||
|
||||
// 时段问候
|
||||
let timeGreeting = '';
|
||||
if (hour >= 5 && hour < 9) {
|
||||
timeGreeting = '早上好';
|
||||
} else if (hour >= 9 && hour < 12) {
|
||||
timeGreeting = '上午好';
|
||||
} else if (hour >= 12 && hour < 14) {
|
||||
timeGreeting = '中午好';
|
||||
} else if (hour >= 14 && hour < 18) {
|
||||
timeGreeting = '下午好';
|
||||
} else if (hour >= 18 && hour < 22) {
|
||||
timeGreeting = '晚上好';
|
||||
} else {
|
||||
timeGreeting = '夜深了';
|
||||
}
|
||||
|
||||
// 欢迎消息模板
|
||||
const welcomeMessages = [
|
||||
{
|
||||
condition: () => hour >= 5 && hour < 9,
|
||||
messages: [
|
||||
`${timeGreeting},${name}!🐳 我是你的小海豹,新的一天开始啦!让我们一起游向健康的目标吧~`,
|
||||
`${timeGreeting}!🌅 早晨的阳光真好呢,我是你的专属小海豹健康伙伴!要不要先制定今天的健康计划呢?`,
|
||||
`${timeGreeting},${name}!🐋 小海豹来报到啦!今天想从哪个方面开始我们的健康之旅呢?营养、运动还是生活管理,我都可以帮你哦~`
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: () => hour >= 9 && hour < 12,
|
||||
messages: [
|
||||
`${timeGreeting},${name}!🐳 上午是身体最活跃的时候呢,小海豹在这里为你加油!有什么健康目标需要我帮你规划吗?`,
|
||||
`${timeGreeting}!☀️ 工作忙碌的上午,别忘了给身体一些关爱哦~我是你的小海豹,随时准备为你提供营养建议和运动指导!`,
|
||||
`${timeGreeting},${name}!🐋 作为你的小海豹伙伴,我想说:每一个健康的选择都在让我们的身体更棒呢!今天想从哪个方面开始呢?`
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: () => hour >= 12 && hour < 14,
|
||||
messages: [
|
||||
`${timeGreeting},${name}!🍽️ 午餐时间到啦!小海豹提醒你,合理的营养搭配能让下午充满能量哦~`,
|
||||
`${timeGreeting}!🌊 忙碌的上午结束了,该给身体补充能量啦!我是你的小海豹,无论是饮食调整还是运动安排,都可以找我商量哦~`,
|
||||
`${timeGreeting},${name}!🐳 午间时光,小海豹建议你关注饮食均衡,也要适度放松一下呢~`
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: () => hour >= 14 && hour < 18,
|
||||
messages: [
|
||||
`${timeGreeting},${name}!🌊 下午是运动的黄金时段呢!小海豹可以为你制定个性化的健身计划,让我们一起游向更好的身材吧~`,
|
||||
`${timeGreeting}!🐋 午后时光,正是关注健康的好时机!我是你的小海豹,从营养到运动,我都能为你提供贴心指导哦~`,
|
||||
`${timeGreeting},${name}!🐳 下午时光,身心健康同样重要呢!作为你的小海豹,我在这里支持你的每一个健康目标~`
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: () => hour >= 18 && hour < 22,
|
||||
messages: [
|
||||
`${timeGreeting},${name}!🌙 忙碌了一天,现在是放松身心的好时候呢!小海豹可以为你提供放松建议和恢复方案哦~`,
|
||||
`${timeGreeting}!🌊 夜幕降临,这是一天中最适合总结的时刻!我是你的小海豹,让我们一起回顾今天的健康表现,规划明天的目标吧~`,
|
||||
`${timeGreeting},${name}!🐳 晚间时光属于你自己,也是关爱身体的珍贵时间!作为你的小海豹,我想陪你聊聊如何更好地管理健康生活呢~`
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: () => hour >= 22 || hour < 5,
|
||||
messages: [
|
||||
`${timeGreeting},${name}!🌙 优质睡眠是健康的基石呢!小海豹提醒你,如果需要睡眠优化建议,随时可以问我哦~`,
|
||||
`夜深了,${name}。🌊 充足的睡眠对身体恢复很重要呢!我是你的小海豹,有什么关于睡眠健康的问题都可以咨询我~`,
|
||||
`夜深了,愿你能拥有甜甜的睡眠。🐳 我是你的小海豹,明天我们继续在健康管理的海洋里同行。晚安,${name}!`
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 特殊情况的消息
|
||||
const specialMessages = [
|
||||
{
|
||||
condition: () => !userProfile?.weight && !userProfile?.height,
|
||||
message: `你好,${name}!🐳 我是你的小海豹!我注意到你还没有完善健康档案呢,不如先聊聊你的健康目标和身体状况,这样小海豹就能为你制定更贴心的健康方案啦~`
|
||||
},
|
||||
{
|
||||
condition: () => userProfile && (!userProfile.pilatesPurposes || userProfile.pilatesPurposes.length === 0),
|
||||
message: `${timeGreeting},${name}!🐋 作为你的小海豹,我想更好地了解你的健康需求呢!告诉我你希望在营养摄入、身材管理、健身锻炼或生活管理方面实现什么目标吧~`
|
||||
},
|
||||
// 新增:晚上20点后且未记录心情的特殊提示
|
||||
{
|
||||
condition: () => hour >= 20 && !hasRecordedMoodToday,
|
||||
message: `${timeGreeting},${name}!🌙 夜深了,小海豹注意到你今天还没有记录心情呢。记录心情有助于更好地了解自己的情绪变化,对健康管理很有帮助哦~要不要现在记录一下今天的心情?`
|
||||
}
|
||||
];
|
||||
|
||||
// 检查特殊情况
|
||||
for (const special of specialMessages) {
|
||||
if (special.condition()) {
|
||||
return special.message;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据时间选择合适的消息组
|
||||
const timeGroup = welcomeMessages.find(group => group.condition());
|
||||
if (timeGroup) {
|
||||
const messages = timeGroup.messages;
|
||||
return messages[Math.floor(Math.random() * messages.length)];
|
||||
}
|
||||
|
||||
// 默认消息
|
||||
return `你好,我是你的小海豹!🐳 可以向我咨询营养摄入、身材管理、健身锻炼、生活管理等各方面的健康问题哦~`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户今天是否已经记录了心情
|
||||
* @param lastMoodDate 上次记录心情的日期
|
||||
* @returns 今天是否已记录心情
|
||||
*/
|
||||
export function hasRecordedMoodToday(lastMoodDate?: string | Date): boolean {
|
||||
if (!lastMoodDate) return false;
|
||||
|
||||
const today = dayjs().format('YYYY-MM-DD');
|
||||
const lastMoodDay = dayjs(lastMoodDate).format('YYYY-MM-DD');
|
||||
|
||||
return today === lastMoodDay;
|
||||
}
|
||||
Reference in New Issue
Block a user