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:
@@ -24,7 +24,7 @@ import { createWaterRecordAction } from '@/store/waterSlice';
|
||||
import { loadActiveFastingSchedule } from '@/utils/fasting';
|
||||
import { initializeHealthPermissions } from '@/utils/health';
|
||||
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 React, { useEffect } from 'react';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
@@ -221,39 +221,56 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
try {
|
||||
logger.info('📢 开始批量注册通知提醒...');
|
||||
|
||||
// 并行注册所有通知,提高效率
|
||||
await Promise.all([
|
||||
// 营养提醒
|
||||
// 获取用户偏好设置
|
||||
const [nutritionReminderEnabled, moodReminderEnabled, waterSettings] = await Promise.all([
|
||||
getNutritionReminderEnabled(),
|
||||
getMoodReminderEnabled(),
|
||||
getWaterReminderSettings(),
|
||||
]);
|
||||
|
||||
// 准备所有通知注册任务
|
||||
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('ℹ️ 用户未开启心情提醒,跳过注册');
|
||||
}
|
||||
|
||||
// 喝水提醒 - 需要先检查设置
|
||||
getWaterReminderSettings().then(settings => {
|
||||
if (settings.enabled) {
|
||||
// 如果使用的是自定义提醒,scheduleCustomWaterReminders 会被调用(通常在设置页面保存时)
|
||||
// 但为了保险起见,这里也可以根据设置类型来决定调用哪个
|
||||
// 目前逻辑似乎是 scheduleRegularWaterReminders 是默认的/旧的逻辑?
|
||||
// 查看 notificationHelpers.ts,scheduleRegularWaterReminders 是每2小时一次的固定逻辑
|
||||
// 而 scheduleCustomWaterReminders 是根据用户设置的时间段和间隔
|
||||
|
||||
// 如果用户开启了提醒,应该使用 scheduleCustomWaterReminders
|
||||
WaterNotificationHelpers.scheduleCustomWaterReminders(profile.name || '用户', settings).then(() =>
|
||||
// 喝水提醒 - 根据用户设置决定是否注册
|
||||
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;
|
||||
|
||||
@@ -3,9 +3,13 @@ import { useI18n } from '@/hooks/useI18n';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import {
|
||||
getMedicationReminderEnabled,
|
||||
getMoodReminderEnabled,
|
||||
getNotificationEnabled,
|
||||
getNutritionReminderEnabled,
|
||||
setMedicationReminderEnabled,
|
||||
setNotificationEnabled
|
||||
setMoodReminderEnabled,
|
||||
setNotificationEnabled,
|
||||
setNutritionReminderEnabled
|
||||
} from '@/utils/userPreferences';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||||
@@ -24,17 +28,23 @@ export default function NotificationSettingsScreen() {
|
||||
// 通知设置状态
|
||||
const [notificationEnabled, setNotificationEnabledState] = useState(false);
|
||||
const [medicationReminderEnabled, setMedicationReminderEnabledState] = useState(false);
|
||||
const [nutritionReminderEnabled, setNutritionReminderEnabledState] = useState(false);
|
||||
const [moodReminderEnabled, setMoodReminderEnabledState] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// 加载通知设置
|
||||
const loadNotificationSettings = useCallback(async () => {
|
||||
try {
|
||||
const [notification, medicationReminder] = await Promise.all([
|
||||
const [notification, medicationReminder, nutritionReminder, moodReminder] = await Promise.all([
|
||||
getNotificationEnabled(),
|
||||
getMedicationReminderEnabled(),
|
||||
getNutritionReminderEnabled(),
|
||||
getMoodReminderEnabled(),
|
||||
]);
|
||||
setNotificationEnabledState(notification);
|
||||
setMedicationReminderEnabledState(medicationReminder);
|
||||
setNutritionReminderEnabledState(nutritionReminder);
|
||||
setMoodReminderEnabledState(moodReminder);
|
||||
} catch (error) {
|
||||
console.error('Failed to load notification settings:', error);
|
||||
} finally {
|
||||
@@ -87,9 +97,13 @@ export default function NotificationSettingsScreen() {
|
||||
// 关闭推送,保存用户偏好设置
|
||||
await setNotificationEnabled(false);
|
||||
setNotificationEnabledState(false);
|
||||
// 关闭总开关时,也关闭药品提醒
|
||||
// 关闭总开关时,也关闭所有提醒
|
||||
await setMedicationReminderEnabled(false);
|
||||
setMedicationReminderEnabledState(false);
|
||||
await setNutritionReminderEnabled(false);
|
||||
setNutritionReminderEnabledState(false);
|
||||
await setMoodReminderEnabled(false);
|
||||
setMoodReminderEnabledState(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to disable push notifications:', error);
|
||||
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 = () => (
|
||||
<TouchableOpacity
|
||||
@@ -247,6 +303,34 @@ export default function NotificationSettingsScreen() {
|
||||
</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}>
|
||||
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.description')}</Text>
|
||||
|
||||
@@ -29,6 +29,7 @@ const WORKOUT_TYPES = [
|
||||
{ key: 'walking', label: '步行' },
|
||||
{ key: 'other', label: '其他运动' },
|
||||
];
|
||||
const WORKOUT_TYPE_KEYS = WORKOUT_TYPES.map(type => type.key);
|
||||
|
||||
export default function WorkoutNotificationSettingsScreen() {
|
||||
const safeAreaTop = useSafeAreaTop()
|
||||
@@ -80,16 +81,18 @@ export default function WorkoutNotificationSettingsScreen() {
|
||||
};
|
||||
|
||||
const handleWorkoutTypeToggle = (workoutType: string) => {
|
||||
const currentTypes = preferences.enabledWorkoutTypes;
|
||||
let newTypes: string[];
|
||||
const currentTypes = preferences.enabledWorkoutTypes.length === 0
|
||||
? [...WORKOUT_TYPE_KEYS] // 空数组表示全部启用,先展开成完整列表,避免影响其他开关的当前状态
|
||||
: [...preferences.enabledWorkoutTypes];
|
||||
|
||||
if (currentTypes.includes(workoutType)) {
|
||||
newTypes = currentTypes.filter(type => type !== workoutType);
|
||||
} else {
|
||||
newTypes = [...currentTypes, workoutType];
|
||||
}
|
||||
const nextTypes = currentTypes.includes(workoutType)
|
||||
? currentTypes.filter(type => type !== workoutType)
|
||||
: [...currentTypes, workoutType];
|
||||
|
||||
savePreferences({ enabledWorkoutTypes: newTypes });
|
||||
// 如果全部类型都开启,回退为空数组表示“全部启用”,以保持原有存储约定
|
||||
const normalizedTypes = nextTypes.length === WORKOUT_TYPE_KEYS.length ? [] : nextTypes;
|
||||
|
||||
savePreferences({ enabledWorkoutTypes: normalizedTypes });
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
|
||||
@@ -825,6 +825,8 @@ const notificationSettingsResources = {
|
||||
sections: {
|
||||
notifications: '通知设置',
|
||||
medicationReminder: '药品提醒',
|
||||
nutritionReminder: '营养提醒',
|
||||
moodReminder: '心情提醒',
|
||||
description: '说明',
|
||||
},
|
||||
items: {
|
||||
@@ -836,9 +838,17 @@ const notificationSettingsResources = {
|
||||
title: '药品通知提醒',
|
||||
description: '在用药时间接收提醒通知',
|
||||
},
|
||||
nutritionReminder: {
|
||||
title: '营养记录提醒',
|
||||
description: '在用餐时间接收营养记录提醒',
|
||||
},
|
||||
moodReminder: {
|
||||
title: '心情记录提醒',
|
||||
description: '在晚间接收心情记录提醒',
|
||||
},
|
||||
},
|
||||
description: {
|
||||
text: '• 消息推送是所有通知的总开关\n• 药品通知提醒需要在消息推送开启后才能使用\n• 您可以在系统设置中管理通知权限\n• 关闭消息推送将停止所有应用通知',
|
||||
text: '• 消息推送是所有通知的总开关\n• 各类提醒需要在消息推送开启后才能使用\n• 您可以在系统设置中管理通知权限\n• 关闭消息推送将停止所有应用通知',
|
||||
},
|
||||
alerts: {
|
||||
permissionDenied: {
|
||||
@@ -852,6 +862,8 @@ const notificationSettingsResources = {
|
||||
message: '请求通知权限失败',
|
||||
saveFailed: '保存设置失败',
|
||||
medicationReminderFailed: '设置药品提醒失败',
|
||||
nutritionReminderFailed: '设置营养提醒失败',
|
||||
moodReminderFailed: '设置心情提醒失败',
|
||||
},
|
||||
notificationsEnabled: {
|
||||
title: '通知已开启',
|
||||
@@ -861,6 +873,14 @@ const notificationSettingsResources = {
|
||||
title: '药品提醒已开启',
|
||||
body: '您将在用药时间收到提醒通知',
|
||||
},
|
||||
nutritionReminderEnabled: {
|
||||
title: '营养提醒已开启',
|
||||
body: '您将在用餐时间收到营养记录提醒',
|
||||
},
|
||||
moodReminderEnabled: {
|
||||
title: '心情提醒已开启',
|
||||
body: '您将在晚间收到心情记录提醒',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1597,6 +1617,8 @@ const resources = {
|
||||
sections: {
|
||||
notifications: 'Notification Settings',
|
||||
medicationReminder: 'Medication Reminder',
|
||||
nutritionReminder: 'Nutrition Reminder',
|
||||
moodReminder: 'Mood Reminder',
|
||||
description: 'Description',
|
||||
},
|
||||
items: {
|
||||
@@ -1608,9 +1630,17 @@ const resources = {
|
||||
title: 'Medication Reminder',
|
||||
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: {
|
||||
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: {
|
||||
permissionDenied: {
|
||||
@@ -1624,6 +1654,8 @@ const resources = {
|
||||
message: 'Failed to request notification permission',
|
||||
saveFailed: 'Failed to save settings',
|
||||
medicationReminderFailed: 'Failed to set medication reminder',
|
||||
nutritionReminderFailed: 'Failed to set nutrition reminder',
|
||||
moodReminderFailed: 'Failed to set mood reminder',
|
||||
},
|
||||
notificationsEnabled: {
|
||||
title: 'Notifications Enabled',
|
||||
@@ -1633,6 +1665,14 @@ const resources = {
|
||||
title: 'Medication Reminder Enabled',
|
||||
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: {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.29</string>
|
||||
<string>1.0.30</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
206
ios/Podfile.lock
206
ios/Podfile.lock
@@ -2679,127 +2679,127 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
EXApplication: 296622817d459f46b6c5fe8691f4aac44d2b79e7
|
||||
EXConstants: fd688cef4e401dcf798a021cfb5d87c890c30ba3
|
||||
EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05
|
||||
EXNotifications: 7cff475adb5d7a255a9ea46bbd2589cb3b454506
|
||||
Expo: 27ae59be9be4feab2b1c1ae06550752c524ca558
|
||||
ExpoAppleAuthentication: bc9de6e9ff3340604213ab9031d4c4f7f802623e
|
||||
ExpoAsset: 9ba6fbd677fb8e241a3899ac00fa735bc911eadf
|
||||
ExpoBackgroundTask: e0d201d38539c571efc5f9cb661fae8ab36ed61b
|
||||
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f
|
||||
ExpoCamera: e75f6807a2c047f3338bbadd101af4c71a1d13a5
|
||||
ExpoFileSystem: b79eadbda7b7f285f378f95f959cc9313a1c9c61
|
||||
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961
|
||||
ExpoGlassEffect: 779c46bd04ea47ba4726efb73267b5bcc6abd664
|
||||
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84
|
||||
ExpoHead: e317214fa14edeaf17748d39ec9e550a3d1194fb
|
||||
ExpoImage: 9c3428921c536ab29e5c6721d001ad5c1f469566
|
||||
ExpoImagePicker: d251aab45a1b1857e4156fed88511b278b4eee1c
|
||||
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe
|
||||
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27
|
||||
ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d
|
||||
ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca
|
||||
ExpoMediaLibrary: 641a6952299b395159ccd459bd8f5f6764bf55fe
|
||||
ExpoModulesCore: 5f20603cf25698682d7c43c05fbba8c748b189d2
|
||||
ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f
|
||||
ExpoSplashScreen: cbb839de72110dea1851dd3e85080b7923af2540
|
||||
ExpoSQLite: 7fa091ba5562474093fef09be644161a65e11b3f
|
||||
ExpoSymbols: 1ae04ce686de719b9720453b988d8bc5bf776c68
|
||||
ExpoSystemUI: 2761aa6875849af83286364811d46e8ed8ea64c7
|
||||
ExpoUI: b99a1d1ef5352a60bebf4f4fd3a50d2f896ae804
|
||||
ExpoWebBrowser: d04a0d6247a0bea4519fbc2ea816610019ad83e0
|
||||
EXTaskManager: cbbb80cbccea6487ccca0631809fbba2ed3e5271
|
||||
EXApplication: a9d1c46d473d36f61302a9a81db2379441f3f094
|
||||
EXConstants: e6e50cdfcb4524f40121d1fdcff24e97b7dcd2fd
|
||||
EXImageLoader: e501c001bc40b8326605e82e6e80363c80fe06b5
|
||||
EXNotifications: 7aab54f0e5f3023122bc95699eaff7c52bacb559
|
||||
Expo: 795e9f87aca407bf92895d54ae5f7777fc1f3fbc
|
||||
ExpoAppleAuthentication: 414e4316f8e25a2afbc3943cf725579c910f24b8
|
||||
ExpoAsset: 3c3b7dd9b1318846a02ef05ce420e63d542aeb9f
|
||||
ExpoBackgroundTask: e048da30cd2d669c5ba20d5d704bee8dd6da320c
|
||||
ExpoBlur: b5b7a26572b3c33a11f0b2aa2f95c17c4c393b76
|
||||
ExpoCamera: 6d0c5bc68bc8de669f1ecd4242284de0827c4431
|
||||
ExpoFileSystem: 56f081328f5b48a6dcc8302eee51d4f2d9d0049b
|
||||
ExpoFont: b881d43057dceb7b31ff767b24f612609e80f60f
|
||||
ExpoGlassEffect: d35ec1a8e9d84492f23755d3020a6a81a20bd585
|
||||
ExpoHaptics: b48d913e7e5f23816c6f130e525c9a6501b160b5
|
||||
ExpoHead: aa5f5a8afaa9bd4969bfdd6d5b76681e2490fe5b
|
||||
ExpoImage: 6eb842cd07817402640545c41884dd7f5fbfbca5
|
||||
ExpoImagePicker: bd0a5c81d7734548f6908a480609257e85d19ea8
|
||||
ExpoKeepAwake: 3f5e3ac53627849174f3603271df8e08f174ed4a
|
||||
ExpoLinearGradient: f9e7182e5253d53b2de4134b69d70bbfc2d50588
|
||||
ExpoLinking: f5c171877e118e792cb9a77e5ade0b080d899b14
|
||||
ExpoLocalization: 6c6f0f89ad2822001ab0bc2eb6d4d980c77f080c
|
||||
ExpoMediaLibrary: 648cee3f5dcba13410ec9cc8ac9a426e89a61a31
|
||||
ExpoModulesCore: ed46799cfdf75784ee3ca37dac982c5298683e83
|
||||
ExpoQuickActions: 62b9db8a20618be1cc19efa3b562ac963c803d58
|
||||
ExpoSplashScreen: 9d2ff8fa08f2c00336a83f93bebffed3a8312519
|
||||
ExpoSQLite: f9d1202877e12bfa78a58309a3977ee4ea0b1314
|
||||
ExpoSymbols: ef7b8ac77ac2d496b1bc3f0f7daf5e19c3a9933a
|
||||
ExpoSystemUI: 9441d46a8efbf9224d1b2e6b18042452ffd0ed79
|
||||
ExpoUI: 821b058da921ea4aa6172b36d080991ea6fb2fae
|
||||
ExpoWebBrowser: 51218ce6ef35ea769e33409aac87fea3df4b919d
|
||||
EXTaskManager: 53f87ed11659341c3f3f02c0041498ef293f5684
|
||||
FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12
|
||||
hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172
|
||||
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
|
||||
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
|
||||
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
|
||||
lottie-react-native: 97a11537edc72d0763edab0c83e8cc8a0b9d8484
|
||||
PurchasesHybridCommon: b7b4eafb55fbaaac19b4c36d4082657a3f0d8490
|
||||
RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990
|
||||
RCTRequired: 8f3cfc90cc25cf6e420ddb3e7caaaabc57df6043
|
||||
RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c
|
||||
React: 914f8695f9bf38e6418228c2ffb70021e559f92f
|
||||
React-callinvoker: 1c0808402aee0c6d4a0d8e7220ce6547af9fba71
|
||||
React-Core: c61410ef0ca6055e204a963992e363227e0fd1c5
|
||||
React-Core-prebuilt: 02f0ad625ddd47463c009c2d0c5dd35c0d982599
|
||||
React-CoreModules: 1f6d1744b5f9f2ec684a4bb5ced25370f87e5382
|
||||
React-cxxreact: 3af79478e8187b63ffc22b794cd42d3fc1f1f2da
|
||||
React-Core: 4ae98f9e8135b8ddbd7c98730afb6fdae883db90
|
||||
React-Core-prebuilt: 8f4cca589c14e8cf8fc6db4587ef1c2056b5c151
|
||||
React-CoreModules: e878a90bb19b8f3851818af997dbae3b3b0a27ac
|
||||
React-cxxreact: 28af9844f6dc87be1385ab521fbfb3746f19563c
|
||||
React-debug: 6328c2228e268846161f10082e80dc69eac2e90a
|
||||
React-defaultsnativemodule: d635ef36d755321e5d6fc065bd166b2c5a0e9833
|
||||
React-domnativemodule: dd28f6d96cd21236e020be2eff6fe0b7d4ec3b66
|
||||
React-Fabric: 2e32c3fdbb1fbcf5fde54607e3abe453c6652ce2
|
||||
React-FabricComponents: 5ed0cdb81f6b91656cb4d3be432feaa28a58071a
|
||||
React-FabricImage: 2bc714f818cb24e454f5d3961864373271b2faf8
|
||||
React-featureflags: 847642f41fa71ad4eec5e0351badebcad4fe6171
|
||||
React-featureflagsnativemodule: c868a544b2c626fa337bcbd364b1befe749f0d3f
|
||||
React-graphics: 192ec701def5b3f2a07db2814dfba5a44986cff6
|
||||
React-hermes: e875778b496c86d07ab2ccaa36a9505d248a254b
|
||||
React-idlecallbacksnativemodule: 4d57965cdf82c14ee3b337189836cd8491632b76
|
||||
React-ImageManager: bd0b99e370b13de82c9cd15f0f08144ff3de079e
|
||||
React-jserrorhandler: a2fdef4cbcfdcdf3fa9f5d1f7190f7fd4535248d
|
||||
React-jsi: 89d43d1e7d4d0663f8ba67e0b39eb4e4672c27de
|
||||
React-jsiexecutor: abe4874aaab90dfee5dec480680220b2f8af07e3
|
||||
React-jsinspector: a0b3e051aef842b0b2be2353790ae2b2a5a65a8f
|
||||
React-jsinspectorcdp: 6346013b2247c6263fbf5199adf4a8751e53bd89
|
||||
React-jsinspectornetwork: 26281aa50d49fc1ec93abf981d934698fa95714f
|
||||
React-jsinspectortracing: 55eedf6d57540507570259a778663b90060bbd6e
|
||||
React-jsitooling: 0e001113fa56d8498aa8ac28437ac0d36348e51a
|
||||
React-jsitracing: b713793eb8a5bbc4d86a84e9d9e5023c0f58cbaf
|
||||
React-logger: 50fdb9a8236da90c0b1072da5c32ee03aeb5bf28
|
||||
React-Mapbuffer: 9050ee10c19f4f7fca8963d0211b2854d624973e
|
||||
React-microtasksnativemodule: f775db9e991c6f3b8ccbc02bfcde22770f96e23b
|
||||
react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd
|
||||
react-native-safe-area-context: 42a1b4f8774b577d03b53de7326e3d5757fe9513
|
||||
react-native-view-shot: fb3c0774edb448f42705491802a455beac1502a2
|
||||
react-native-voice: 908a0eba96c8c3d643e4f98b7232c6557d0a6f9c
|
||||
react-native-webview: b29007f4723bca10872028067b07abacfa1cb35a
|
||||
React-NativeModulesApple: 8969913947d5b576de4ed371a939455a8daf28aa
|
||||
React-defaultsnativemodule: afc9d809ec75780f39464a6949c07987fbea488c
|
||||
React-domnativemodule: 91a233260411d41f27f67aa1358b7f9f0bfd101d
|
||||
React-Fabric: 21f349b5e93f305a3c38c885902683a9c79cf983
|
||||
React-FabricComponents: 47ac634cc9ecc64b30a9997192f510eebe4177e4
|
||||
React-FabricImage: 21873acd6d4a51a0b97c133141051c7acb11cc86
|
||||
React-featureflags: 653f469f0c3c9dc271d610373e3b6e66a9fd847d
|
||||
React-featureflagsnativemodule: c91a8a3880e0f4838286402241ead47db43aed28
|
||||
React-graphics: b4bdb0f635b8048c652a5d2b73eb8b1ddd950f24
|
||||
React-hermes: fcfad3b917400f49026f3232561e039c9d1c34bf
|
||||
React-idlecallbacksnativemodule: 8cb83207e39f8179ac1d344b6177c6ab3ccebcdc
|
||||
React-ImageManager: 396128004783fc510e629124dce682d38d1088e7
|
||||
React-jserrorhandler: b58b788d788cdbf8bda7db74a88ebfcffc8a0795
|
||||
React-jsi: d2c3f8555175371c02da6dfe7ed1b64b55a9d6c0
|
||||
React-jsiexecutor: ba537434eb45ee018b590ed7d29ee233fddb8669
|
||||
React-jsinspector: f21b6654baf96cb9f71748844a32468a5f73ad51
|
||||
React-jsinspectorcdp: 3f8be4830694c3c1c39442e50f8db877966d43f0
|
||||
React-jsinspectornetwork: 70e41469565712ad60e11d9c8b8f999b9f7f61eb
|
||||
React-jsinspectortracing: eccf9bfa4ec7f130d514f215cfb2222dc3c0e270
|
||||
React-jsitooling: b376a695f5a507627f7934748533b24eed1751ca
|
||||
React-jsitracing: 5c8c3273dda2d95191cc0612fb5e71c4d9018d2a
|
||||
React-logger: c3e2f8a2e284341205f61eef3d4677ab5a309dfd
|
||||
React-Mapbuffer: 603c18db65844bb81dbe62fee8fcc976eaeb7108
|
||||
React-microtasksnativemodule: d77e0c426fce34c23227394c96ca1033b30c813c
|
||||
react-native-render-html: 984dfe2294163d04bf5fe25d7c9f122e60e05ebe
|
||||
react-native-safe-area-context: add9b4ba236fe95ec600604d0fc72f395433dd59
|
||||
react-native-view-shot: 26174e54ec6b4b7c5d70b86964b747919759adc1
|
||||
react-native-voice: f5e8eec2278451d0017eb6a30a6ccc726aca34e0
|
||||
react-native-webview: a4f0775a31b73cf13cfc3d2d2b119aa94ec76e49
|
||||
React-NativeModulesApple: 1664340b8750d64e0ef3907c5e53d9481f74bcbd
|
||||
React-oscompat: ce47230ed20185e91de62d8c6d139ae61763d09c
|
||||
React-perflogger: 02b010e665772c7dcb859d85d44c1bfc5ac7c0e4
|
||||
React-performancetimeline: 130db956b5a83aa4fb41ddf5ae68da89f3fb1526
|
||||
React-perflogger: b1af3cfb3f095f819b2814910000392a8e17ba9f
|
||||
React-performancetimeline: f9ec65b77bcadbc7bd8b47a6f4b4b697da7b1490
|
||||
React-RCTActionSheet: 0b14875b3963e9124a5a29a45bd1b22df8803916
|
||||
React-RCTAnimation: a7b90fd2af7bb9c084428867445a1481a8cb112e
|
||||
React-RCTAppDelegate: 3262bedd01263f140ec62b7989f4355f57cec016
|
||||
React-RCTBlob: c17531368702f1ebed5d0ada75a7cf5915072a53
|
||||
React-RCTFabric: 6409edd8cfdc3133b6cc75636d3b858fdb1d11ea
|
||||
React-RCTFBReactNativeSpec: c004b27b4fa3bd85878ad2cf53de3bbec85da797
|
||||
React-RCTImage: c68078a120d0123f4f07a5ac77bea3bb10242f32
|
||||
React-RCTLinking: cf8f9391fe7fe471f96da3a5f0435235eca18c5b
|
||||
React-RCTNetwork: ca31f7c879355760c2d9832a06ee35f517938a20
|
||||
React-RCTRuntime: a6cf4a1e42754fc87f493e538f2ac6b820e45418
|
||||
React-RCTSettings: e0e140b2ff4bf86d34e9637f6316848fc00be035
|
||||
React-RCTText: 75915bace6f7877c03a840cc7b6c622fb62bfa6b
|
||||
React-RCTVibration: 25f26b85e5e432bb3c256f8b384f9269e9529f25
|
||||
React-RCTAnimation: 60f6eca214a62b9673f64db6df3830cee902b5af
|
||||
React-RCTAppDelegate: 37734b39bac108af30a0fd9d3e1149ec68b82c28
|
||||
React-RCTBlob: 83fbcbd57755caf021787324aac2fe9b028cc264
|
||||
React-RCTFabric: a05cb1df484008db3753c8b4a71e4c6d9f1e43a6
|
||||
React-RCTFBReactNativeSpec: d58d7ae9447020bbbac651e3b0674422aba18266
|
||||
React-RCTImage: 47aba3be7c6c64f956b7918ab933769602406aac
|
||||
React-RCTLinking: 2dbaa4df2e4523f68baa07936bd8efdfa34d5f31
|
||||
React-RCTNetwork: 1fca7455f9dedf7de2b95bec438da06680f3b000
|
||||
React-RCTRuntime: 17819dd1dfc8613efaf4cbb9d8686baae4a83e5b
|
||||
React-RCTSettings: 01bf91c856862354d3d2f642ccb82f3697a4284a
|
||||
React-RCTText: cb576a3797dcb64933613c522296a07eaafc0461
|
||||
React-RCTVibration: 560af8c086741f3525b8456a482cdbe27f9d098e
|
||||
React-rendererconsistency: 2dac03f448ff337235fd5820b10f81633328870d
|
||||
React-renderercss: 477da167bb96b5ac86d30c5d295412fb853f5453
|
||||
React-rendererdebug: 2a1798c6f3ef5f22d466df24c33653edbabb5b89
|
||||
React-RuntimeApple: 28cf4d8eb18432f6a21abbed7d801ab7f6b6f0b4
|
||||
React-RuntimeCore: 41bf0fd56a00de5660f222415af49879fa49c4f0
|
||||
React-runtimeexecutor: 1afb774dde3011348e8334be69d2f57a359ea43e
|
||||
React-RuntimeHermes: f3b158ea40e8212b1a723a68b4315e7a495c5fc6
|
||||
React-runtimescheduler: 3e1e2bec7300bae512533107d8e54c6e5c63fe0f
|
||||
React-timing: 6fa9883de2e41791e5dc4ec404e5e37f3f50e801
|
||||
React-utils: 6e2035b53d087927768649a11a26c4e092448e34
|
||||
ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79
|
||||
ReactCodegen: 7d4593f7591f002d137fe40cef3f6c11f13c88cc
|
||||
ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8
|
||||
React-renderercss: c5c6b7a15948dd28facca39a18ac269073718490
|
||||
React-rendererdebug: 3c9d5e1634273f5a24d84cc5669f290ce0bdc812
|
||||
React-RuntimeApple: 887637d1e12ea8262df7d32bc100467df2302613
|
||||
React-RuntimeCore: 91f779835dc4f8f84777fe5dd24f1a22f96454e4
|
||||
React-runtimeexecutor: 8bb6b738f37b0ada4a6269e6f8ab1133dea0285c
|
||||
React-RuntimeHermes: 4cb93de9fa8b1cc753d200dbe61a01b9ec5f5562
|
||||
React-runtimescheduler: 83dc28f530bfbd2fce84ed13aa7feebdc24e5af7
|
||||
React-timing: 03c7217455d2bff459b27a3811be25796b600f47
|
||||
React-utils: 6d46795ae0444ec8a5d9a5f201157b286bf5250a
|
||||
ReactAppDependencyProvider: c277c5b231881ad4f00cd59e3aa0671b99d7ebee
|
||||
ReactCodegen: 4c44b74b77fc41ae25b9e2c7e9bd6e2bc772c23f
|
||||
ReactCommon: e6e232202a447d353e5531f2be82f50f47cbaa9a
|
||||
ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a
|
||||
RevenueCat: a51003d4cb33820cc504cf177c627832b462a98e
|
||||
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
|
||||
RNCMaskedView: d2578d41c59b936db122b2798ba37e4722d21035
|
||||
RNCPicker: a7170edbcbf8288de8edb2502e08e7fc757fa755
|
||||
RNDateTimePicker: be0e44bcb9ed0607c7c5f47dbedd88cf091f6791
|
||||
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
|
||||
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
|
||||
RNPurchases: 2569675abdc1dbc739f2eec0fa564a112cf860de
|
||||
RNReanimated: 3895a29fdf77bbe2a627e1ed599a5e5d1df76c29
|
||||
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
|
||||
RNSentry: 41979b419908128847ef662cc130a400b7576fa9
|
||||
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34
|
||||
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1
|
||||
RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4
|
||||
RNCMaskedView: 3c9d7586e2b9bbab573591dcb823918bc4668005
|
||||
RNCPicker: f97c908b7774248c1093ec3831ca70d338627bf7
|
||||
RNDateTimePicker: 6fdd63f5d1e0f21faf4cc8674957c52958a7efae
|
||||
RNDeviceInfo: 8b6fa8379062949dd79a009cf3d6b02a9c03ca59
|
||||
RNGestureHandler: 6a488ce85c88e82d8610db1108daf04e9b2d5162
|
||||
RNPurchases: e7d57c35ec94625f455981307c1487adde5e3188
|
||||
RNReanimated: 1c03486192caeabe2795787e4bb046116383be7a
|
||||
RNScreens: dd61bc3a3e6f6901ad833efa411917d44827cf51
|
||||
RNSentry: bf366a415176cb6971a5adac37bbe66dfea272f3
|
||||
RNSVG: 2825ee146e0f6a16221e852299943e4cceef4528
|
||||
RNWorklets: 83609071441ac7d623f1e0e63b9043f4f345e2a2
|
||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
|
||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||
|
||||
@@ -12,6 +12,8 @@ const PREFERENCES_KEYS = {
|
||||
WATER_REMINDER_END_TIME: 'user_preference_water_reminder_end_time',
|
||||
WATER_REMINDER_INTERVAL: 'user_preference_water_reminder_interval',
|
||||
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;
|
||||
|
||||
// 用户偏好设置接口
|
||||
@@ -26,6 +28,8 @@ export interface UserPreferences {
|
||||
waterReminderEndTime: string; // 格式: "22:00"
|
||||
waterReminderInterval: number; // 分钟
|
||||
medicationReminderEnabled: boolean;
|
||||
nutritionReminderEnabled: boolean;
|
||||
moodReminderEnabled: boolean;
|
||||
}
|
||||
|
||||
// 默认的用户偏好设置
|
||||
@@ -40,6 +44,8 @@ const DEFAULT_PREFERENCES: UserPreferences = {
|
||||
waterReminderEndTime: '22:00', // 默认结束时间晚上10点
|
||||
waterReminderInterval: 60, // 默认提醒间隔60分钟
|
||||
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 waterReminderInterval = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_REMINDER_INTERVAL);
|
||||
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 {
|
||||
quickWaterAmount: quickWaterAmount ? parseInt(quickWaterAmount, 10) : DEFAULT_PREFERENCES.quickWaterAmount,
|
||||
@@ -69,6 +77,8 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
|
||||
waterReminderEndTime: waterReminderEndTime || DEFAULT_PREFERENCES.waterReminderEndTime,
|
||||
waterReminderInterval: waterReminderInterval ? parseInt(waterReminderInterval, 10) : DEFAULT_PREFERENCES.waterReminderInterval,
|
||||
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) {
|
||||
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_INTERVAL);
|
||||
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) {
|
||||
console.error('重置用户偏好设置失败:', error);
|
||||
throw error;
|
||||
@@ -412,3 +424,55 @@ export const getMedicationReminderEnabled = async (): Promise<boolean> => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user