feat(water): 重构饮水模块并新增自定义提醒设置功能
- 新增饮水详情页面 `/water/detail` 展示每日饮水记录与统计 - 新增饮水设置页面 `/water/settings` 支持目标与快速添加配置 - 新增喝水提醒设置页面 `/water/reminder-settings` 支持自定义时间段与间隔 - 重构 `useWaterData` Hook,支持按日期查询与实时刷新 - 新增 `WaterNotificationHelpers.scheduleCustomWaterReminders` 实现个性化提醒 - 优化心情编辑页键盘体验,新增 `KeyboardAvoidingView` 与滚动逻辑 - 升级版本号至 1.0.14 并补充路由常量 - 补充用户偏好存储字段 `waterReminderEnabled/startTime/endTime/interval` - 废弃后台定时任务中的旧版喝水提醒逻辑,改为用户手动管理
This commit is contained in:
@@ -822,6 +822,102 @@ export class WaterNotificationHelpers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户设置安排喝水提醒通知
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有喝水提醒
|
||||
*/
|
||||
@@ -831,7 +927,8 @@ export class WaterNotificationHelpers {
|
||||
|
||||
for (const notification of notifications) {
|
||||
if (notification.content.data?.type === 'water_reminder' ||
|
||||
notification.content.data?.type === 'regular_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);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ const PREFERENCES_KEYS = {
|
||||
NOTIFICATION_ENABLED: 'user_preference_notification_enabled',
|
||||
FITNESS_EXERCISE_MINUTES_INFO_DISMISSED: 'user_preference_fitness_exercise_minutes_info_dismissed',
|
||||
FITNESS_ACTIVE_HOURS_INFO_DISMISSED: 'user_preference_fitness_active_hours_info_dismissed',
|
||||
WATER_REMINDER_ENABLED: 'user_preference_water_reminder_enabled',
|
||||
WATER_REMINDER_START_TIME: 'user_preference_water_reminder_start_time',
|
||||
WATER_REMINDER_END_TIME: 'user_preference_water_reminder_end_time',
|
||||
WATER_REMINDER_INTERVAL: 'user_preference_water_reminder_interval',
|
||||
} as const;
|
||||
|
||||
// 用户偏好设置接口
|
||||
@@ -16,6 +20,10 @@ export interface UserPreferences {
|
||||
notificationEnabled: boolean;
|
||||
fitnessExerciseMinutesInfoDismissed: boolean;
|
||||
fitnessActiveHoursInfoDismissed: boolean;
|
||||
waterReminderEnabled: boolean;
|
||||
waterReminderStartTime: string; // 格式: "08:00"
|
||||
waterReminderEndTime: string; // 格式: "22:00"
|
||||
waterReminderInterval: number; // 分钟
|
||||
}
|
||||
|
||||
// 默认的用户偏好设置
|
||||
@@ -25,6 +33,10 @@ const DEFAULT_PREFERENCES: UserPreferences = {
|
||||
notificationEnabled: true, // 默认开启消息推送
|
||||
fitnessExerciseMinutesInfoDismissed: false, // 默认显示锻炼分钟说明
|
||||
fitnessActiveHoursInfoDismissed: false, // 默认显示活动小时说明
|
||||
waterReminderEnabled: true, // 默认关闭喝水提醒
|
||||
waterReminderStartTime: '08:00', // 默认开始时间早上8点
|
||||
waterReminderEndTime: '22:00', // 默认结束时间晚上10点
|
||||
waterReminderInterval: 60, // 默认提醒间隔60分钟
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -37,6 +49,10 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
|
||||
const notificationEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.NOTIFICATION_ENABLED);
|
||||
const fitnessExerciseMinutesInfoDismissed = await AsyncStorage.getItem(PREFERENCES_KEYS.FITNESS_EXERCISE_MINUTES_INFO_DISMISSED);
|
||||
const fitnessActiveHoursInfoDismissed = await AsyncStorage.getItem(PREFERENCES_KEYS.FITNESS_ACTIVE_HOURS_INFO_DISMISSED);
|
||||
const waterReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_ENABLED);
|
||||
const waterReminderStartTime = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_START_TIME);
|
||||
const waterReminderEndTime = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME);
|
||||
const waterReminderInterval = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL);
|
||||
|
||||
return {
|
||||
quickWaterAmount: quickWaterAmount ? parseInt(quickWaterAmount, 10) : DEFAULT_PREFERENCES.quickWaterAmount,
|
||||
@@ -44,6 +60,10 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
|
||||
notificationEnabled: notificationEnabled !== null ? notificationEnabled === 'true' : DEFAULT_PREFERENCES.notificationEnabled,
|
||||
fitnessExerciseMinutesInfoDismissed: fitnessExerciseMinutesInfoDismissed !== null ? fitnessExerciseMinutesInfoDismissed === 'true' : DEFAULT_PREFERENCES.fitnessExerciseMinutesInfoDismissed,
|
||||
fitnessActiveHoursInfoDismissed: fitnessActiveHoursInfoDismissed !== null ? fitnessActiveHoursInfoDismissed === 'true' : DEFAULT_PREFERENCES.fitnessActiveHoursInfoDismissed,
|
||||
waterReminderEnabled: waterReminderEnabled !== null ? waterReminderEnabled === 'true' : DEFAULT_PREFERENCES.waterReminderEnabled,
|
||||
waterReminderStartTime: waterReminderStartTime || DEFAULT_PREFERENCES.waterReminderStartTime,
|
||||
waterReminderEndTime: waterReminderEndTime || DEFAULT_PREFERENCES.waterReminderEndTime,
|
||||
waterReminderInterval: waterReminderInterval ? parseInt(waterReminderInterval, 10) : DEFAULT_PREFERENCES.waterReminderInterval,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取用户偏好设置失败:', error);
|
||||
@@ -185,6 +205,163 @@ export const getFitnessActiveHoursInfoDismissed = async (): Promise<boolean> =>
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置喝水提醒开关
|
||||
* @param enabled 是否开启喝水提醒
|
||||
*/
|
||||
export const setWaterReminderEnabled = async (enabled: boolean): Promise<void> => {
|
||||
try {
|
||||
await AsyncStorage.setItem(PREFERENCES_KEYS.WATER_REMINDER_ENABLED, enabled.toString());
|
||||
} catch (error) {
|
||||
console.error('设置喝水提醒开关失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取喝水提醒开关状态
|
||||
*/
|
||||
export const getWaterReminderEnabled = async (): Promise<boolean> => {
|
||||
try {
|
||||
const enabled = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_ENABLED);
|
||||
return enabled !== null ? enabled === 'true' : DEFAULT_PREFERENCES.waterReminderEnabled;
|
||||
} catch (error) {
|
||||
console.error('获取喝水提醒开关状态失败:', error);
|
||||
return DEFAULT_PREFERENCES.waterReminderEnabled;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置喝水提醒开始时间
|
||||
* @param startTime 开始时间,格式为 "HH:mm"
|
||||
*/
|
||||
export const setWaterReminderStartTime = async (startTime: string): Promise<void> => {
|
||||
try {
|
||||
await AsyncStorage.setItem(PREFERENCES_KEYS.WATER_REMINDER_START_TIME, startTime);
|
||||
} catch (error) {
|
||||
console.error('设置喝水提醒开始时间失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取喝水提醒开始时间
|
||||
*/
|
||||
export const getWaterReminderStartTime = async (): Promise<string> => {
|
||||
try {
|
||||
const startTime = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_START_TIME);
|
||||
return startTime || DEFAULT_PREFERENCES.waterReminderStartTime;
|
||||
} catch (error) {
|
||||
console.error('获取喝水提醒开始时间失败:', error);
|
||||
return DEFAULT_PREFERENCES.waterReminderStartTime;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置喝水提醒结束时间
|
||||
* @param endTime 结束时间,格式为 "HH:mm"
|
||||
*/
|
||||
export const setWaterReminderEndTime = async (endTime: string): Promise<void> => {
|
||||
try {
|
||||
await AsyncStorage.setItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME, endTime);
|
||||
} catch (error) {
|
||||
console.error('设置喝水提醒结束时间失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取喝水提醒结束时间
|
||||
*/
|
||||
export const getWaterReminderEndTime = async (): Promise<string> => {
|
||||
try {
|
||||
const endTime = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME);
|
||||
return endTime || DEFAULT_PREFERENCES.waterReminderEndTime;
|
||||
} catch (error) {
|
||||
console.error('获取喝水提醒结束时间失败:', error);
|
||||
return DEFAULT_PREFERENCES.waterReminderEndTime;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置喝水提醒间隔时间
|
||||
* @param interval 间隔时间(分钟),范围 30-180
|
||||
*/
|
||||
export const setWaterReminderInterval = async (interval: number): Promise<void> => {
|
||||
try {
|
||||
// 确保值在合理范围内(30-180分钟)
|
||||
const validInterval = Math.max(30, Math.min(180, interval));
|
||||
await AsyncStorage.setItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL, validInterval.toString());
|
||||
} catch (error) {
|
||||
console.error('设置喝水提醒间隔时间失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取喝水提醒间隔时间
|
||||
*/
|
||||
export const getWaterReminderInterval = async (): Promise<number> => {
|
||||
try {
|
||||
const interval = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL);
|
||||
return interval ? parseInt(interval, 10) : DEFAULT_PREFERENCES.waterReminderInterval;
|
||||
} catch (error) {
|
||||
console.error('获取喝水提醒间隔时间失败:', error);
|
||||
return DEFAULT_PREFERENCES.waterReminderInterval;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取完整的喝水提醒配置
|
||||
*/
|
||||
export const getWaterReminderSettings = async () => {
|
||||
try {
|
||||
const [enabled, startTime, endTime, interval] = await Promise.all([
|
||||
getWaterReminderEnabled(),
|
||||
getWaterReminderStartTime(),
|
||||
getWaterReminderEndTime(),
|
||||
getWaterReminderInterval(),
|
||||
]);
|
||||
|
||||
return {
|
||||
enabled,
|
||||
startTime,
|
||||
endTime,
|
||||
interval,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取喝水提醒配置失败:', error);
|
||||
return {
|
||||
enabled: DEFAULT_PREFERENCES.waterReminderEnabled,
|
||||
startTime: DEFAULT_PREFERENCES.waterReminderStartTime,
|
||||
endTime: DEFAULT_PREFERENCES.waterReminderEndTime,
|
||||
interval: DEFAULT_PREFERENCES.waterReminderInterval,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量设置喝水提醒配置
|
||||
*/
|
||||
export const setWaterReminderSettings = async (settings: {
|
||||
enabled: boolean;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
interval: number;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
await Promise.all([
|
||||
setWaterReminderEnabled(settings.enabled),
|
||||
setWaterReminderStartTime(settings.startTime),
|
||||
setWaterReminderEndTime(settings.endTime),
|
||||
setWaterReminderInterval(settings.interval),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('批量设置喝水提醒配置失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置所有用户偏好设置为默认值
|
||||
*/
|
||||
@@ -194,6 +371,10 @@ export const resetUserPreferences = async (): Promise<void> => {
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.NOTIFICATION_ENABLED);
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.FITNESS_EXERCISE_MINUTES_INFO_DISMISSED);
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.FITNESS_ACTIVE_HOURS_INFO_DISMISSED);
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_ENABLED);
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_START_TIME);
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME);
|
||||
await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL);
|
||||
} catch (error) {
|
||||
console.error('重置用户偏好设置失败:', error);
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user