feat(app): 新增生理周期记录功能与首页卡片自定义支持

- 新增生理周期追踪页面及相关算法逻辑,支持经期记录与预测
- 新增首页统计卡片自定义页面,支持VIP用户调整卡片显示状态与顺序
- 重构首页统计页面布局逻辑,支持动态渲染与混合布局
- 引入 react-native-draggable-flatlist 用于实现拖拽排序功能
- 添加相关多语言配置及用户偏好设置存储接口
This commit is contained in:
richarjiang
2025-12-16 17:25:21 +08:00
parent 5e11da34ee
commit 9b4a300380
10 changed files with 2041 additions and 79 deletions

View File

@@ -15,10 +15,57 @@ const PREFERENCES_KEYS = {
NUTRITION_REMINDER_ENABLED: 'user_preference_nutrition_reminder_enabled',
MOOD_REMINDER_ENABLED: 'user_preference_mood_reminder_enabled',
HRV_REMINDER_ENABLED: 'user_preference_hrv_reminder_enabled',
// 首页身体指标卡片显示设置
SHOW_MOOD_CARD: 'user_preference_show_mood_card',
SHOW_STEPS_CARD: 'user_preference_show_steps_card',
SHOW_STRESS_CARD: 'user_preference_show_stress_card',
SHOW_SLEEP_CARD: 'user_preference_show_sleep_card',
SHOW_FITNESS_RINGS_CARD: 'user_preference_show_fitness_rings_card',
SHOW_WATER_CARD: 'user_preference_show_water_card',
SHOW_BASAL_METABOLISM_CARD: 'user_preference_show_basal_metabolism_card',
SHOW_OXYGEN_SATURATION_CARD: 'user_preference_show_oxygen_saturation_card',
SHOW_MENSTRUAL_CYCLE_CARD: 'user_preference_show_menstrual_cycle_card',
SHOW_WEIGHT_CARD: 'user_preference_show_weight_card',
SHOW_CIRCUMFERENCE_CARD: 'user_preference_show_circumference_card',
// 首页身体指标卡片排序设置
STATISTICS_CARD_ORDER: 'user_preference_statistics_card_order',
} as const;
// 首页身体指标卡片显示设置接口
export interface StatisticsCardsVisibility {
showMood: boolean;
showSteps: boolean;
showStress: boolean;
showSleep: boolean;
showFitnessRings: boolean;
showWater: boolean;
showBasalMetabolism: boolean;
showOxygenSaturation: boolean;
showMenstrualCycle: boolean;
showWeight: boolean;
showCircumference: boolean;
}
// 默认卡片顺序
export const DEFAULT_CARD_ORDER: string[] = [
'mood',
'steps',
'stress',
'sleep',
'fitness',
'water',
'metabolism',
'oxygen',
'menstrual',
'weight',
'circumference',
];
// 用户偏好设置接口
export interface UserPreferences {
export interface UserPreferences extends StatisticsCardsVisibility {
cardOrder: string[];
quickWaterAmount: number;
waterGoal: number;
notificationEnabled: boolean;
@@ -49,6 +96,22 @@ const DEFAULT_PREFERENCES: UserPreferences = {
nutritionReminderEnabled: true, // 默认开启营养提醒
moodReminderEnabled: true, // 默认开启心情提醒
hrvReminderEnabled: true, // 默认开启 HRV 压力提醒
// 默认显示所有卡片
showMood: true,
showSteps: true,
showStress: true,
showSleep: true,
showFitnessRings: true,
showWater: true,
showBasalMetabolism: true,
showOxygenSaturation: true,
showMenstrualCycle: true,
showWeight: true,
showCircumference: true,
// 默认卡片顺序
cardOrder: DEFAULT_CARD_ORDER,
};
/**
@@ -70,6 +133,21 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
const moodReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.MOOD_REMINDER_ENABLED);
const hrvReminderEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.HRV_REMINDER_ENABLED);
// 获取首页卡片显示设置
const showMood = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_MOOD_CARD);
const showSteps = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_STEPS_CARD);
const showStress = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_STRESS_CARD);
const showSleep = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_SLEEP_CARD);
const showFitnessRings = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_FITNESS_RINGS_CARD);
const showWater = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_WATER_CARD);
const showBasalMetabolism = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_BASAL_METABOLISM_CARD);
const showOxygenSaturation = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_OXYGEN_SATURATION_CARD);
const showMenstrualCycle = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_MENSTRUAL_CYCLE_CARD);
const showWeight = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_WEIGHT_CARD);
const showCircumference = await AsyncStorage.getItem(PREFERENCES_KEYS.SHOW_CIRCUMFERENCE_CARD);
const cardOrderStr = await AsyncStorage.getItem(PREFERENCES_KEYS.STATISTICS_CARD_ORDER);
const cardOrder = cardOrderStr ? JSON.parse(cardOrderStr) : DEFAULT_PREFERENCES.cardOrder;
return {
quickWaterAmount: quickWaterAmount ? parseInt(quickWaterAmount, 10) : DEFAULT_PREFERENCES.quickWaterAmount,
waterGoal: waterGoal ? parseInt(waterGoal, 10) : DEFAULT_PREFERENCES.waterGoal,
@@ -84,6 +162,19 @@ export const getUserPreferences = async (): Promise<UserPreferences> => {
nutritionReminderEnabled: nutritionReminderEnabled !== null ? nutritionReminderEnabled === 'true' : DEFAULT_PREFERENCES.nutritionReminderEnabled,
moodReminderEnabled: moodReminderEnabled !== null ? moodReminderEnabled === 'true' : DEFAULT_PREFERENCES.moodReminderEnabled,
hrvReminderEnabled: hrvReminderEnabled !== null ? hrvReminderEnabled === 'true' : DEFAULT_PREFERENCES.hrvReminderEnabled,
showMood: showMood !== null ? showMood === 'true' : DEFAULT_PREFERENCES.showMood,
showSteps: showSteps !== null ? showSteps === 'true' : DEFAULT_PREFERENCES.showSteps,
showStress: showStress !== null ? showStress === 'true' : DEFAULT_PREFERENCES.showStress,
showSleep: showSleep !== null ? showSleep === 'true' : DEFAULT_PREFERENCES.showSleep,
showFitnessRings: showFitnessRings !== null ? showFitnessRings === 'true' : DEFAULT_PREFERENCES.showFitnessRings,
showWater: showWater !== null ? showWater === 'true' : DEFAULT_PREFERENCES.showWater,
showBasalMetabolism: showBasalMetabolism !== null ? showBasalMetabolism === 'true' : DEFAULT_PREFERENCES.showBasalMetabolism,
showOxygenSaturation: showOxygenSaturation !== null ? showOxygenSaturation === 'true' : DEFAULT_PREFERENCES.showOxygenSaturation,
showMenstrualCycle: showMenstrualCycle !== null ? showMenstrualCycle === 'true' : DEFAULT_PREFERENCES.showMenstrualCycle,
showWeight: showWeight !== null ? showWeight === 'true' : DEFAULT_PREFERENCES.showWeight,
showCircumference: showCircumference !== null ? showCircumference === 'true' : DEFAULT_PREFERENCES.showCircumference,
cardOrder,
};
} catch (error) {
console.error('获取用户偏好设置失败:', error);
@@ -501,3 +592,92 @@ export const getHRVReminderEnabled = async (): Promise<boolean> => {
return DEFAULT_PREFERENCES.hrvReminderEnabled;
}
};
/**
* 获取首页卡片显示设置
*/
export const getStatisticsCardsVisibility = async (): Promise<StatisticsCardsVisibility> => {
try {
const userPreferences = await getUserPreferences();
return {
showMood: userPreferences.showMood,
showSteps: userPreferences.showSteps,
showStress: userPreferences.showStress,
showSleep: userPreferences.showSleep,
showFitnessRings: userPreferences.showFitnessRings,
showWater: userPreferences.showWater,
showBasalMetabolism: userPreferences.showBasalMetabolism,
showOxygenSaturation: userPreferences.showOxygenSaturation,
showMenstrualCycle: userPreferences.showMenstrualCycle,
showWeight: userPreferences.showWeight,
showCircumference: userPreferences.showCircumference,
};
} catch (error) {
console.error('获取首页卡片显示设置失败:', error);
return {
showMood: DEFAULT_PREFERENCES.showMood,
showSteps: DEFAULT_PREFERENCES.showSteps,
showStress: DEFAULT_PREFERENCES.showStress,
showSleep: DEFAULT_PREFERENCES.showSleep,
showFitnessRings: DEFAULT_PREFERENCES.showFitnessRings,
showWater: DEFAULT_PREFERENCES.showWater,
showBasalMetabolism: DEFAULT_PREFERENCES.showBasalMetabolism,
showOxygenSaturation: DEFAULT_PREFERENCES.showOxygenSaturation,
showMenstrualCycle: DEFAULT_PREFERENCES.showMenstrualCycle,
showWeight: DEFAULT_PREFERENCES.showWeight,
showCircumference: DEFAULT_PREFERENCES.showCircumference,
};
}
};
/**
* 获取首页卡片顺序
*/
export const getStatisticsCardOrder = async (): Promise<string[]> => {
try {
const orderStr = await AsyncStorage.getItem(PREFERENCES_KEYS.STATISTICS_CARD_ORDER);
return orderStr ? JSON.parse(orderStr) : DEFAULT_CARD_ORDER;
} catch (error) {
console.error('获取首页卡片顺序失败:', error);
return DEFAULT_CARD_ORDER;
}
};
/**
* 设置首页卡片顺序
*/
export const setStatisticsCardOrder = async (order: string[]): Promise<void> => {
try {
await AsyncStorage.setItem(PREFERENCES_KEYS.STATISTICS_CARD_ORDER, JSON.stringify(order));
} catch (error) {
console.error('设置首页卡片顺序失败:', error);
throw error;
}
};
/**
* 设置首页卡片显示设置
*/
export const setStatisticsCardVisibility = async (key: keyof StatisticsCardsVisibility, value: boolean): Promise<void> => {
try {
let storageKey: string;
switch (key) {
case 'showMood': storageKey = PREFERENCES_KEYS.SHOW_MOOD_CARD; break;
case 'showSteps': storageKey = PREFERENCES_KEYS.SHOW_STEPS_CARD; break;
case 'showStress': storageKey = PREFERENCES_KEYS.SHOW_STRESS_CARD; break;
case 'showSleep': storageKey = PREFERENCES_KEYS.SHOW_SLEEP_CARD; break;
case 'showFitnessRings': storageKey = PREFERENCES_KEYS.SHOW_FITNESS_RINGS_CARD; break;
case 'showWater': storageKey = PREFERENCES_KEYS.SHOW_WATER_CARD; break;
case 'showBasalMetabolism': storageKey = PREFERENCES_KEYS.SHOW_BASAL_METABOLISM_CARD; break;
case 'showOxygenSaturation': storageKey = PREFERENCES_KEYS.SHOW_OXYGEN_SATURATION_CARD; break;
case 'showMenstrualCycle': storageKey = PREFERENCES_KEYS.SHOW_MENSTRUAL_CYCLE_CARD; break;
case 'showWeight': storageKey = PREFERENCES_KEYS.SHOW_WEIGHT_CARD; break;
case 'showCircumference': storageKey = PREFERENCES_KEYS.SHOW_CIRCUMFERENCE_CARD; break;
default: return;
}
await AsyncStorage.setItem(storageKey, value.toString());
} catch (error) {
console.error(`设置首页卡片显示失败 (${key}):`, error);
throw error;
}
};