feat: add nutrition and mood reminder settings

- Implemented nutrition and mood reminder toggles in notification settings screen.
- Added corresponding utility functions for managing nutrition and mood reminder preferences.
- Updated user preferences interface to include nutrition and mood reminder states.
- Enhanced localization for new reminder settings and alerts.
- Incremented iOS app version to 1.0.30.
This commit is contained in:
2025-11-23 22:47:54 +08:00
parent bcb910140e
commit 8cbf6be50a
7 changed files with 359 additions and 151 deletions

View File

@@ -24,7 +24,7 @@ import { createWaterRecordAction } from '@/store/waterSlice';
import { loadActiveFastingSchedule } from '@/utils/fasting'; import { loadActiveFastingSchedule } from '@/utils/fasting';
import { initializeHealthPermissions } from '@/utils/health'; import { initializeHealthPermissions } from '@/utils/health';
import { MoodNotificationHelpers, NutritionNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers'; import { MoodNotificationHelpers, NutritionNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
import { getWaterReminderSettings } from '@/utils/userPreferences'; import { getMoodReminderEnabled, getNutritionReminderEnabled, getWaterReminderSettings } from '@/utils/userPreferences';
import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync'; import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native'; import { AppState, AppStateStatus } from 'react-native';
@@ -221,40 +221,57 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
try { try {
logger.info('📢 开始批量注册通知提醒...'); logger.info('📢 开始批量注册通知提醒...');
// 并行注册所有通知,提高效率 // 获取用户偏好设置
await Promise.all([ const [nutritionReminderEnabled, moodReminderEnabled, waterSettings] = await Promise.all([
// 营养提醒 getNutritionReminderEnabled(),
NutritionNotificationHelpers.scheduleDailyLunchReminder(profile.name || '').then(() => getMoodReminderEnabled(),
logger.info('✅ 午餐提醒已注册') getWaterReminderSettings(),
),
NutritionNotificationHelpers.scheduleDailyDinnerReminder(profile.name || '').then(() =>
logger.info('✅ 晚餐提醒已注册')
),
// 心情提醒
MoodNotificationHelpers.scheduleDailyMoodReminder(profile.name || '').then(() =>
logger.info('✅ 心情提醒已注册')
),
// 喝水提醒 - 需要先检查设置
getWaterReminderSettings().then(settings => {
if (settings.enabled) {
// 如果使用的是自定义提醒scheduleCustomWaterReminders 会被调用(通常在设置页面保存时)
// 但为了保险起见,这里也可以根据设置类型来决定调用哪个
// 目前逻辑似乎是 scheduleRegularWaterReminders 是默认的/旧的逻辑?
// 查看 notificationHelpers.tsscheduleRegularWaterReminders 是每2小时一次的固定逻辑
// 而 scheduleCustomWaterReminders 是根据用户设置的时间段和间隔
// 如果用户开启了提醒,应该使用 scheduleCustomWaterReminders
WaterNotificationHelpers.scheduleCustomWaterReminders(profile.name || '用户', settings).then(() =>
logger.info('✅ 自定义喝水提醒已注册')
);
} else {
logger.info(' 用户未开启喝水提醒,跳过注册');
}
}),
]); ]);
// 准备所有通知注册任务
const notificationTasks = [];
// 营养提醒 - 根据用户设置决定是否注册
if (nutritionReminderEnabled) {
notificationTasks.push(
NutritionNotificationHelpers.scheduleDailyLunchReminder(profile.name || '').then(() =>
logger.info('✅ 午餐提醒已注册')
),
NutritionNotificationHelpers.scheduleDailyDinnerReminder(profile.name || '').then(() =>
logger.info('✅ 晚餐提醒已注册')
)
);
} else {
logger.info(' 用户未开启营养提醒,跳过注册');
}
// 心情提醒 - 根据用户设置决定是否注册
if (moodReminderEnabled) {
notificationTasks.push(
MoodNotificationHelpers.scheduleDailyMoodReminder(profile.name || '').then(() =>
logger.info('✅ 心情提醒已注册')
)
);
} else {
logger.info(' 用户未开启心情提醒,跳过注册');
}
// 喝水提醒 - 根据用户设置决定是否注册
if (waterSettings.enabled) {
notificationTasks.push(
WaterNotificationHelpers.scheduleCustomWaterReminders(profile.name || '用户', waterSettings).then(() =>
logger.info('✅ 自定义喝水提醒已注册')
)
);
} else {
logger.info(' 用户未开启喝水提醒,跳过注册');
}
// 并行执行所有通知注册任务
if (notificationTasks.length > 0) {
await Promise.all(notificationTasks);
}
// 检查断食通知(如果有活跃计划) // 检查断食通知(如果有活跃计划)
const fastingSchedule = store.getState().fasting.activeSchedule; const fastingSchedule = store.getState().fasting.activeSchedule;
if (fastingSchedule) { if (fastingSchedule) {

View File

@@ -3,9 +3,13 @@ import { useI18n } from '@/hooks/useI18n';
import { useNotifications } from '@/hooks/useNotifications'; import { useNotifications } from '@/hooks/useNotifications';
import { import {
getMedicationReminderEnabled, getMedicationReminderEnabled,
getMoodReminderEnabled,
getNotificationEnabled, getNotificationEnabled,
getNutritionReminderEnabled,
setMedicationReminderEnabled, setMedicationReminderEnabled,
setNotificationEnabled setMoodReminderEnabled,
setNotificationEnabled,
setNutritionReminderEnabled
} from '@/utils/userPreferences'; } from '@/utils/userPreferences';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
@@ -24,17 +28,23 @@ export default function NotificationSettingsScreen() {
// 通知设置状态 // 通知设置状态
const [notificationEnabled, setNotificationEnabledState] = useState(false); const [notificationEnabled, setNotificationEnabledState] = useState(false);
const [medicationReminderEnabled, setMedicationReminderEnabledState] = useState(false); const [medicationReminderEnabled, setMedicationReminderEnabledState] = useState(false);
const [nutritionReminderEnabled, setNutritionReminderEnabledState] = useState(false);
const [moodReminderEnabled, setMoodReminderEnabledState] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
// 加载通知设置 // 加载通知设置
const loadNotificationSettings = useCallback(async () => { const loadNotificationSettings = useCallback(async () => {
try { try {
const [notification, medicationReminder] = await Promise.all([ const [notification, medicationReminder, nutritionReminder, moodReminder] = await Promise.all([
getNotificationEnabled(), getNotificationEnabled(),
getMedicationReminderEnabled(), getMedicationReminderEnabled(),
getNutritionReminderEnabled(),
getMoodReminderEnabled(),
]); ]);
setNotificationEnabledState(notification); setNotificationEnabledState(notification);
setMedicationReminderEnabledState(medicationReminder); setMedicationReminderEnabledState(medicationReminder);
setNutritionReminderEnabledState(nutritionReminder);
setMoodReminderEnabledState(moodReminder);
} catch (error) { } catch (error) {
console.error('Failed to load notification settings:', error); console.error('Failed to load notification settings:', error);
} finally { } finally {
@@ -87,9 +97,13 @@ export default function NotificationSettingsScreen() {
// 关闭推送,保存用户偏好设置 // 关闭推送,保存用户偏好设置
await setNotificationEnabled(false); await setNotificationEnabled(false);
setNotificationEnabledState(false); setNotificationEnabledState(false);
// 关闭总开关时,也关闭药品提醒 // 关闭总开关时,也关闭所有提醒
await setMedicationReminderEnabled(false); await setMedicationReminderEnabled(false);
setMedicationReminderEnabledState(false); setMedicationReminderEnabledState(false);
await setNutritionReminderEnabled(false);
setNutritionReminderEnabledState(false);
await setMoodReminderEnabled(false);
setMoodReminderEnabledState(false);
} catch (error) { } catch (error) {
console.error('Failed to disable push notifications:', error); console.error('Failed to disable push notifications:', error);
Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.saveFailed')); Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.saveFailed'));
@@ -118,6 +132,48 @@ export default function NotificationSettingsScreen() {
} }
}; };
// 处理营养通知提醒开关变化
const handleNutritionReminderToggle = async (value: boolean) => {
try {
await setNutritionReminderEnabled(value);
setNutritionReminderEnabledState(value);
if (value) {
// 发送测试通知
await sendNotification({
title: t('notificationSettings.alerts.nutritionReminderEnabled.title'),
body: t('notificationSettings.alerts.nutritionReminderEnabled.body'),
sound: true,
priority: 'high',
});
}
} catch (error) {
console.error('Failed to set nutrition reminder:', error);
Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.nutritionReminderFailed'));
}
};
// 处理心情通知提醒开关变化
const handleMoodReminderToggle = async (value: boolean) => {
try {
await setMoodReminderEnabled(value);
setMoodReminderEnabledState(value);
if (value) {
// 发送测试通知
await sendNotification({
title: t('notificationSettings.alerts.moodReminderEnabled.title'),
body: t('notificationSettings.alerts.moodReminderEnabled.body'),
sound: true,
priority: 'high',
});
}
} catch (error) {
console.error('Failed to set mood reminder:', error);
Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.moodReminderFailed'));
}
};
// 返回按钮 // 返回按钮
const BackButton = () => ( const BackButton = () => (
<TouchableOpacity <TouchableOpacity
@@ -247,6 +303,34 @@ export default function NotificationSettingsScreen() {
</View> </View>
</View> </View>
{/* 营养提醒部分 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.nutritionReminder')}</Text>
<View style={styles.card}>
<SwitchItem
title={t('notificationSettings.items.nutritionReminder.title')}
description={t('notificationSettings.items.nutritionReminder.description')}
value={nutritionReminderEnabled}
onValueChange={handleNutritionReminderToggle}
disabled={!notificationEnabled}
/>
</View>
</View>
{/* 心情提醒部分 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.moodReminder')}</Text>
<View style={styles.card}>
<SwitchItem
title={t('notificationSettings.items.moodReminder.title')}
description={t('notificationSettings.items.moodReminder.description')}
value={moodReminderEnabled}
onValueChange={handleMoodReminderToggle}
disabled={!notificationEnabled}
/>
</View>
</View>
{/* 说明部分 */} {/* 说明部分 */}
<View style={styles.section}> <View style={styles.section}>
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.description')}</Text> <Text style={styles.sectionTitle}>{t('notificationSettings.sections.description')}</Text>

View File

@@ -29,6 +29,7 @@ const WORKOUT_TYPES = [
{ key: 'walking', label: '步行' }, { key: 'walking', label: '步行' },
{ key: 'other', label: '其他运动' }, { key: 'other', label: '其他运动' },
]; ];
const WORKOUT_TYPE_KEYS = WORKOUT_TYPES.map(type => type.key);
export default function WorkoutNotificationSettingsScreen() { export default function WorkoutNotificationSettingsScreen() {
const safeAreaTop = useSafeAreaTop() const safeAreaTop = useSafeAreaTop()
@@ -80,16 +81,18 @@ export default function WorkoutNotificationSettingsScreen() {
}; };
const handleWorkoutTypeToggle = (workoutType: string) => { const handleWorkoutTypeToggle = (workoutType: string) => {
const currentTypes = preferences.enabledWorkoutTypes; const currentTypes = preferences.enabledWorkoutTypes.length === 0
let newTypes: string[]; ? [...WORKOUT_TYPE_KEYS] // 空数组表示全部启用,先展开成完整列表,避免影响其他开关的当前状态
: [...preferences.enabledWorkoutTypes];
if (currentTypes.includes(workoutType)) { const nextTypes = currentTypes.includes(workoutType)
newTypes = currentTypes.filter(type => type !== workoutType); ? currentTypes.filter(type => type !== workoutType)
} else { : [...currentTypes, workoutType];
newTypes = [...currentTypes, workoutType];
}
savePreferences({ enabledWorkoutTypes: newTypes }); // 如果全部类型都开启,回退为空数组表示“全部启用”,以保持原有存储约定
const normalizedTypes = nextTypes.length === WORKOUT_TYPE_KEYS.length ? [] : nextTypes;
savePreferences({ enabledWorkoutTypes: normalizedTypes });
}; };
const handleReset = () => { const handleReset = () => {

View File

@@ -825,6 +825,8 @@ const notificationSettingsResources = {
sections: { sections: {
notifications: '通知设置', notifications: '通知设置',
medicationReminder: '药品提醒', medicationReminder: '药品提醒',
nutritionReminder: '营养提醒',
moodReminder: '心情提醒',
description: '说明', description: '说明',
}, },
items: { items: {
@@ -836,9 +838,17 @@ const notificationSettingsResources = {
title: '药品通知提醒', title: '药品通知提醒',
description: '在用药时间接收提醒通知', description: '在用药时间接收提醒通知',
}, },
nutritionReminder: {
title: '营养记录提醒',
description: '在用餐时间接收营养记录提醒',
},
moodReminder: {
title: '心情记录提醒',
description: '在晚间接收心情记录提醒',
},
}, },
description: { description: {
text: '• 消息推送是所有通知的总开关\n• 药品通知提醒需要在消息推送开启后才能使用\n• 您可以在系统设置中管理通知权限\n• 关闭消息推送将停止所有应用通知', text: '• 消息推送是所有通知的总开关\n• 各类提醒需要在消息推送开启后才能使用\n• 您可以在系统设置中管理通知权限\n• 关闭消息推送将停止所有应用通知',
}, },
alerts: { alerts: {
permissionDenied: { permissionDenied: {
@@ -852,6 +862,8 @@ const notificationSettingsResources = {
message: '请求通知权限失败', message: '请求通知权限失败',
saveFailed: '保存设置失败', saveFailed: '保存设置失败',
medicationReminderFailed: '设置药品提醒失败', medicationReminderFailed: '设置药品提醒失败',
nutritionReminderFailed: '设置营养提醒失败',
moodReminderFailed: '设置心情提醒失败',
}, },
notificationsEnabled: { notificationsEnabled: {
title: '通知已开启', title: '通知已开启',
@@ -861,6 +873,14 @@ const notificationSettingsResources = {
title: '药品提醒已开启', title: '药品提醒已开启',
body: '您将在用药时间收到提醒通知', body: '您将在用药时间收到提醒通知',
}, },
nutritionReminderEnabled: {
title: '营养提醒已开启',
body: '您将在用餐时间收到营养记录提醒',
},
moodReminderEnabled: {
title: '心情提醒已开启',
body: '您将在晚间收到心情记录提醒',
},
}, },
}; };
@@ -1597,6 +1617,8 @@ const resources = {
sections: { sections: {
notifications: 'Notification Settings', notifications: 'Notification Settings',
medicationReminder: 'Medication Reminder', medicationReminder: 'Medication Reminder',
nutritionReminder: 'Nutrition Reminder',
moodReminder: 'Mood Reminder',
description: 'Description', description: 'Description',
}, },
items: { items: {
@@ -1608,9 +1630,17 @@ const resources = {
title: 'Medication Reminder', title: 'Medication Reminder',
description: 'Receive reminder notifications at medication time', description: 'Receive reminder notifications at medication time',
}, },
nutritionReminder: {
title: 'Nutrition Record Reminder',
description: 'Receive nutrition record reminders at meal times',
},
moodReminder: {
title: 'Mood Record Reminder',
description: 'Receive mood record reminders in the evening',
},
}, },
description: { description: {
text: '• Push notifications is the master switch for all notifications\n• Medication reminder requires push notifications to be enabled\n• You can manage notification permissions in system settings\n• Disabling push notifications will stop all app notifications', text: '• Push notifications is the master switch for all notifications\n• Various reminders require push notifications to be enabled\n• You can manage notification permissions in system settings\n• Disabling push notifications will stop all app notifications',
}, },
alerts: { alerts: {
permissionDenied: { permissionDenied: {
@@ -1624,6 +1654,8 @@ const resources = {
message: 'Failed to request notification permission', message: 'Failed to request notification permission',
saveFailed: 'Failed to save settings', saveFailed: 'Failed to save settings',
medicationReminderFailed: 'Failed to set medication reminder', medicationReminderFailed: 'Failed to set medication reminder',
nutritionReminderFailed: 'Failed to set nutrition reminder',
moodReminderFailed: 'Failed to set mood reminder',
}, },
notificationsEnabled: { notificationsEnabled: {
title: 'Notifications Enabled', title: 'Notifications Enabled',
@@ -1633,6 +1665,14 @@ const resources = {
title: 'Medication Reminder Enabled', title: 'Medication Reminder Enabled',
body: 'You will receive reminder notifications at medication time', body: 'You will receive reminder notifications at medication time',
}, },
nutritionReminderEnabled: {
title: 'Nutrition Reminder Enabled',
body: 'You will receive nutrition record reminders at meal times',
},
moodReminderEnabled: {
title: 'Mood Reminder Enabled',
body: 'You will receive mood record reminders in the evening',
},
}, },
}, },
tabBarConfig: { tabBarConfig: {

View File

@@ -27,7 +27,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.29</string> <string>1.0.30</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

View File

@@ -2679,127 +2679,127 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga" :path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS: SPEC CHECKSUMS:
EXApplication: 296622817d459f46b6c5fe8691f4aac44d2b79e7 EXApplication: a9d1c46d473d36f61302a9a81db2379441f3f094
EXConstants: fd688cef4e401dcf798a021cfb5d87c890c30ba3 EXConstants: e6e50cdfcb4524f40121d1fdcff24e97b7dcd2fd
EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05 EXImageLoader: e501c001bc40b8326605e82e6e80363c80fe06b5
EXNotifications: 7cff475adb5d7a255a9ea46bbd2589cb3b454506 EXNotifications: 7aab54f0e5f3023122bc95699eaff7c52bacb559
Expo: 27ae59be9be4feab2b1c1ae06550752c524ca558 Expo: 795e9f87aca407bf92895d54ae5f7777fc1f3fbc
ExpoAppleAuthentication: bc9de6e9ff3340604213ab9031d4c4f7f802623e ExpoAppleAuthentication: 414e4316f8e25a2afbc3943cf725579c910f24b8
ExpoAsset: 9ba6fbd677fb8e241a3899ac00fa735bc911eadf ExpoAsset: 3c3b7dd9b1318846a02ef05ce420e63d542aeb9f
ExpoBackgroundTask: e0d201d38539c571efc5f9cb661fae8ab36ed61b ExpoBackgroundTask: e048da30cd2d669c5ba20d5d704bee8dd6da320c
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f ExpoBlur: b5b7a26572b3c33a11f0b2aa2f95c17c4c393b76
ExpoCamera: e75f6807a2c047f3338bbadd101af4c71a1d13a5 ExpoCamera: 6d0c5bc68bc8de669f1ecd4242284de0827c4431
ExpoFileSystem: b79eadbda7b7f285f378f95f959cc9313a1c9c61 ExpoFileSystem: 56f081328f5b48a6dcc8302eee51d4f2d9d0049b
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961 ExpoFont: b881d43057dceb7b31ff767b24f612609e80f60f
ExpoGlassEffect: 779c46bd04ea47ba4726efb73267b5bcc6abd664 ExpoGlassEffect: d35ec1a8e9d84492f23755d3020a6a81a20bd585
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84 ExpoHaptics: b48d913e7e5f23816c6f130e525c9a6501b160b5
ExpoHead: e317214fa14edeaf17748d39ec9e550a3d1194fb ExpoHead: aa5f5a8afaa9bd4969bfdd6d5b76681e2490fe5b
ExpoImage: 9c3428921c536ab29e5c6721d001ad5c1f469566 ExpoImage: 6eb842cd07817402640545c41884dd7f5fbfbca5
ExpoImagePicker: d251aab45a1b1857e4156fed88511b278b4eee1c ExpoImagePicker: bd0a5c81d7734548f6908a480609257e85d19ea8
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe ExpoKeepAwake: 3f5e3ac53627849174f3603271df8e08f174ed4a
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27 ExpoLinearGradient: f9e7182e5253d53b2de4134b69d70bbfc2d50588
ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d ExpoLinking: f5c171877e118e792cb9a77e5ade0b080d899b14
ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca ExpoLocalization: 6c6f0f89ad2822001ab0bc2eb6d4d980c77f080c
ExpoMediaLibrary: 641a6952299b395159ccd459bd8f5f6764bf55fe ExpoMediaLibrary: 648cee3f5dcba13410ec9cc8ac9a426e89a61a31
ExpoModulesCore: 5f20603cf25698682d7c43c05fbba8c748b189d2 ExpoModulesCore: ed46799cfdf75784ee3ca37dac982c5298683e83
ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f ExpoQuickActions: 62b9db8a20618be1cc19efa3b562ac963c803d58
ExpoSplashScreen: cbb839de72110dea1851dd3e85080b7923af2540 ExpoSplashScreen: 9d2ff8fa08f2c00336a83f93bebffed3a8312519
ExpoSQLite: 7fa091ba5562474093fef09be644161a65e11b3f ExpoSQLite: f9d1202877e12bfa78a58309a3977ee4ea0b1314
ExpoSymbols: 1ae04ce686de719b9720453b988d8bc5bf776c68 ExpoSymbols: ef7b8ac77ac2d496b1bc3f0f7daf5e19c3a9933a
ExpoSystemUI: 2761aa6875849af83286364811d46e8ed8ea64c7 ExpoSystemUI: 9441d46a8efbf9224d1b2e6b18042452ffd0ed79
ExpoUI: b99a1d1ef5352a60bebf4f4fd3a50d2f896ae804 ExpoUI: 821b058da921ea4aa6172b36d080991ea6fb2fae
ExpoWebBrowser: d04a0d6247a0bea4519fbc2ea816610019ad83e0 ExpoWebBrowser: 51218ce6ef35ea769e33409aac87fea3df4b919d
EXTaskManager: cbbb80cbccea6487ccca0631809fbba2ed3e5271 EXTaskManager: 53f87ed11659341c3f3f02c0041498ef293f5684
FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12 FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12
hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
lottie-ios: a881093fab623c467d3bce374367755c272bdd59 lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c lottie-react-native: 97a11537edc72d0763edab0c83e8cc8a0b9d8484
PurchasesHybridCommon: b7b4eafb55fbaaac19b4c36d4082657a3f0d8490 PurchasesHybridCommon: b7b4eafb55fbaaac19b4c36d4082657a3f0d8490
RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990 RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990
RCTRequired: 8f3cfc90cc25cf6e420ddb3e7caaaabc57df6043 RCTRequired: 8f3cfc90cc25cf6e420ddb3e7caaaabc57df6043
RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c
React: 914f8695f9bf38e6418228c2ffb70021e559f92f React: 914f8695f9bf38e6418228c2ffb70021e559f92f
React-callinvoker: 1c0808402aee0c6d4a0d8e7220ce6547af9fba71 React-callinvoker: 1c0808402aee0c6d4a0d8e7220ce6547af9fba71
React-Core: c61410ef0ca6055e204a963992e363227e0fd1c5 React-Core: 4ae98f9e8135b8ddbd7c98730afb6fdae883db90
React-Core-prebuilt: 02f0ad625ddd47463c009c2d0c5dd35c0d982599 React-Core-prebuilt: 8f4cca589c14e8cf8fc6db4587ef1c2056b5c151
React-CoreModules: 1f6d1744b5f9f2ec684a4bb5ced25370f87e5382 React-CoreModules: e878a90bb19b8f3851818af997dbae3b3b0a27ac
React-cxxreact: 3af79478e8187b63ffc22b794cd42d3fc1f1f2da React-cxxreact: 28af9844f6dc87be1385ab521fbfb3746f19563c
React-debug: 6328c2228e268846161f10082e80dc69eac2e90a React-debug: 6328c2228e268846161f10082e80dc69eac2e90a
React-defaultsnativemodule: d635ef36d755321e5d6fc065bd166b2c5a0e9833 React-defaultsnativemodule: afc9d809ec75780f39464a6949c07987fbea488c
React-domnativemodule: dd28f6d96cd21236e020be2eff6fe0b7d4ec3b66 React-domnativemodule: 91a233260411d41f27f67aa1358b7f9f0bfd101d
React-Fabric: 2e32c3fdbb1fbcf5fde54607e3abe453c6652ce2 React-Fabric: 21f349b5e93f305a3c38c885902683a9c79cf983
React-FabricComponents: 5ed0cdb81f6b91656cb4d3be432feaa28a58071a React-FabricComponents: 47ac634cc9ecc64b30a9997192f510eebe4177e4
React-FabricImage: 2bc714f818cb24e454f5d3961864373271b2faf8 React-FabricImage: 21873acd6d4a51a0b97c133141051c7acb11cc86
React-featureflags: 847642f41fa71ad4eec5e0351badebcad4fe6171 React-featureflags: 653f469f0c3c9dc271d610373e3b6e66a9fd847d
React-featureflagsnativemodule: c868a544b2c626fa337bcbd364b1befe749f0d3f React-featureflagsnativemodule: c91a8a3880e0f4838286402241ead47db43aed28
React-graphics: 192ec701def5b3f2a07db2814dfba5a44986cff6 React-graphics: b4bdb0f635b8048c652a5d2b73eb8b1ddd950f24
React-hermes: e875778b496c86d07ab2ccaa36a9505d248a254b React-hermes: fcfad3b917400f49026f3232561e039c9d1c34bf
React-idlecallbacksnativemodule: 4d57965cdf82c14ee3b337189836cd8491632b76 React-idlecallbacksnativemodule: 8cb83207e39f8179ac1d344b6177c6ab3ccebcdc
React-ImageManager: bd0b99e370b13de82c9cd15f0f08144ff3de079e React-ImageManager: 396128004783fc510e629124dce682d38d1088e7
React-jserrorhandler: a2fdef4cbcfdcdf3fa9f5d1f7190f7fd4535248d React-jserrorhandler: b58b788d788cdbf8bda7db74a88ebfcffc8a0795
React-jsi: 89d43d1e7d4d0663f8ba67e0b39eb4e4672c27de React-jsi: d2c3f8555175371c02da6dfe7ed1b64b55a9d6c0
React-jsiexecutor: abe4874aaab90dfee5dec480680220b2f8af07e3 React-jsiexecutor: ba537434eb45ee018b590ed7d29ee233fddb8669
React-jsinspector: a0b3e051aef842b0b2be2353790ae2b2a5a65a8f React-jsinspector: f21b6654baf96cb9f71748844a32468a5f73ad51
React-jsinspectorcdp: 6346013b2247c6263fbf5199adf4a8751e53bd89 React-jsinspectorcdp: 3f8be4830694c3c1c39442e50f8db877966d43f0
React-jsinspectornetwork: 26281aa50d49fc1ec93abf981d934698fa95714f React-jsinspectornetwork: 70e41469565712ad60e11d9c8b8f999b9f7f61eb
React-jsinspectortracing: 55eedf6d57540507570259a778663b90060bbd6e React-jsinspectortracing: eccf9bfa4ec7f130d514f215cfb2222dc3c0e270
React-jsitooling: 0e001113fa56d8498aa8ac28437ac0d36348e51a React-jsitooling: b376a695f5a507627f7934748533b24eed1751ca
React-jsitracing: b713793eb8a5bbc4d86a84e9d9e5023c0f58cbaf React-jsitracing: 5c8c3273dda2d95191cc0612fb5e71c4d9018d2a
React-logger: 50fdb9a8236da90c0b1072da5c32ee03aeb5bf28 React-logger: c3e2f8a2e284341205f61eef3d4677ab5a309dfd
React-Mapbuffer: 9050ee10c19f4f7fca8963d0211b2854d624973e React-Mapbuffer: 603c18db65844bb81dbe62fee8fcc976eaeb7108
React-microtasksnativemodule: f775db9e991c6f3b8ccbc02bfcde22770f96e23b React-microtasksnativemodule: d77e0c426fce34c23227394c96ca1033b30c813c
react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd react-native-render-html: 984dfe2294163d04bf5fe25d7c9f122e60e05ebe
react-native-safe-area-context: 42a1b4f8774b577d03b53de7326e3d5757fe9513 react-native-safe-area-context: add9b4ba236fe95ec600604d0fc72f395433dd59
react-native-view-shot: fb3c0774edb448f42705491802a455beac1502a2 react-native-view-shot: 26174e54ec6b4b7c5d70b86964b747919759adc1
react-native-voice: 908a0eba96c8c3d643e4f98b7232c6557d0a6f9c react-native-voice: f5e8eec2278451d0017eb6a30a6ccc726aca34e0
react-native-webview: b29007f4723bca10872028067b07abacfa1cb35a react-native-webview: a4f0775a31b73cf13cfc3d2d2b119aa94ec76e49
React-NativeModulesApple: 8969913947d5b576de4ed371a939455a8daf28aa React-NativeModulesApple: 1664340b8750d64e0ef3907c5e53d9481f74bcbd
React-oscompat: ce47230ed20185e91de62d8c6d139ae61763d09c React-oscompat: ce47230ed20185e91de62d8c6d139ae61763d09c
React-perflogger: 02b010e665772c7dcb859d85d44c1bfc5ac7c0e4 React-perflogger: b1af3cfb3f095f819b2814910000392a8e17ba9f
React-performancetimeline: 130db956b5a83aa4fb41ddf5ae68da89f3fb1526 React-performancetimeline: f9ec65b77bcadbc7bd8b47a6f4b4b697da7b1490
React-RCTActionSheet: 0b14875b3963e9124a5a29a45bd1b22df8803916 React-RCTActionSheet: 0b14875b3963e9124a5a29a45bd1b22df8803916
React-RCTAnimation: a7b90fd2af7bb9c084428867445a1481a8cb112e React-RCTAnimation: 60f6eca214a62b9673f64db6df3830cee902b5af
React-RCTAppDelegate: 3262bedd01263f140ec62b7989f4355f57cec016 React-RCTAppDelegate: 37734b39bac108af30a0fd9d3e1149ec68b82c28
React-RCTBlob: c17531368702f1ebed5d0ada75a7cf5915072a53 React-RCTBlob: 83fbcbd57755caf021787324aac2fe9b028cc264
React-RCTFabric: 6409edd8cfdc3133b6cc75636d3b858fdb1d11ea React-RCTFabric: a05cb1df484008db3753c8b4a71e4c6d9f1e43a6
React-RCTFBReactNativeSpec: c004b27b4fa3bd85878ad2cf53de3bbec85da797 React-RCTFBReactNativeSpec: d58d7ae9447020bbbac651e3b0674422aba18266
React-RCTImage: c68078a120d0123f4f07a5ac77bea3bb10242f32 React-RCTImage: 47aba3be7c6c64f956b7918ab933769602406aac
React-RCTLinking: cf8f9391fe7fe471f96da3a5f0435235eca18c5b React-RCTLinking: 2dbaa4df2e4523f68baa07936bd8efdfa34d5f31
React-RCTNetwork: ca31f7c879355760c2d9832a06ee35f517938a20 React-RCTNetwork: 1fca7455f9dedf7de2b95bec438da06680f3b000
React-RCTRuntime: a6cf4a1e42754fc87f493e538f2ac6b820e45418 React-RCTRuntime: 17819dd1dfc8613efaf4cbb9d8686baae4a83e5b
React-RCTSettings: e0e140b2ff4bf86d34e9637f6316848fc00be035 React-RCTSettings: 01bf91c856862354d3d2f642ccb82f3697a4284a
React-RCTText: 75915bace6f7877c03a840cc7b6c622fb62bfa6b React-RCTText: cb576a3797dcb64933613c522296a07eaafc0461
React-RCTVibration: 25f26b85e5e432bb3c256f8b384f9269e9529f25 React-RCTVibration: 560af8c086741f3525b8456a482cdbe27f9d098e
React-rendererconsistency: 2dac03f448ff337235fd5820b10f81633328870d React-rendererconsistency: 2dac03f448ff337235fd5820b10f81633328870d
React-renderercss: 477da167bb96b5ac86d30c5d295412fb853f5453 React-renderercss: c5c6b7a15948dd28facca39a18ac269073718490
React-rendererdebug: 2a1798c6f3ef5f22d466df24c33653edbabb5b89 React-rendererdebug: 3c9d5e1634273f5a24d84cc5669f290ce0bdc812
React-RuntimeApple: 28cf4d8eb18432f6a21abbed7d801ab7f6b6f0b4 React-RuntimeApple: 887637d1e12ea8262df7d32bc100467df2302613
React-RuntimeCore: 41bf0fd56a00de5660f222415af49879fa49c4f0 React-RuntimeCore: 91f779835dc4f8f84777fe5dd24f1a22f96454e4
React-runtimeexecutor: 1afb774dde3011348e8334be69d2f57a359ea43e React-runtimeexecutor: 8bb6b738f37b0ada4a6269e6f8ab1133dea0285c
React-RuntimeHermes: f3b158ea40e8212b1a723a68b4315e7a495c5fc6 React-RuntimeHermes: 4cb93de9fa8b1cc753d200dbe61a01b9ec5f5562
React-runtimescheduler: 3e1e2bec7300bae512533107d8e54c6e5c63fe0f React-runtimescheduler: 83dc28f530bfbd2fce84ed13aa7feebdc24e5af7
React-timing: 6fa9883de2e41791e5dc4ec404e5e37f3f50e801 React-timing: 03c7217455d2bff459b27a3811be25796b600f47
React-utils: 6e2035b53d087927768649a11a26c4e092448e34 React-utils: 6d46795ae0444ec8a5d9a5f201157b286bf5250a
ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79 ReactAppDependencyProvider: c277c5b231881ad4f00cd59e3aa0671b99d7ebee
ReactCodegen: 7d4593f7591f002d137fe40cef3f6c11f13c88cc ReactCodegen: 4c44b74b77fc41ae25b9e2c7e9bd6e2bc772c23f
ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8 ReactCommon: e6e232202a447d353e5531f2be82f50f47cbaa9a
ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a
RevenueCat: a51003d4cb33820cc504cf177c627832b462a98e RevenueCat: a51003d4cb33820cc504cf177c627832b462a98e
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4
RNCMaskedView: d2578d41c59b936db122b2798ba37e4722d21035 RNCMaskedView: 3c9d7586e2b9bbab573591dcb823918bc4668005
RNCPicker: a7170edbcbf8288de8edb2502e08e7fc757fa755 RNCPicker: f97c908b7774248c1093ec3831ca70d338627bf7
RNDateTimePicker: be0e44bcb9ed0607c7c5f47dbedd88cf091f6791 RNDateTimePicker: 6fdd63f5d1e0f21faf4cc8674957c52958a7efae
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c RNDeviceInfo: 8b6fa8379062949dd79a009cf3d6b02a9c03ca59
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3 RNGestureHandler: 6a488ce85c88e82d8610db1108daf04e9b2d5162
RNPurchases: 2569675abdc1dbc739f2eec0fa564a112cf860de RNPurchases: e7d57c35ec94625f455981307c1487adde5e3188
RNReanimated: 3895a29fdf77bbe2a627e1ed599a5e5d1df76c29 RNReanimated: 1c03486192caeabe2795787e4bb046116383be7a
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845 RNScreens: dd61bc3a3e6f6901ad833efa411917d44827cf51
RNSentry: 41979b419908128847ef662cc130a400b7576fa9 RNSentry: bf366a415176cb6971a5adac37bbe66dfea272f3
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34 RNSVG: 2825ee146e0f6a16221e852299943e4cceef4528
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1 RNWorklets: 83609071441ac7d623f1e0e63b9043f4f345e2a2
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c

View File

@@ -12,6 +12,8 @@ const PREFERENCES_KEYS = {
WATER_REMINDER_END_TIME: 'user_preference_water_reminder_end_time', WATER_REMINDER_END_TIME: 'user_preference_water_reminder_end_time',
WATER_REMINDER_INTERVAL: 'user_preference_water_reminder_interval', WATER_REMINDER_INTERVAL: 'user_preference_water_reminder_interval',
MEDICATION_REMINDER_ENABLED: 'user_preference_medication_reminder_enabled', MEDICATION_REMINDER_ENABLED: 'user_preference_medication_reminder_enabled',
NUTRITION_REMINDER_ENABLED: 'user_preference_nutrition_reminder_enabled',
MOOD_REMINDER_ENABLED: 'user_preference_mood_reminder_enabled',
} as const; } as const;
// 用户偏好设置接口 // 用户偏好设置接口
@@ -26,6 +28,8 @@ export interface UserPreferences {
waterReminderEndTime: string; // 格式: "22:00" waterReminderEndTime: string; // 格式: "22:00"
waterReminderInterval: number; // 分钟 waterReminderInterval: number; // 分钟
medicationReminderEnabled: boolean; medicationReminderEnabled: boolean;
nutritionReminderEnabled: boolean;
moodReminderEnabled: boolean;
} }
// 默认的用户偏好设置 // 默认的用户偏好设置
@@ -40,6 +44,8 @@ const DEFAULT_PREFERENCES: UserPreferences = {
waterReminderEndTime: '22:00', // 默认结束时间晚上10点 waterReminderEndTime: '22:00', // 默认结束时间晚上10点
waterReminderInterval: 60, // 默认提醒间隔60分钟 waterReminderInterval: 60, // 默认提醒间隔60分钟
medicationReminderEnabled: true, // 默认开启药品提醒 medicationReminderEnabled: true, // 默认开启药品提醒
nutritionReminderEnabled: true, // 默认开启营养提醒
moodReminderEnabled: true, // 默认开启心情提醒
}; };
/** /**
@@ -57,6 +63,8 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
const waterReminderEndTime = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME); const waterReminderEndTime = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME);
const waterReminderInterval = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL); const waterReminderInterval = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL);
const medicationReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.MEDICATION_REMINDER_ENABLED); const medicationReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.MEDICATION_REMINDER_ENABLED);
const nutritionReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.NUTRITION_REMINDER_ENABLED);
const moodReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.MOOD_REMINDER_ENABLED);
return { return {
quickWaterAmount: quickWaterAmount ? parseInt(quickWaterAmount, 10) : DEFAULT_PREFERENCES.quickWaterAmount, quickWaterAmount: quickWaterAmount ? parseInt(quickWaterAmount, 10) : DEFAULT_PREFERENCES.quickWaterAmount,
@@ -69,6 +77,8 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
waterReminderEndTime: waterReminderEndTime || DEFAULT_PREFERENCES.waterReminderEndTime, waterReminderEndTime: waterReminderEndTime || DEFAULT_PREFERENCES.waterReminderEndTime,
waterReminderInterval: waterReminderInterval ? parseInt(waterReminderInterval, 10) : DEFAULT_PREFERENCES.waterReminderInterval, waterReminderInterval: waterReminderInterval ? parseInt(waterReminderInterval, 10) : DEFAULT_PREFERENCES.waterReminderInterval,
medicationReminderEnabled: medicationReminderEnabled !== null ? medicationReminderEnabled === 'true' : DEFAULT_PREFERENCES.medicationReminderEnabled, medicationReminderEnabled: medicationReminderEnabled !== null ? medicationReminderEnabled === 'true' : DEFAULT_PREFERENCES.medicationReminderEnabled,
nutritionReminderEnabled: nutritionReminderEnabled !== null ? nutritionReminderEnabled === 'true' : DEFAULT_PREFERENCES.nutritionReminderEnabled,
moodReminderEnabled: moodReminderEnabled !== null ? moodReminderEnabled === 'true' : DEFAULT_PREFERENCES.moodReminderEnabled,
}; };
} catch (error) { } catch (error) {
console.error('获取用户偏好设置失败:', error); console.error('获取用户偏好设置失败:', error);
@@ -381,6 +391,8 @@ export const resetUserPreferences = async (): Promise<void> => {
await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME); await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_END_TIME);
await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL); await AsyncStorage.removeItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL);
await AsyncStorage.removeItem(PREFERENCES_KEYS.MEDICATION_REMINDER_ENABLED); await AsyncStorage.removeItem(PREFERENCES_KEYS.MEDICATION_REMINDER_ENABLED);
await AsyncStorage.removeItem(PREFERENCES_KEYS.NUTRITION_REMINDER_ENABLED);
await AsyncStorage.removeItem(PREFERENCES_KEYS.MOOD_REMINDER_ENABLED);
} catch (error) { } catch (error) {
console.error('重置用户偏好设置失败:', error); console.error('重置用户偏好设置失败:', error);
throw error; throw error;
@@ -412,3 +424,55 @@ export const getMedicationReminderEnabled = async (): Promise<boolean> => {
return DEFAULT_PREFERENCES.medicationReminderEnabled; return DEFAULT_PREFERENCES.medicationReminderEnabled;
} }
}; };
/**
* 设置营养提醒开关
* @param enabled 是否开启营养提醒
*/
export const setNutritionReminderEnabled = async (enabled: boolean): Promise<void> => {
try {
await AsyncStorage.setItem(PREFERENCES_KEYS.NUTRITION_REMINDER_ENABLED, enabled.toString());
} catch (error) {
console.error('设置营养提醒开关失败:', error);
throw error;
}
};
/**
* 获取营养提醒开关状态
*/
export const getNutritionReminderEnabled = async (): Promise<boolean> => {
try {
const enabled = await AsyncStorage.getItem(PREFERENCES_KEYS.NUTRITION_REMINDER_ENABLED);
return enabled !== null ? enabled === 'true' : DEFAULT_PREFERENCES.nutritionReminderEnabled;
} catch (error) {
console.error('获取营养提醒开关状态失败:', error);
return DEFAULT_PREFERENCES.nutritionReminderEnabled;
}
};
/**
* 设置心情提醒开关
* @param enabled 是否开启心情提醒
*/
export const setMoodReminderEnabled = async (enabled: boolean): Promise<void> => {
try {
await AsyncStorage.setItem(PREFERENCES_KEYS.MOOD_REMINDER_ENABLED, enabled.toString());
} catch (error) {
console.error('设置心情提醒开关失败:', error);
throw error;
}
};
/**
* 获取心情提醒开关状态
*/
export const getMoodReminderEnabled = async (): Promise<boolean> => {
try {
const enabled = await AsyncStorage.getItem(PREFERENCES_KEYS.MOOD_REMINDER_ENABLED);
return enabled !== null ? enabled === 'true' : DEFAULT_PREFERENCES.moodReminderEnabled;
} catch (error) {
console.error('获取心情提醒开关状态失败:', error);
return DEFAULT_PREFERENCES.moodReminderEnabled;
}
};