feat: 增强通知功能及用户体验
- 在 Bootstrapper 组件中新增通知服务初始化逻辑,注册每日午餐提醒 - 在 CoachScreen 中优化欢迎消息生成逻辑,整合用户配置文件数据 - 更新 GoalsScreen 组件,优化目标创建时的通知设置逻辑 - 在 NotificationTest 组件中添加调试通知状态功能,提升开发便利性 - 新增 NutritionNotificationHelpers 中的午餐提醒功能,支持每日提醒设置 - 更新相关文档,详细描述新功能和使用方法
This commit is contained in:
@@ -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-6,0为周日,仅用于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 {
|
||||
|
||||
Reference in New Issue
Block a user