- 新增饮水详情页面 `/water/detail` 展示每日饮水记录与统计 - 新增饮水设置页面 `/water/settings` 支持目标与快速添加配置 - 新增喝水提醒设置页面 `/water/reminder-settings` 支持自定义时间段与间隔 - 重构 `useWaterData` Hook,支持按日期查询与实时刷新 - 新增 `WaterNotificationHelpers.scheduleCustomWaterReminders` 实现个性化提醒 - 优化心情编辑页键盘体验,新增 `KeyboardAvoidingView` 与滚动逻辑 - 升级版本号至 1.0.14 并补充路由常量 - 补充用户偏好存储字段 `waterReminderEnabled/startTime/endTime/interval` - 废弃后台定时任务中的旧版喝水提醒逻辑,改为用户手动管理
1586 lines
51 KiB
TypeScript
1586 lines
51 KiB
TypeScript
import * as Notifications from 'expo-notifications';
|
||
import { NotificationData, notificationService } from '../services/notifications';
|
||
import { getNotificationEnabled } from './userPreferences';
|
||
|
||
/**
|
||
* 构建 coach 页面的深度链接
|
||
*/
|
||
export function buildCoachDeepLink(params: {
|
||
action?: 'diet' | 'weight' | 'mood' | 'workout';
|
||
subAction?: 'record' | 'photo' | 'text' | 'card';
|
||
meal?: 'breakfast' | 'lunch' | 'dinner' | 'snack';
|
||
message?: string;
|
||
}): string {
|
||
const baseUrl = '/coach';
|
||
const searchParams = new URLSearchParams();
|
||
|
||
if (params.action) searchParams.set('action', params.action);
|
||
if (params.subAction) searchParams.set('subAction', params.subAction);
|
||
if (params.meal) searchParams.set('meal', params.meal);
|
||
if (params.message) searchParams.set('message', encodeURIComponent(params.message));
|
||
|
||
const queryString = searchParams.toString();
|
||
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||
}
|
||
|
||
/**
|
||
* 运动相关的通知辅助函数
|
||
*/
|
||
export class WorkoutNotificationHelpers {
|
||
/**
|
||
* 发送运动开始提醒
|
||
*/
|
||
static async sendWorkoutStartReminder(userName: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '运动时间到',
|
||
body: `${userName},该开始今天的普拉提训练了!`,
|
||
data: { type: 'workout_start' },
|
||
sound: true,
|
||
priority: 'high',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送运动完成通知
|
||
*/
|
||
static async sendWorkoutCompleteNotification(userName: string, duration: number) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '运动完成',
|
||
body: `${userName},恭喜您完成了${duration}分钟的训练!`,
|
||
data: { type: 'workout_complete' },
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 安排每日运动提醒
|
||
*/
|
||
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);
|
||
}
|
||
|
||
return notificationService.scheduleRepeatingNotification(
|
||
{
|
||
title: '每日运动提醒',
|
||
body: `${userName},该开始今天的普拉提训练了!`,
|
||
data: { type: 'daily_workout_reminder' },
|
||
sound: true,
|
||
priority: 'high',
|
||
},
|
||
{ days: 1 }
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 目标相关的通知辅助函数
|
||
*/
|
||
export class GoalNotificationHelpers {
|
||
/**
|
||
* 发送目标达成通知
|
||
*/
|
||
static async sendGoalAchievementNotification(userName: string, goalName: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '目标达成',
|
||
body: `${userName},恭喜您达成了目标:${goalName}!`,
|
||
data: { type: 'goal_achievement', goalName },
|
||
sound: true,
|
||
priority: 'high',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送目标进度更新通知
|
||
*/
|
||
static async sendGoalProgressNotification(userName: string, goalName: string, progress: number) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '目标进度',
|
||
body: `${userName},您的目标"${goalName}"已完成${progress}%!`,
|
||
data: { type: 'goal_progress', goalName, progress },
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 安排目标提醒
|
||
*/
|
||
static async scheduleGoalReminder(userName: string, goalName: string, deadline: Date) {
|
||
// 在截止日期前一天发送提醒
|
||
const reminderDate = new Date(deadline);
|
||
reminderDate.setDate(reminderDate.getDate() - 1);
|
||
|
||
return notificationService.scheduleNotificationAtDate(
|
||
{
|
||
title: '目标截止提醒',
|
||
body: `${userName},您的目标"${goalName}"明天就要截止了,加油!`,
|
||
data: { type: 'goal_deadline_reminder', goalName },
|
||
sound: true,
|
||
priority: 'high',
|
||
},
|
||
reminderDate
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 根据目标设置创建定时推送
|
||
* @param goalData 目标数据
|
||
* @param userName 用户名
|
||
* @returns 通知ID数组
|
||
*/
|
||
static async scheduleGoalNotifications(
|
||
goalData: {
|
||
title: string;
|
||
repeatType: 'daily' | 'weekly' | 'monthly';
|
||
frequency: number;
|
||
hasReminder: boolean;
|
||
reminderTime?: string;
|
||
customRepeatRule?: {
|
||
weekdays?: number[];
|
||
dayOfMonth?: number[];
|
||
};
|
||
startTime?: number;
|
||
},
|
||
userName: string
|
||
): Promise<string[]> {
|
||
const notificationIds: string[] = [];
|
||
|
||
// 如果没有开启提醒,直接返回
|
||
if (!goalData.hasReminder || !goalData.reminderTime) {
|
||
console.log('目标未开启提醒或未设置提醒时间');
|
||
return notificationIds;
|
||
}
|
||
|
||
try {
|
||
// 解析提醒时间
|
||
const [hours, minutes] = goalData.reminderTime.split(':').map(Number);
|
||
|
||
// 创建通知内容
|
||
const notification: NotificationData = {
|
||
title: '目标提醒',
|
||
body: `${userName},该完成您的目标"${goalData.title}"了!`,
|
||
data: {
|
||
type: 'goal_reminder',
|
||
goalTitle: goalData.title,
|
||
repeatType: goalData.repeatType,
|
||
frequency: goalData.frequency
|
||
},
|
||
sound: true,
|
||
priority: 'high',
|
||
};
|
||
|
||
// 根据重复类型创建不同的通知
|
||
switch (goalData.repeatType) {
|
||
case 'daily':
|
||
// 每日重复 - 使用日历重复通知
|
||
const dailyId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
notification,
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.DAILY,
|
||
hour: hours,
|
||
minute: minutes,
|
||
}
|
||
);
|
||
notificationIds.push(dailyId);
|
||
console.log(`已安排每日目标提醒,通知ID:${dailyId}`);
|
||
break;
|
||
|
||
case 'weekly':
|
||
// 每周重复 - 为每个选中的星期几创建单独的通知
|
||
if (goalData.customRepeatRule?.weekdays && goalData.customRepeatRule.weekdays.length > 0) {
|
||
for (const weekday of goalData.customRepeatRule.weekdays) {
|
||
const weeklyId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
notification,
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
|
||
hour: hours,
|
||
minute: minutes,
|
||
weekdays: [weekday],
|
||
}
|
||
);
|
||
notificationIds.push(weeklyId);
|
||
console.log(`已安排每周目标提醒,星期${weekday},通知ID:${weeklyId}`);
|
||
}
|
||
} else {
|
||
// 默认每周重复
|
||
const weeklyId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
notification,
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
|
||
hour: hours,
|
||
minute: minutes,
|
||
}
|
||
);
|
||
notificationIds.push(weeklyId);
|
||
console.log(`已安排每周目标提醒,通知ID:${weeklyId}`);
|
||
}
|
||
break;
|
||
|
||
case 'monthly':
|
||
// 每月重复 - 为每个选中的日期创建单独的通知
|
||
if (goalData.customRepeatRule?.dayOfMonth && goalData.customRepeatRule.dayOfMonth.length > 0) {
|
||
for (const dayOfMonth of goalData.customRepeatRule.dayOfMonth) {
|
||
const monthlyId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
notification,
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
|
||
hour: hours,
|
||
minute: minutes,
|
||
dayOfMonth: dayOfMonth,
|
||
}
|
||
);
|
||
notificationIds.push(monthlyId);
|
||
console.log(`已安排每月目标提醒,${dayOfMonth}号,通知ID:${monthlyId}`);
|
||
}
|
||
} else {
|
||
// 默认每月重复
|
||
const monthlyId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
notification,
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
|
||
hour: hours,
|
||
minute: minutes,
|
||
dayOfMonth: 1,
|
||
}
|
||
);
|
||
notificationIds.push(monthlyId);
|
||
console.log(`已安排每月目标提醒,通知ID:${monthlyId}`);
|
||
}
|
||
break;
|
||
}
|
||
|
||
console.log(`目标"${goalData.title}"的定时推送已创建完成,共${notificationIds.length}个通知`);
|
||
return notificationIds;
|
||
|
||
} catch (error) {
|
||
console.error('创建目标定时推送失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 取消特定目标的所有通知
|
||
*/
|
||
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) {
|
||
await notificationService.cancelNotification(notification.identifier);
|
||
console.log(`已取消目标"${goalTitle}"的通知:${notification.identifier}`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('取消目标通知失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 营养相关的通知辅助函数
|
||
*/
|
||
export class NutritionNotificationHelpers {
|
||
/**
|
||
* 发送营养记录提醒
|
||
*/
|
||
static async sendNutritionRecordReminder(userName: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '营养记录',
|
||
body: `${userName},记得记录今天的饮食情况`,
|
||
data: { type: 'nutrition_record_reminder' },
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 安排每日午餐提醒
|
||
* @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();
|
||
|
||
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;
|
||
}
|
||
|
||
// 构建跳转到 coach 页面的深度链接
|
||
const coachUrl = buildCoachDeepLink({
|
||
action: 'diet',
|
||
subAction: 'card',
|
||
meal: 'lunch'
|
||
});
|
||
|
||
// 创建午餐提醒通知
|
||
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
{
|
||
title: '午餐记录提醒',
|
||
body: `${userName},记得记录今天的午餐情况哦!`,
|
||
data: {
|
||
type: 'lunch_reminder',
|
||
isDailyReminder: true,
|
||
meal: '午餐',
|
||
url: coachUrl // 添加深度链接
|
||
},
|
||
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) {
|
||
const coachUrl = buildCoachDeepLink({
|
||
action: 'diet',
|
||
subAction: 'card',
|
||
meal: 'lunch'
|
||
});
|
||
|
||
return notificationService.sendImmediateNotification({
|
||
title: '午餐记录提醒',
|
||
body: `${userName},记得记录今天的午餐情况哦!`,
|
||
data: {
|
||
type: 'lunch_reminder',
|
||
meal: '午餐',
|
||
url: coachUrl
|
||
},
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安排每日晚餐提醒
|
||
* @param userName 用户名
|
||
* @param hour 小时 (默认18点)
|
||
* @param minute 分钟 (默认0分)
|
||
* @returns 通知ID
|
||
*/
|
||
static async scheduleDailyDinnerReminder(
|
||
userName: string,
|
||
hour: number = 18,
|
||
minute: number = 0
|
||
): Promise<string | null> {
|
||
try {
|
||
// 检查是否已经存在晚餐提醒
|
||
const existingNotifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
const existingDinnerReminder = existingNotifications.find(
|
||
notification =>
|
||
notification.content.data?.type === 'dinner_reminder' &&
|
||
notification.content.data?.isDailyReminder === true
|
||
);
|
||
|
||
if (existingDinnerReminder) {
|
||
console.log('晚餐提醒已存在,跳过重复注册:', existingDinnerReminder.identifier);
|
||
return existingDinnerReminder.identifier;
|
||
}
|
||
|
||
// 创建晚餐提醒通知
|
||
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
{
|
||
title: '🍽️ 晚餐时光到啦!',
|
||
body: `${userName},美好的晚餐时光开始了~记得记录今天的晚餐哦!营养均衡很重要呢 💪`,
|
||
data: {
|
||
type: 'dinner_reminder',
|
||
isDailyReminder: true,
|
||
meal: '晚餐',
|
||
url: '/nutrition/records' // 直接跳转到营养记录页面
|
||
},
|
||
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 cancelDinnerReminder(): Promise<void> {
|
||
try {
|
||
const notifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
for (const notification of notifications) {
|
||
if (notification.content.data?.type === 'dinner_reminder' &&
|
||
notification.content.data?.isDailyReminder === true) {
|
||
await notificationService.cancelNotification(notification.identifier);
|
||
console.log('已取消晚餐提醒:', notification.identifier);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('取消晚餐提醒失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安排营养记录提醒
|
||
*/
|
||
static async scheduleNutritionReminders(userName: string) {
|
||
// 安排三餐提醒
|
||
const mealTimes = [
|
||
{ hour: 8, minute: 0, meal: '早餐' },
|
||
{ hour: 12, minute: 0, meal: '午餐' },
|
||
{ hour: 18, minute: 0, meal: '晚餐' },
|
||
];
|
||
|
||
const notifications = [];
|
||
|
||
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);
|
||
}
|
||
|
||
// 构建深度链接
|
||
const mealTypeMap: Record<string, string> = {
|
||
'早餐': 'breakfast',
|
||
'午餐': 'lunch',
|
||
'晚餐': 'dinner'
|
||
};
|
||
|
||
const coachUrl = buildCoachDeepLink({
|
||
action: 'diet',
|
||
subAction: 'card',
|
||
meal: mealTypeMap[mealTime.meal] as 'breakfast' | 'lunch' | 'dinner'
|
||
});
|
||
|
||
const notificationId = await notificationService.scheduleRepeatingNotification(
|
||
{
|
||
title: `${mealTime.meal}提醒`,
|
||
body: `${userName},记得记录您的${mealTime.meal}情况`,
|
||
data: {
|
||
type: 'meal_reminder',
|
||
meal: mealTime.meal,
|
||
url: coachUrl
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
},
|
||
{ days: 1 }
|
||
);
|
||
|
||
notifications.push(notificationId);
|
||
}
|
||
|
||
return notifications;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 心情相关的通知辅助函数
|
||
*/
|
||
export class MoodNotificationHelpers {
|
||
/**
|
||
* 安排每日心情提醒
|
||
* @param userName 用户名
|
||
* @param hour 小时 (默认21点)
|
||
* @param minute 分钟 (默认0分)
|
||
* @returns 通知ID
|
||
*/
|
||
static async scheduleDailyMoodReminder(
|
||
userName: string,
|
||
hour: number = 21,
|
||
minute: number = 0
|
||
): Promise<string | null> {
|
||
try {
|
||
// 检查是否已经存在心情提醒
|
||
const existingNotifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
const existingMoodReminder = existingNotifications.find(
|
||
notification =>
|
||
notification.content.data?.type === 'mood_reminder' &&
|
||
notification.content.data?.isDailyReminder === true
|
||
);
|
||
|
||
if (existingMoodReminder) {
|
||
console.log('心情提醒已存在,跳过重复注册:', existingMoodReminder.identifier);
|
||
return existingMoodReminder.identifier;
|
||
}
|
||
|
||
// 创建心情提醒通知
|
||
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
{
|
||
title: '🌙 今天过得怎么样呀?',
|
||
body: `${userName},夜深了~来记录一下今天的心情吧!每一份情感都值得被珍藏 ✨💕`,
|
||
data: {
|
||
type: 'mood_reminder',
|
||
isDailyReminder: true,
|
||
url: '/mood-statistics' // 跳转到心情统计页面
|
||
},
|
||
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 sendMoodReminder(userName: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '🌙 今天过得怎么样呀?',
|
||
body: `${userName},夜深了~来记录一下今天的心情吧!每一份情感都值得被珍藏 ✨💕`,
|
||
data: {
|
||
type: 'mood_reminder',
|
||
url: '/mood-statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 取消心情提醒
|
||
*/
|
||
static async cancelMoodReminder(): Promise<void> {
|
||
try {
|
||
const notifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
for (const notification of notifications) {
|
||
if (notification.content.data?.type === 'mood_reminder' &&
|
||
notification.content.data?.isDailyReminder === true) {
|
||
await notificationService.cancelNotification(notification.identifier);
|
||
console.log('已取消心情提醒:', notification.identifier);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('取消心情提醒失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 喝水相关的通知辅助函数
|
||
*/
|
||
export class WaterNotificationHelpers {
|
||
/**
|
||
* 检查喝水目标完成情况并发送提醒
|
||
* @param userName 用户名
|
||
* @param todayStats 今日喝水统计数据
|
||
* @param currentHour 当前小时(用于时间限制检查)
|
||
* @returns 是否发送了通知
|
||
*/
|
||
static async checkWaterGoalAndNotify(
|
||
userName: string,
|
||
todayStats: { totalAmount: number; dailyGoal: number; completionRate: number },
|
||
currentHour: number = new Date().getHours()
|
||
): Promise<boolean> {
|
||
try {
|
||
// 检查时间限制:早上9点以前和晚上9点以后不通知
|
||
if (currentHour < 9 || currentHour >= 23) {
|
||
console.log(`当前时间${currentHour}点,不在通知时间范围内(9:00-21:00),跳过喝水提醒`);
|
||
return false;
|
||
}
|
||
|
||
// 检查喝水目标是否已达成
|
||
if (todayStats.completionRate >= 100) {
|
||
console.log('喝水目标已达成,无需发送提醒');
|
||
return false;
|
||
}
|
||
|
||
// 检查是否在过去2小时内已经发送过喝水提醒,避免重复打扰
|
||
const lastNotificationKey = '@last_water_notification';
|
||
const AsyncStorage = (await import('@/utils/kvStore')).default;
|
||
const lastNotificationTime = await AsyncStorage.getItem(lastNotificationKey);
|
||
const now = new Date().getTime();
|
||
const twoHoursAgo = now - (2 * 60 * 60 * 1000); // 2小时前
|
||
|
||
if (lastNotificationTime && parseInt(lastNotificationTime) > twoHoursAgo) {
|
||
console.log('2小时内已发送过喝水提醒,跳过本次通知');
|
||
return false;
|
||
}
|
||
|
||
// 计算还需要喝多少水
|
||
const remainingAmount = todayStats.dailyGoal - todayStats.totalAmount;
|
||
const completionPercentage = Math.round(todayStats.completionRate);
|
||
|
||
// 根据完成度生成不同的提醒消息
|
||
let title = '💧 该喝水啦!';
|
||
let body = '';
|
||
|
||
if (completionPercentage < 30) {
|
||
// 完成度低于30%
|
||
const encouragingMessages = [
|
||
`${userName},今天才喝了${completionPercentage}%的水哦!还需要${Math.round(remainingAmount)}ml,记得多补水~身体会感谢您的!💙`,
|
||
`${userName},水分补充进度${completionPercentage}%,再喝${Math.round(remainingAmount)}ml就更健康啦!来一大杯清水吧~🚰`,
|
||
`${userName},喝水进度才${completionPercentage}%呢~身体需要更多水分,还差${Math.round(remainingAmount)}ml,一起加油!✨`,
|
||
];
|
||
body = encouragingMessages[Math.floor(Math.random() * encouragingMessages.length)];
|
||
} else if (completionPercentage < 60) {
|
||
// 完成度30-60%
|
||
const moderateMessages = [
|
||
`${userName},喝水进度${completionPercentage}%,还需要${Math.round(remainingAmount)}ml哦!保持这个节奏,您做得很棒!👍`,
|
||
`${userName},水分补充已完成${completionPercentage}%,再来${Math.round(remainingAmount)}ml就达标了!继续保持~💪`,
|
||
`${userName},今日饮水${completionPercentage}%完成!距离目标还有${Math.round(remainingAmount)}ml,加把劲!🌊`,
|
||
];
|
||
body = moderateMessages[Math.floor(Math.random() * moderateMessages.length)];
|
||
} else {
|
||
// 完成度60-99%
|
||
const almostDoneMessages = [
|
||
`${userName},喝水进度${completionPercentage}%,太棒了!最后${Math.round(remainingAmount)}ml就达成目标啦!🎉`,
|
||
`${userName},已经完成${completionPercentage}%了!还有${Math.round(remainingAmount)}ml就成功了,您很快就能达成今天的目标!🏆`,
|
||
`${userName},水分补充进度${completionPercentage}%,就差最后一点点!再喝${Math.round(remainingAmount)}ml就胜利了!🥳`,
|
||
];
|
||
body = almostDoneMessages[Math.floor(Math.random() * almostDoneMessages.length)];
|
||
}
|
||
|
||
// 发送通知
|
||
const notificationId = await notificationService.sendImmediateNotification({
|
||
title,
|
||
body,
|
||
data: {
|
||
type: 'water_reminder',
|
||
completionRate: completionPercentage,
|
||
remainingAmount: Math.round(remainingAmount),
|
||
dailyGoal: todayStats.dailyGoal,
|
||
currentAmount: todayStats.totalAmount,
|
||
url: '/statistics' // 跳转到统计页面查看详情
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
|
||
// 记录通知发送时间
|
||
await AsyncStorage.setItem(lastNotificationKey, now.toString());
|
||
|
||
console.log(`喝水提醒通知已发送,ID: ${notificationId},完成度: ${completionPercentage}%`);
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('检查喝水目标并发送通知失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送立即喝水提醒
|
||
* @param userName 用户名
|
||
* @param message 自定义消息(可选)
|
||
*/
|
||
static async sendWaterReminder(userName: string, message?: string) {
|
||
const defaultMessage = `${userName},记得要多喝水哦!保持身体水分充足很重要~💧`;
|
||
|
||
return notificationService.sendImmediateNotification({
|
||
title: '💧 喝水提醒',
|
||
body: message || defaultMessage,
|
||
data: {
|
||
type: 'water_reminder',
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 安排定期喝水提醒(每2小时一次,在9:00-21:00之间)
|
||
* @param userName 用户名
|
||
* @returns 通知ID数组
|
||
*/
|
||
static async scheduleRegularWaterReminders(userName: string): Promise<string[]> {
|
||
try {
|
||
const notificationIds: string[] = [];
|
||
|
||
// 检查是否已经存在定期喝水提醒
|
||
const existingNotifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
const existingWaterReminders = existingNotifications.filter(
|
||
notification =>
|
||
notification.content.data?.type === 'regular_water_reminder' &&
|
||
notification.content.data?.isRegularReminder === true
|
||
);
|
||
|
||
if (existingWaterReminders.length > 0) {
|
||
console.log('定期喝水提醒已存在,跳过重复注册');
|
||
return existingWaterReminders.map(n => n.identifier);
|
||
}
|
||
|
||
// 创建多个时间点的喝水提醒(9:00-21:00,每2小时一次)
|
||
const reminderHours = [9, 11, 13, 15, 17, 19, 21];
|
||
|
||
for (const hour of reminderHours) {
|
||
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
{
|
||
title: '💧 定时喝水提醒',
|
||
body: `${userName},该喝水啦!记得补充水分,保持身体健康~`,
|
||
data: {
|
||
type: 'regular_water_reminder',
|
||
isRegularReminder: true,
|
||
reminderHour: hour,
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
},
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.DAILY,
|
||
hour: hour,
|
||
minute: 0,
|
||
}
|
||
);
|
||
|
||
notificationIds.push(notificationId);
|
||
console.log(`已安排${hour}:00的定期喝水提醒,通知ID: ${notificationId}`);
|
||
}
|
||
|
||
console.log(`定期喝水提醒设置完成,共${notificationIds.length}个通知`);
|
||
return notificationIds;
|
||
|
||
} catch (error) {
|
||
console.error('设置定期喝水提醒失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据用户设置安排喝水提醒通知
|
||
* @param userName 用户名
|
||
* @param settings 喝水提醒设置
|
||
* @returns 通知ID数组
|
||
*/
|
||
static async scheduleCustomWaterReminders(
|
||
userName: string,
|
||
settings: {
|
||
enabled: boolean;
|
||
startTime: string; // 格式: "HH:mm"
|
||
endTime: string; // 格式: "HH:mm"
|
||
interval: number; // 分钟
|
||
}
|
||
): Promise<string[]> {
|
||
try {
|
||
const notificationIds: string[] = [];
|
||
|
||
// 如果不启用提醒,先取消所有提醒
|
||
if (!settings.enabled) {
|
||
await this.cancelAllWaterReminders();
|
||
return notificationIds;
|
||
}
|
||
|
||
// 先取消现有的喝水提醒
|
||
await this.cancelAllWaterReminders();
|
||
|
||
// 解析开始和结束时间
|
||
const [startHour, startMinute] = settings.startTime.split(':').map(Number);
|
||
const [endHour, endMinute] = settings.endTime.split(':').map(Number);
|
||
|
||
// 计算一天内所有的提醒时间点
|
||
const reminderTimes: { hour: number; minute: number }[] = [];
|
||
|
||
// 创建开始时间的 Date 对象
|
||
let currentTime = new Date();
|
||
currentTime.setHours(startHour, startMinute, 0, 0);
|
||
|
||
// 创建结束时间的 Date 对象
|
||
let endTime = new Date();
|
||
endTime.setHours(endHour, endMinute, 0, 0);
|
||
|
||
// 如果结束时间小于开始时间,说明跨天了,结束时间设为第二天
|
||
if (endTime <= currentTime) {
|
||
endTime.setDate(endTime.getDate() + 1);
|
||
}
|
||
|
||
// 生成所有的提醒时间点
|
||
while (currentTime < endTime) {
|
||
reminderTimes.push({
|
||
hour: currentTime.getHours(),
|
||
minute: currentTime.getMinutes(),
|
||
});
|
||
|
||
// 增加间隔时间
|
||
currentTime.setTime(currentTime.getTime() + settings.interval * 60 * 1000);
|
||
}
|
||
|
||
console.log(`计算出${reminderTimes.length}个喝水提醒时间点:`, reminderTimes);
|
||
|
||
// 为每个时间点创建重复通知
|
||
for (const time of reminderTimes) {
|
||
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
{
|
||
title: '💧 喝水时间到啦!',
|
||
body: `${userName},记得补充水分哦~保持身体健康!`,
|
||
data: {
|
||
type: 'custom_water_reminder',
|
||
isCustomReminder: true,
|
||
reminderHour: time.hour,
|
||
reminderMinute: time.minute,
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
},
|
||
{
|
||
type: Notifications.SchedulableTriggerInputTypes.DAILY,
|
||
hour: time.hour,
|
||
minute: time.minute,
|
||
}
|
||
);
|
||
|
||
notificationIds.push(notificationId);
|
||
console.log(`已安排${time.hour}:${String(time.minute).padStart(2, '0')}的喝水提醒,通知ID: ${notificationId}`);
|
||
}
|
||
|
||
console.log(`自定义喝水提醒设置完成,共${notificationIds.length}个通知`);
|
||
return notificationIds;
|
||
|
||
} catch (error) {
|
||
console.error('设置自定义喝水提醒失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 取消所有喝水提醒
|
||
*/
|
||
static async cancelAllWaterReminders(): Promise<void> {
|
||
try {
|
||
const notifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
for (const notification of notifications) {
|
||
if (notification.content.data?.type === 'water_reminder' ||
|
||
notification.content.data?.type === 'regular_water_reminder' ||
|
||
notification.content.data?.type === 'custom_water_reminder') {
|
||
await notificationService.cancelNotification(notification.identifier);
|
||
console.log('已取消喝水提醒:', notification.identifier);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('取消喝水提醒失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通用通知辅助函数
|
||
*/
|
||
export class GeneralNotificationHelpers {
|
||
/**
|
||
* 发送欢迎通知
|
||
*/
|
||
static async sendWelcomeNotification(userName: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '欢迎使用',
|
||
body: `${userName},欢迎来到普拉提世界!开始您的健康之旅吧`,
|
||
data: { type: 'welcome' },
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送成就通知
|
||
*/
|
||
static async sendAchievementNotification(userName: string, achievement: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '新成就',
|
||
body: `${userName},恭喜您获得了新成就:${achievement}!`,
|
||
data: { type: 'achievement', achievement },
|
||
sound: true,
|
||
priority: 'high',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送系统维护通知
|
||
*/
|
||
static async sendMaintenanceNotification(message: string) {
|
||
return notificationService.sendImmediateNotification({
|
||
title: '系统通知',
|
||
body: message,
|
||
data: { type: 'maintenance' },
|
||
sound: true,
|
||
priority: 'high',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 取消特定类型的通知
|
||
*/
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 批量发送通知
|
||
*/
|
||
static async sendBatchNotifications(notifications: NotificationData[]) {
|
||
const results = [];
|
||
|
||
for (const notification of notifications) {
|
||
try {
|
||
const id = await notificationService.sendImmediateNotification(notification);
|
||
results.push({ success: true, id, notification });
|
||
} catch (error) {
|
||
results.push({ success: false, error, notification });
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 站立提醒通知助手
|
||
*/
|
||
export class StandReminderHelpers {
|
||
/**
|
||
* 检查站立状态并发送提醒通知
|
||
*/
|
||
static async checkStandStatusAndNotify(userName: string): Promise<boolean> {
|
||
try {
|
||
console.log('检查站立状态并发送提醒通知...');
|
||
|
||
// 动态导入健康工具,避免循环依赖
|
||
const { getCurrentHourStandStatus } = await import('@/utils/health');
|
||
|
||
// 获取当前小时站立状态
|
||
const standStatus = await getCurrentHourStandStatus();
|
||
|
||
console.log('当前站立状态:', standStatus);
|
||
|
||
// 如果已经站立过,不需要提醒
|
||
if (standStatus.hasStood) {
|
||
console.log('用户当前小时已经站立,无需提醒');
|
||
return false;
|
||
}
|
||
|
||
// 检查时间范围(工作时间内提醒,避免深夜或清晨打扰)
|
||
const currentHour = new Date().getHours();
|
||
if (currentHour < 9 || currentHour >= 22) {
|
||
console.log(`当前时间${currentHour}点,不在站立提醒时间范围内`);
|
||
return false;
|
||
}
|
||
|
||
// 检查是否启用了通知
|
||
if (!(await getNotificationEnabled())) {
|
||
console.log('用户未启用通知功能,跳过站立提醒');
|
||
return false;
|
||
}
|
||
|
||
// 生成提醒消息
|
||
const reminderMessage = this.generateStandReminderMessage(userName, standStatus.standHours, standStatus.standHoursGoal);
|
||
|
||
// 发送站立提醒通知
|
||
await notificationService.sendImmediateNotification({
|
||
title: '站立提醒',
|
||
body: reminderMessage,
|
||
data: {
|
||
type: 'stand_reminder',
|
||
currentStandHours: standStatus.standHours,
|
||
standHoursGoal: standStatus.standHoursGoal,
|
||
timestamp: Date.now()
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
|
||
console.log('站立提醒通知发送成功');
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('检查站立状态并发送提醒失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成站立提醒消息
|
||
*/
|
||
private static generateStandReminderMessage(userName: string, currentStandHours: number, goalHours: number): string {
|
||
const currentHour = new Date().getHours();
|
||
const progress = Math.round((currentStandHours / goalHours) * 100);
|
||
|
||
const messages = [
|
||
`${userName},该站起来活动一下了!当前已完成${progress}%的站立目标`,
|
||
`${userName},久坐伤身,起来走走吧~已站立${currentStandHours}/${goalHours}小时`,
|
||
`${userName},站立一会儿对健康有益,目前进度${currentStandHours}/${goalHours}小时`,
|
||
`${userName},记得起身活动哦!今日站立进度${progress}%`
|
||
];
|
||
|
||
// 根据时间选择不同的消息
|
||
const messageIndex = currentHour % messages.length;
|
||
return messages[messageIndex];
|
||
}
|
||
|
||
/**
|
||
* 取消所有站立提醒通知
|
||
*/
|
||
static async cancelStandReminders(): Promise<void> {
|
||
try {
|
||
await GeneralNotificationHelpers.cancelNotificationsByType('stand_reminder');
|
||
console.log('已取消所有站立提醒通知');
|
||
} catch (error) {
|
||
console.error('取消站立提醒通知失败:', error);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 每日总结通知助手
|
||
*/
|
||
export class DailySummaryNotificationHelpers {
|
||
/**
|
||
* 获取当日数据汇总
|
||
*/
|
||
static async getDailySummaryData(date: string = new Date().toISOString().split('T')[0]) {
|
||
try {
|
||
console.log('获取每日汇总数据:', date);
|
||
|
||
// 动态导入相关服务,避免循环依赖
|
||
const { getDietRecords } = await import('@/services/dietRecords');
|
||
const { getDailyMoodCheckins } = await import('@/services/moodCheckins');
|
||
const { getWaterRecords } = await import('@/services/waterRecords');
|
||
const { workoutsApi } = await import('@/services/workoutsApi');
|
||
|
||
// 设置日期范围
|
||
const startDate = new Date(`${date}T00:00:00.000Z`).toISOString();
|
||
const endDate = new Date(`${date}T23:59:59.999Z`).toISOString();
|
||
|
||
// 并行获取各项数据
|
||
const [dietData, moodData, waterData, workoutData] = await Promise.allSettled([
|
||
getDietRecords({ startDate, endDate, limit: 100 }),
|
||
getDailyMoodCheckins(date),
|
||
getWaterRecords({ date, limit: 100 }),
|
||
workoutsApi.getTodayWorkout()
|
||
]);
|
||
|
||
// 处理饮食数据
|
||
const dietSummary = {
|
||
hasRecords: false,
|
||
mealCount: 0,
|
||
recordCount: 0
|
||
};
|
||
if (dietData.status === 'fulfilled' && dietData.value.records.length > 0) {
|
||
dietSummary.hasRecords = true;
|
||
dietSummary.recordCount = dietData.value.records.length;
|
||
dietSummary.mealCount = new Set(dietData.value.records.map(r => r.mealType)).size;
|
||
}
|
||
|
||
// 处理心情数据
|
||
const moodSummary = {
|
||
hasRecords: false,
|
||
recordCount: 0,
|
||
latestMood: null as string | null
|
||
};
|
||
if (moodData.status === 'fulfilled' && moodData.value.length > 0) {
|
||
moodSummary.hasRecords = true;
|
||
moodSummary.recordCount = moodData.value.length;
|
||
moodSummary.latestMood = moodData.value[0].moodType;
|
||
}
|
||
|
||
// 处理饮水数据
|
||
const waterSummary = {
|
||
hasRecords: false,
|
||
recordCount: 0,
|
||
totalAmount: 0,
|
||
completionRate: 0
|
||
};
|
||
if (waterData.status === 'fulfilled' && waterData.value.records.length > 0) {
|
||
waterSummary.hasRecords = true;
|
||
waterSummary.recordCount = waterData.value.records.length;
|
||
waterSummary.totalAmount = waterData.value.records.reduce((sum, r) => sum + r.amount, 0);
|
||
// 假设默认目标是2000ml,实际应该从用户设置获取
|
||
const dailyGoal = 2000;
|
||
waterSummary.completionRate = Math.round((waterSummary.totalAmount / dailyGoal) * 100);
|
||
}
|
||
|
||
// 处理锻炼数据
|
||
const workoutSummary = {
|
||
hasWorkout: false,
|
||
isCompleted: false,
|
||
exerciseCount: 0,
|
||
completedCount: 0,
|
||
duration: 0
|
||
};
|
||
if (workoutData.status === 'fulfilled' && workoutData.value) {
|
||
workoutSummary.hasWorkout = true;
|
||
workoutSummary.isCompleted = workoutData.value.status === 'completed';
|
||
workoutSummary.exerciseCount = workoutData.value.exercises?.length || 0;
|
||
workoutSummary.completedCount = workoutData.value.exercises?.filter(e => e.status === 'completed').length || 0;
|
||
if (workoutData.value.completedAt && workoutData.value.startedAt) {
|
||
workoutSummary.duration = Math.round((new Date(workoutData.value.completedAt).getTime() - new Date(workoutData.value.startedAt).getTime()) / (1000 * 60));
|
||
}
|
||
}
|
||
|
||
return {
|
||
date,
|
||
diet: dietSummary,
|
||
mood: moodSummary,
|
||
water: waterSummary,
|
||
workout: workoutSummary
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('获取每日汇总数据失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成每日总结推送消息
|
||
*/
|
||
static generateDailySummaryMessage(userName: string, summaryData: any): { title: string; body: string } {
|
||
const { diet, mood, water, workout } = summaryData;
|
||
|
||
// 计算完成的项目数量
|
||
const completedItems = [];
|
||
const encouragementItems = [];
|
||
|
||
// 饮食记录检查
|
||
if (diet.hasRecords) {
|
||
completedItems.push(`记录了${diet.mealCount}餐饮食`);
|
||
} else {
|
||
encouragementItems.push('饮食记录');
|
||
}
|
||
|
||
// 心情记录检查
|
||
if (mood.hasRecords) {
|
||
completedItems.push(`记录了心情状态`);
|
||
} else {
|
||
encouragementItems.push('心情记录');
|
||
}
|
||
|
||
// 饮水记录检查
|
||
if (water.hasRecords) {
|
||
if (water.completionRate >= 80) {
|
||
completedItems.push(`完成了${water.completionRate}%的饮水目标`);
|
||
} else {
|
||
completedItems.push(`喝水${water.completionRate}%`);
|
||
encouragementItems.push('多喝水');
|
||
}
|
||
} else {
|
||
encouragementItems.push('饮水记录');
|
||
}
|
||
|
||
// 锻炼记录检查
|
||
if (workout.hasWorkout) {
|
||
if (workout.isCompleted) {
|
||
completedItems.push(`完成了${workout.duration}分钟锻炼`);
|
||
} else {
|
||
completedItems.push(`开始了锻炼训练`);
|
||
encouragementItems.push('完成锻炼');
|
||
}
|
||
} else {
|
||
encouragementItems.push('运动锻炼');
|
||
}
|
||
|
||
// 生成标题和内容
|
||
let title = '今日健康总结';
|
||
let body = '';
|
||
|
||
if (completedItems.length > 0) {
|
||
if (completedItems.length >= 3) {
|
||
// 完成度很高的鼓励
|
||
const titles = ['今天表现棒极了!', '健康习惯养成中!', '今日收获满满!'];
|
||
title = titles[Math.floor(Math.random() * titles.length)];
|
||
body = `${userName},今天您${completedItems.join('、')},真的很棒!`;
|
||
|
||
if (encouragementItems.length > 0) {
|
||
body += `明天在${encouragementItems.join('、')}方面再加把劲哦~`;
|
||
} else {
|
||
body += '继续保持这样的好习惯!🌟';
|
||
}
|
||
} else {
|
||
// 中等完成度的鼓励
|
||
title = '今日健康小结';
|
||
body = `${userName},今天您${completedItems.join('、')},已经很不错了!`;
|
||
|
||
if (encouragementItems.length > 0) {
|
||
body += `明天记得关注一下${encouragementItems.join('、')},让健康生活更完整~`;
|
||
}
|
||
}
|
||
} else {
|
||
// 完成度较低的温柔提醒
|
||
const titles = ['明天是新的开始', '健康从每一天开始', '小步前进也是进步'];
|
||
title = titles[Math.floor(Math.random() * titles.length)];
|
||
body = `${userName},今天可能比较忙碌。明天记得关注${encouragementItems.slice(0, 2).join('和')},每一个小改变都是向健康生活迈进的一步~💪`;
|
||
}
|
||
|
||
return { title, body };
|
||
}
|
||
|
||
/**
|
||
* 发送每日总结推送
|
||
*/
|
||
static async sendDailySummaryNotification(userName: string, date?: string): Promise<boolean> {
|
||
try {
|
||
console.log('开始发送每日总结推送...');
|
||
|
||
// 检查是否启用了通知
|
||
if (!(await getNotificationEnabled())) {
|
||
console.log('用户未启用通知功能,跳过每日总结推送');
|
||
return false;
|
||
}
|
||
|
||
// 获取当日数据汇总
|
||
const summaryData = await this.getDailySummaryData(date);
|
||
console.log('每日汇总数据:', summaryData);
|
||
|
||
// 生成推送消息
|
||
const { title, body } = this.generateDailySummaryMessage(userName, summaryData);
|
||
|
||
// 发送通知
|
||
await notificationService.sendImmediateNotification({
|
||
title,
|
||
body,
|
||
data: {
|
||
type: 'daily_summary',
|
||
date: summaryData.date,
|
||
summaryData,
|
||
url: '/statistics' // 跳转到统计页面
|
||
},
|
||
sound: true,
|
||
priority: 'normal',
|
||
});
|
||
|
||
console.log('每日总结推送发送成功');
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('发送每日总结推送失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安排每日总结推送(每天晚上9点)
|
||
*/
|
||
static async scheduleDailySummaryNotification(
|
||
userName: string,
|
||
hour: number = 21,
|
||
minute: number = 0
|
||
): Promise<string | null> {
|
||
try {
|
||
// 检查是否已经存在每日总结提醒
|
||
const existingNotifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
const existingSummaryReminder = existingNotifications.find(
|
||
notification =>
|
||
notification.content.data?.type === 'daily_summary_reminder' &&
|
||
notification.content.data?.isDailyReminder === true
|
||
);
|
||
|
||
if (existingSummaryReminder) {
|
||
console.log('每日总结推送已存在,跳过重复注册:', existingSummaryReminder.identifier);
|
||
return existingSummaryReminder.identifier;
|
||
}
|
||
|
||
// 创建每日总结推送通知
|
||
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
|
||
{
|
||
title: '今日健康总结',
|
||
body: `${userName},来看看今天的健康生活总结吧~每一份记录都是成长的足迹!✨`,
|
||
data: {
|
||
type: 'daily_summary_reminder',
|
||
isDailyReminder: true,
|
||
url: '/statistics'
|
||
},
|
||
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 cancelDailySummaryNotification(): Promise<void> {
|
||
try {
|
||
const notifications = await notificationService.getAllScheduledNotifications();
|
||
|
||
for (const notification of notifications) {
|
||
if (notification.content.data?.type === 'daily_summary_reminder' &&
|
||
notification.content.data?.isDailyReminder === true) {
|
||
await notificationService.cancelNotification(notification.identifier);
|
||
console.log('已取消每日总结推送:', notification.identifier);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('取消每日总结推送失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通知模板
|
||
*/
|
||
export const NotificationTemplates = {
|
||
workout: {
|
||
start: (userName: string) => ({
|
||
title: '运动时间到',
|
||
body: `${userName},该开始今天的普拉提训练了!`,
|
||
data: { type: 'workout_start' },
|
||
sound: true,
|
||
priority: 'high' as const,
|
||
}),
|
||
complete: (userName: string, duration: number) => ({
|
||
title: '运动完成',
|
||
body: `${userName},恭喜您完成了${duration}分钟的训练!`,
|
||
data: { type: 'workout_complete' },
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
},
|
||
goal: {
|
||
achievement: (userName: string, goalName: string) => ({
|
||
title: '目标达成',
|
||
body: `${userName},恭喜您达成了目标:${goalName}!`,
|
||
data: { type: 'goal_achievement', goalName },
|
||
sound: true,
|
||
priority: 'high' as const,
|
||
}),
|
||
progress: (userName: string, goalName: string, progress: number) => ({
|
||
title: '目标进度',
|
||
body: `${userName},您的目标"${goalName}"已完成${progress}%!`,
|
||
data: { type: 'goal_progress', goalName, progress },
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
},
|
||
mood: {
|
||
reminder: (userName: string) => ({
|
||
title: '心情打卡',
|
||
body: `${userName},记得记录今天的心情状态哦`,
|
||
data: { type: 'mood_checkin_reminder' },
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
},
|
||
nutrition: {
|
||
reminder: (userName: string, meal: string) => {
|
||
const mealTypeMap: Record<string, string> = {
|
||
'早餐': 'breakfast',
|
||
'午餐': 'lunch',
|
||
'晚餐': 'dinner',
|
||
'加餐': 'snack'
|
||
};
|
||
|
||
const coachUrl = buildCoachDeepLink({
|
||
action: 'diet',
|
||
subAction: 'card',
|
||
meal: mealTypeMap[meal] as 'breakfast' | 'lunch' | 'dinner' | 'snack'
|
||
});
|
||
|
||
return {
|
||
title: `${meal}提醒`,
|
||
body: `${userName},记得记录您的${meal}情况`,
|
||
data: {
|
||
type: 'meal_reminder',
|
||
meal,
|
||
url: coachUrl
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
};
|
||
},
|
||
lunch: (userName: string) => {
|
||
const coachUrl = buildCoachDeepLink({
|
||
action: 'diet',
|
||
subAction: 'card',
|
||
meal: 'lunch'
|
||
});
|
||
|
||
return {
|
||
title: '午餐记录提醒',
|
||
body: `${userName},记得记录今天的午餐情况哦!`,
|
||
data: {
|
||
type: 'lunch_reminder',
|
||
meal: '午餐',
|
||
url: coachUrl
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
};
|
||
},
|
||
},
|
||
water: {
|
||
reminder: (userName: string, completionRate: number, remainingAmount: number) => ({
|
||
title: '💧 该喝水啦!',
|
||
body: `${userName},今日喝水进度${completionRate}%,还需要${Math.round(remainingAmount)}ml,记得补充水分~`,
|
||
data: {
|
||
type: 'water_reminder',
|
||
completionRate,
|
||
remainingAmount: Math.round(remainingAmount),
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
regular: (userName: string) => ({
|
||
title: '💧 定时喝水提醒',
|
||
body: `${userName},该喝水啦!记得补充水分,保持身体健康~`,
|
||
data: {
|
||
type: 'regular_water_reminder',
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
achievement: (userName: string) => ({
|
||
title: '🎉 喝水目标达成!',
|
||
body: `${userName},恭喜您完成了今天的喝水目标!继续保持健康的饮水习惯~`,
|
||
data: {
|
||
type: 'water_achievement',
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'high' as const,
|
||
}),
|
||
},
|
||
dailySummary: {
|
||
reminder: (userName: string) => ({
|
||
title: '今日健康总结',
|
||
body: `${userName},来看看今天的健康生活总结吧~每一份记录都是成长的足迹!✨`,
|
||
data: {
|
||
type: 'daily_summary_reminder',
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
highCompletion: (userName: string, completedItems: string[]) => ({
|
||
title: '今天表现棒极了!',
|
||
body: `${userName},今天您${completedItems.join('、')},真的很棒!继续保持这样的好习惯!🌟`,
|
||
data: {
|
||
type: 'daily_summary',
|
||
completedItems,
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
mediumCompletion: (userName: string, completedItems: string[], encouragementItems: string[]) => ({
|
||
title: '今日健康小结',
|
||
body: `${userName},今天您${completedItems.join('、')},已经很不错了!明天记得关注一下${encouragementItems.join('、')},让健康生活更完整~`,
|
||
data: {
|
||
type: 'daily_summary',
|
||
completedItems,
|
||
encouragementItems,
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
lowCompletion: (userName: string, encouragementItems: string[]) => ({
|
||
title: '明天是新的开始',
|
||
body: `${userName},今天可能比较忙碌。明天记得关注${encouragementItems.slice(0, 2).join('和')},每一个小改变都是向健康生活迈进的一步~💪`,
|
||
data: {
|
||
type: 'daily_summary',
|
||
encouragementItems,
|
||
url: '/statistics'
|
||
},
|
||
sound: true,
|
||
priority: 'normal' as const,
|
||
}),
|
||
},
|
||
};
|