feat: Enhance Oxygen Saturation Card with health permissions and loading state management
feat(i18n): Add common translations and mood-related strings in English and Chinese fix(i18n): Update metabolism titles for consistency in health translations chore: Update Podfile.lock to include SDWebImage 5.21.4 and other dependency versions refactor(moodCheckins): Improve mood configuration retrieval with optional translation support refactor(sleepHealthKit): Replace useI18n with direct i18n import for sleep quality descriptions
This commit is contained in:
@@ -524,6 +524,7 @@ export default function ExploreScreen() {
|
|||||||
{/* 血氧饱和度卡片 */}
|
{/* 血氧饱和度卡片 */}
|
||||||
<FloatingCard style={styles.masonryCard}>
|
<FloatingCard style={styles.masonryCard}>
|
||||||
<OxygenSaturationCard
|
<OxygenSaturationCard
|
||||||
|
selectedDate={currentSelectedDate}
|
||||||
style={styles.basalMetabolismCardOverride}
|
style={styles.basalMetabolismCardOverride}
|
||||||
/>
|
/>
|
||||||
</FloatingCard>
|
</FloatingCard>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||||
import { useAppSelector } from '@/hooks/redux';
|
import { useAppSelector } from '@/hooks/redux';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { useMoodData } from '@/hooks/useMoodData';
|
import { useMoodData } from '@/hooks/useMoodData';
|
||||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||||
import { getMoodOptions } from '@/services/moodCheckins';
|
import { getMoodOptions } from '@/services/moodCheckins';
|
||||||
@@ -61,6 +62,7 @@ const generateCalendarData = (targetDate: Date) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function MoodCalendarScreen() {
|
export default function MoodCalendarScreen() {
|
||||||
|
const { t } = useI18n();
|
||||||
const safeAreaTop = useSafeAreaTop()
|
const safeAreaTop = useSafeAreaTop()
|
||||||
const params = useLocalSearchParams();
|
const params = useLocalSearchParams();
|
||||||
const { fetchMoodRecords, fetchMoodHistoryRecords } = useMoodData();
|
const { fetchMoodRecords, fetchMoodHistoryRecords } = useMoodData();
|
||||||
@@ -89,9 +91,30 @@ export default function MoodCalendarScreen() {
|
|||||||
return selectLatestMoodRecordByDate(selectedDateString)(state);
|
return selectLatestMoodRecordByDate(selectedDateString)(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
const moodOptions = getMoodOptions();
|
const moodOptions = getMoodOptions(t);
|
||||||
const weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
|
const weekDays = [
|
||||||
const monthNames = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
|
t('mood.calendar.weekDays.monday'),
|
||||||
|
t('mood.calendar.weekDays.tuesday'),
|
||||||
|
t('mood.calendar.weekDays.wednesday'),
|
||||||
|
t('mood.calendar.weekDays.thursday'),
|
||||||
|
t('mood.calendar.weekDays.friday'),
|
||||||
|
t('mood.calendar.weekDays.saturday'),
|
||||||
|
t('mood.calendar.weekDays.sunday'),
|
||||||
|
];
|
||||||
|
const monthNames = [
|
||||||
|
t('mood.calendar.months.january'),
|
||||||
|
t('mood.calendar.months.february'),
|
||||||
|
t('mood.calendar.months.march'),
|
||||||
|
t('mood.calendar.months.april'),
|
||||||
|
t('mood.calendar.months.may'),
|
||||||
|
t('mood.calendar.months.june'),
|
||||||
|
t('mood.calendar.months.july'),
|
||||||
|
t('mood.calendar.months.august'),
|
||||||
|
t('mood.calendar.months.september'),
|
||||||
|
t('mood.calendar.months.october'),
|
||||||
|
t('mood.calendar.months.november'),
|
||||||
|
t('mood.calendar.months.december'),
|
||||||
|
];
|
||||||
|
|
||||||
// 生成当前月份的日历数据
|
// 生成当前月份的日历数据
|
||||||
const { calendar, today, month, year } = generateCalendarData(currentMonth);
|
const { calendar, today, month, year } = generateCalendarData(currentMonth);
|
||||||
@@ -103,7 +126,7 @@ export default function MoodCalendarScreen() {
|
|||||||
const endDate = dayjs(targetMonth).endOf('month').format('YYYY-MM-DD');
|
const endDate = dayjs(targetMonth).endOf('month').format('YYYY-MM-DD');
|
||||||
await fetchMoodHistoryRecordsRef.current({ startDate, endDate });
|
await fetchMoodHistoryRecordsRef.current({ startDate, endDate });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载月份心情数据失败:', error);
|
console.error(t('mood.calendar.errors.loadMonthDataFailed'), error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -112,7 +135,7 @@ export default function MoodCalendarScreen() {
|
|||||||
try {
|
try {
|
||||||
await fetchMoodRecordsRef.current(dateString);
|
await fetchMoodRecordsRef.current(dateString);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载心情记录失败:', error);
|
console.error(t('mood.calendar.errors.loadDailyDataFailed'), error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -235,7 +258,7 @@ export default function MoodCalendarScreen() {
|
|||||||
|
|
||||||
<View style={styles.safeArea}>
|
<View style={styles.safeArea}>
|
||||||
<HeaderBar
|
<HeaderBar
|
||||||
title="心情日历"
|
title={t('mood.calendar.title')}
|
||||||
onBack={() => router.back()}
|
onBack={() => router.back()}
|
||||||
withSafeTop={false}
|
withSafeTop={false}
|
||||||
transparent={true}
|
transparent={true}
|
||||||
@@ -255,7 +278,7 @@ export default function MoodCalendarScreen() {
|
|||||||
>
|
>
|
||||||
<Text style={styles.navButtonText}>‹</Text>
|
<Text style={styles.navButtonText}>‹</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={styles.monthTitle}>{year}年{monthNames[month - 1]}</Text>
|
<Text style={styles.monthTitle}>{year} {monthNames[month - 1]}</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.navButton}
|
style={styles.navButton}
|
||||||
onPress={goToNextMonth}
|
onPress={goToNextMonth}
|
||||||
@@ -315,13 +338,13 @@ export default function MoodCalendarScreen() {
|
|||||||
<View style={styles.selectedDateSection}>
|
<View style={styles.selectedDateSection}>
|
||||||
<View style={styles.selectedDateHeader}>
|
<View style={styles.selectedDateHeader}>
|
||||||
<Text style={styles.selectedDateTitle}>
|
<Text style={styles.selectedDateTitle}>
|
||||||
{selectedDay ? dayjs(currentMonth).date(selectedDay).format('YYYY年M月D日') : '请选择日期'}
|
{selectedDay ? dayjs(currentMonth).date(selectedDay).format(t('mood.calendar.selectedDate.dateFormat')) : t('mood.calendar.selectedDate.selectDate')}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.addMoodButton}
|
style={styles.addMoodButton}
|
||||||
onPress={openMoodEdit}
|
onPress={openMoodEdit}
|
||||||
>
|
>
|
||||||
<Text style={styles.addMoodButtonText}>记录</Text>
|
<Text style={styles.addMoodButtonText}>{t('mood.calendar.selectedDate.record')}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -343,7 +366,7 @@ export default function MoodCalendarScreen() {
|
|||||||
<Text style={styles.recordMood}>
|
<Text style={styles.recordMood}>
|
||||||
{moodOptions.find(m => m.type === selectedDateMood.moodType)?.label}
|
{moodOptions.find(m => m.type === selectedDateMood.moodType)?.label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.recordIntensity}>强度: {selectedDateMood.intensity}</Text>
|
<Text style={styles.recordIntensity}>{t('mood.calendar.selectedDate.intensity')}: {selectedDateMood.intensity}</Text>
|
||||||
{selectedDateMood.description && (
|
{selectedDateMood.description && (
|
||||||
<Text style={styles.recordDescription}>{selectedDateMood.description}</Text>
|
<Text style={styles.recordDescription}>{selectedDateMood.description}</Text>
|
||||||
)}
|
)}
|
||||||
@@ -355,14 +378,14 @@ export default function MoodCalendarScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.emptyRecord}>
|
<View style={styles.emptyRecord}>
|
||||||
<Text style={styles.emptyRecordText}>暂无心情记录</Text>
|
<Text style={styles.emptyRecordText}>{t('mood.calendar.selectedDate.noRecord')}</Text>
|
||||||
<Text style={styles.emptyRecordSubtext}>点击右上角"记录"按钮添加心情</Text>
|
<Text style={styles.emptyRecordSubtext}>{t('mood.calendar.selectedDate.noRecordHint')}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.emptyRecord}>
|
<View style={styles.emptyRecord}>
|
||||||
<Text style={styles.emptyRecordText}>请先选择一个日期</Text>
|
<Text style={styles.emptyRecordText}>{t('mood.calendar.selectedDate.noDateSelected')}</Text>
|
||||||
<Text style={styles.emptyRecordSubtext}>点击日历中的日期,然后点击"记录"按钮添加心情</Text>
|
<Text style={styles.emptyRecordSubtext}>{t('mood.calendar.selectedDate.noDateSelectedHint')}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
|
|||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors';
|
||||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||||
import { getMoodOptions, MoodType } from '@/services/moodCheckins';
|
import { getMoodOptions, MoodType } from '@/services/moodCheckins';
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +32,7 @@ import {
|
|||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
export default function MoodEditScreen() {
|
export default function MoodEditScreen() {
|
||||||
|
const { t } = useI18n();
|
||||||
const safeAreaTop = useSafeAreaTop()
|
const safeAreaTop = useSafeAreaTop()
|
||||||
|
|
||||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||||
@@ -51,7 +53,7 @@ export default function MoodEditScreen() {
|
|||||||
const scrollViewRef = useRef<ScrollView>(null);
|
const scrollViewRef = useRef<ScrollView>(null);
|
||||||
const textInputRef = useRef<TextInput>(null);
|
const textInputRef = useRef<TextInput>(null);
|
||||||
|
|
||||||
const moodOptions = getMoodOptions();
|
const moodOptions = getMoodOptions(t);
|
||||||
|
|
||||||
// 从 Redux 获取数据
|
// 从 Redux 获取数据
|
||||||
const moodRecords = useAppSelector(selectMoodRecordsByDate(selectedDate));
|
const moodRecords = useAppSelector(selectMoodRecordsByDate(selectedDate));
|
||||||
@@ -95,7 +97,7 @@ export default function MoodEditScreen() {
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!selectedMood) {
|
if (!selectedMood) {
|
||||||
Alert.alert('提示', '请选择心情');
|
Alert.alert(t('common.alert'), t('mood.edit.alerts.selectMood'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +122,12 @@ export default function MoodEditScreen() {
|
|||||||
})).unwrap();
|
})).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Alert.alert('成功', existingMood ? '心情记录已更新' : '心情记录已保存', [
|
Alert.alert(t('common.success'), existingMood ? t('mood.edit.alerts.updateSuccess') : t('mood.edit.alerts.saveSuccess'), [
|
||||||
{ text: '确定', onPress: () => router.back() }
|
{ text: t('common.confirm'), onPress: () => router.back() }
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存心情失败:', error);
|
console.error('保存心情失败:', error);
|
||||||
Alert.alert('错误', '保存心情失败,请重试');
|
Alert.alert(t('common.error'), t('mood.edit.alerts.saveError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -135,24 +137,24 @@ export default function MoodEditScreen() {
|
|||||||
if (!existingMood) return;
|
if (!existingMood) return;
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'确认删除',
|
t('mood.edit.alerts.confirmDeleteTitle'),
|
||||||
'确定要删除这条心情记录吗?',
|
t('mood.edit.alerts.confirmDelete'),
|
||||||
[
|
[
|
||||||
{ text: '取消', style: 'cancel' },
|
{ text: t('common.cancel'), style: 'cancel' },
|
||||||
{
|
{
|
||||||
text: '删除',
|
text: t('common.delete'),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
await dispatch(deleteMoodRecord({ id: existingMood.id })).unwrap();
|
await dispatch(deleteMoodRecord({ id: existingMood.id })).unwrap();
|
||||||
|
|
||||||
Alert.alert('成功', '心情记录已删除', [
|
Alert.alert(t('common.success'), t('mood.edit.alerts.deleteSuccess'), [
|
||||||
{ text: '确定', onPress: () => router.back() }
|
{ text: t('common.confirm'), onPress: () => router.back() }
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除心情失败:', error);
|
console.error('删除心情失败:', error);
|
||||||
Alert.alert('错误', '删除心情失败,请重试');
|
Alert.alert(t('common.error'), t('mood.edit.alerts.deleteError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
}
|
}
|
||||||
@@ -183,7 +185,7 @@ export default function MoodEditScreen() {
|
|||||||
<View style={styles.decorativeCircle2} />
|
<View style={styles.decorativeCircle2} />
|
||||||
<View style={styles.safeArea} >
|
<View style={styles.safeArea} >
|
||||||
<HeaderBar
|
<HeaderBar
|
||||||
title={existingMood ? '编辑心情' : '记录心情'}
|
title={existingMood ? t('mood.edit.editTitle') : t('mood.edit.title')}
|
||||||
onBack={() => router.back()}
|
onBack={() => router.back()}
|
||||||
withSafeTop={false}
|
withSafeTop={false}
|
||||||
transparent={true}
|
transparent={true}
|
||||||
@@ -207,13 +209,13 @@ export default function MoodEditScreen() {
|
|||||||
{/* 日期显示 */}
|
{/* 日期显示 */}
|
||||||
<View style={styles.dateSection}>
|
<View style={styles.dateSection}>
|
||||||
<Text style={styles.dateTitle}>
|
<Text style={styles.dateTitle}>
|
||||||
{dayjs(selectedDate).format('YYYY年M月D日')}
|
{dayjs(selectedDate).format(t('mood.edit.dateFormat'))}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 心情选择 */}
|
{/* 心情选择 */}
|
||||||
<View style={styles.moodSection}>
|
<View style={styles.moodSection}>
|
||||||
<Text style={styles.sectionTitle}>选择心情</Text>
|
<Text style={styles.sectionTitle}>{t('mood.edit.selectMood')}</Text>
|
||||||
<View style={styles.moodOptions}>
|
<View style={styles.moodOptions}>
|
||||||
{moodOptions.map((mood, index) => (
|
{moodOptions.map((mood, index) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -233,7 +235,7 @@ export default function MoodEditScreen() {
|
|||||||
|
|
||||||
{/* 心情强度选择 */}
|
{/* 心情强度选择 */}
|
||||||
<View style={styles.intensitySection}>
|
<View style={styles.intensitySection}>
|
||||||
<Text style={styles.sectionTitle}>心情强度</Text>
|
<Text style={styles.sectionTitle}>{t('mood.edit.intensity')}</Text>
|
||||||
<MoodIntensitySlider
|
<MoodIntensitySlider
|
||||||
value={intensity}
|
value={intensity}
|
||||||
onValueChange={handleIntensityChange}
|
onValueChange={handleIntensityChange}
|
||||||
@@ -248,18 +250,12 @@ export default function MoodEditScreen() {
|
|||||||
{/* 心情描述 */}
|
{/* 心情描述 */}
|
||||||
|
|
||||||
<View style={styles.descriptionSection}>
|
<View style={styles.descriptionSection}>
|
||||||
<Text style={styles.sectionTitle}>心情日记</Text>
|
<Text style={styles.sectionTitle}>{t('mood.edit.diary')}</Text>
|
||||||
<Text style={styles.diarySubtitle}>记录你的心情,珍藏美好回忆</Text>
|
<Text style={styles.diarySubtitle}>{t('mood.edit.diarySubtitle')}</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={textInputRef}
|
ref={textInputRef}
|
||||||
style={styles.descriptionInput}
|
style={styles.descriptionInput}
|
||||||
placeholder={`今天的心情如何?
|
placeholder={t('mood.edit.placeholder')}
|
||||||
|
|
||||||
你经历过什么特别的事情吗?
|
|
||||||
有什么让你开心的事?
|
|
||||||
或者,有什么让你感到困扰?
|
|
||||||
|
|
||||||
写下你的感受,让这些时刻成为你珍贵的记忆...`}
|
|
||||||
placeholderTextColor="#a8a8a8"
|
placeholderTextColor="#a8a8a8"
|
||||||
value={description}
|
value={description}
|
||||||
onChangeText={setDescription}
|
onChangeText={setDescription}
|
||||||
@@ -289,7 +285,7 @@ export default function MoodEditScreen() {
|
|||||||
disabled={!selectedMood || isLoading}
|
disabled={!selectedMood || isLoading}
|
||||||
>
|
>
|
||||||
<Text style={styles.saveButtonText}>
|
<Text style={styles.saveButtonText}>
|
||||||
{isLoading ? '保存中...' : existingMood ? '更新心情' : '保存心情'}
|
{isLoading ? t('mood.edit.saving') : existingMood ? t('mood.edit.update') : t('mood.edit.save')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{existingMood && (
|
{existingMood && (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ROUTES } from '@/constants/Routes';
|
import { ROUTES } from '@/constants/Routes';
|
||||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { ChallengeType } from '@/services/challengesApi';
|
import { ChallengeType } from '@/services/challengesApi';
|
||||||
import { reportChallengeProgress, selectChallengeList } from '@/store/challengesSlice';
|
import { reportChallengeProgress, selectChallengeList } from '@/store/challengesSlice';
|
||||||
import { ActivityRingsData, fetchActivityRingsForDate } from '@/utils/health';
|
import { ActivityRingsData, fetchActivityRingsForDate } from '@/utils/health';
|
||||||
@@ -26,6 +27,7 @@ export function FitnessRingsCard({
|
|||||||
selectedDate,
|
selectedDate,
|
||||||
resetToken,
|
resetToken,
|
||||||
}: FitnessRingsCardProps) {
|
}: FitnessRingsCardProps) {
|
||||||
|
const { t } = useI18n();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const challenges = useAppSelector(selectChallengeList);
|
const challenges = useAppSelector(selectChallengeList);
|
||||||
const [activityData, setActivityData] = useState<ActivityRingsData | null>(null);
|
const [activityData, setActivityData] = useState<ActivityRingsData | null>(null);
|
||||||
@@ -135,6 +137,24 @@ export function FitnessRingsCard({
|
|||||||
const exerciseProgress = Math.min(1, Math.max(0, exerciseMinutes / exerciseMinutesGoal));
|
const exerciseProgress = Math.min(1, Math.max(0, exerciseMinutes / exerciseMinutesGoal));
|
||||||
const standProgress = Math.min(1, Math.max(0, standHours / standHoursGoal));
|
const standProgress = Math.min(1, Math.max(0, standHours / standHoursGoal));
|
||||||
|
|
||||||
|
const units = useMemo(
|
||||||
|
() => ({
|
||||||
|
kcal: t('statistics.components.fitness.kcal'),
|
||||||
|
minutes: t('statistics.components.fitness.minutes'),
|
||||||
|
hours: t('statistics.components.fitness.hours'),
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fitnessRows = useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: 'active', value: Math.round(activeCalories), goal: activeCaloriesGoal, unit: units.kcal },
|
||||||
|
{ key: 'exercise', value: Math.round(exerciseMinutes), goal: exerciseMinutesGoal, unit: units.minutes },
|
||||||
|
{ key: 'stand', value: Math.round(standHours), goal: standHoursGoal, unit: units.hours },
|
||||||
|
],
|
||||||
|
[activeCalories, activeCaloriesGoal, exerciseMinutes, exerciseMinutesGoal, standHours, standHoursGoal, units]
|
||||||
|
);
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
router.push(ROUTES.FITNESS_RINGS_DETAIL);
|
router.push(ROUTES.FITNESS_RINGS_DETAIL);
|
||||||
};
|
};
|
||||||
@@ -191,47 +211,23 @@ export function FitnessRingsCard({
|
|||||||
|
|
||||||
{/* 右侧数据显示 */}
|
{/* 右侧数据显示 */}
|
||||||
<View style={styles.dataContainer}>
|
<View style={styles.dataContainer}>
|
||||||
<View style={styles.dataRow}>
|
{fitnessRows.map((row) => (
|
||||||
<Text style={styles.dataText}>
|
<View key={row.key} style={styles.dataRow}>
|
||||||
{loading ? (
|
<Text style={styles.dataText}>
|
||||||
<Text style={styles.dataValue}>--</Text>
|
{loading ? (
|
||||||
) : (
|
<Text style={styles.dataValue}>--</Text>
|
||||||
<>
|
) : (
|
||||||
<Text style={styles.dataValue}>{Math.round(activeCalories)}</Text>
|
<>
|
||||||
<Text style={styles.dataGoal}>/{activeCaloriesGoal}</Text>
|
<Text style={styles.dataValue}>{row.value}</Text>
|
||||||
</>
|
<Text style={styles.dataGoal}>
|
||||||
)}
|
{t('statistics.components.fitnessRings.goal', { goal: row.goal })}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.dataUnit}>千卡</Text>
|
</>
|
||||||
</View>
|
)}
|
||||||
|
</Text>
|
||||||
<View style={styles.dataRow}>
|
<Text style={styles.dataUnit}>{row.unit}</Text>
|
||||||
<Text style={styles.dataText}>
|
</View>
|
||||||
{loading ? (
|
))}
|
||||||
<Text style={styles.dataValue}>--</Text>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Text style={styles.dataValue}>{Math.round(exerciseMinutes)}</Text>
|
|
||||||
<Text style={styles.dataGoal}>/{exerciseMinutesGoal}</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.dataUnit}>分钟</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.dataRow}>
|
|
||||||
<Text style={styles.dataText}>
|
|
||||||
{loading ? (
|
|
||||||
<Text style={styles.dataValue}>--</Text>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Text style={styles.dataValue}>{Math.round(standHours)}</Text>
|
|
||||||
<Text style={styles.dataGoal}>/{standHoursGoal}</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.dataUnit}>小时</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface MoodCardProps {
|
|||||||
|
|
||||||
export function MoodCard({ moodCheckin, onPress }: MoodCardProps) {
|
export function MoodCard({ moodCheckin, onPress }: MoodCardProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const moodConfig = moodCheckin ? getMoodConfig(moodCheckin.moodType) : null;
|
const moodConfig = moodCheckin ? getMoodConfig(moodCheckin.moodType, t) : null;
|
||||||
const animationRef = useRef<LottieView>(null);
|
const animationRef = useRef<LottieView>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { MoodCheckin, getMoodConfig } from '@/services/moodCheckins';
|
import { MoodCheckin, getMoodConfig } from '@/services/moodCheckins';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -8,7 +9,9 @@ interface MoodHistoryCardProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MoodHistoryCard({ moodCheckins, title = '心情记录' }: MoodHistoryCardProps) {
|
export function MoodHistoryCard({ moodCheckins, title }: MoodHistoryCardProps) {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const defaultTitle = t('mood.history.title');
|
||||||
// 计算心情统计
|
// 计算心情统计
|
||||||
const moodStats = React.useMemo(() => {
|
const moodStats = React.useMemo(() => {
|
||||||
const stats = {
|
const stats = {
|
||||||
@@ -26,7 +29,7 @@ export function MoodHistoryCard({ moodCheckins, title = '心情记录' }: MoodHi
|
|||||||
|
|
||||||
// 计算心情分布
|
// 计算心情分布
|
||||||
moodCheckins.forEach(checkin => {
|
moodCheckins.forEach(checkin => {
|
||||||
const moodLabel = getMoodConfig(checkin.moodType)?.label || checkin.moodType;
|
const moodLabel = getMoodConfig(checkin.moodType, t)?.label || checkin.moodType;
|
||||||
stats.moodDistribution[moodLabel] = (stats.moodDistribution[moodLabel] || 0) + 1;
|
stats.moodDistribution[moodLabel] = (stats.moodDistribution[moodLabel] || 0) + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,11 +48,11 @@ export function MoodHistoryCard({ moodCheckins, title = '心情记录' }: MoodHi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>{title}</Text>
|
<Text style={styles.title}>{title || defaultTitle}</Text>
|
||||||
|
|
||||||
{moodCheckins.length === 0 ? (
|
{moodCheckins.length === 0 ? (
|
||||||
<View style={styles.emptyState}>
|
<View style={styles.emptyState}>
|
||||||
<Text style={styles.emptyText}>暂无心情记录</Text>
|
<Text style={styles.emptyText}>{t('mood.history.noRecords')}</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -57,36 +60,36 @@ export function MoodHistoryCard({ moodCheckins, title = '心情记录' }: MoodHi
|
|||||||
<View style={styles.statsContainer}>
|
<View style={styles.statsContainer}>
|
||||||
<View style={styles.statItem}>
|
<View style={styles.statItem}>
|
||||||
<Text style={styles.statValue}>{moodStats.total}</Text>
|
<Text style={styles.statValue}>{moodStats.total}</Text>
|
||||||
<Text style={styles.statLabel}>总记录</Text>
|
<Text style={styles.statLabel}>{t('mood.history.totalRecords')}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.statItem}>
|
<View style={styles.statItem}>
|
||||||
<Text style={styles.statValue}>{moodStats.averageIntensity}</Text>
|
<Text style={styles.statValue}>{moodStats.averageIntensity}</Text>
|
||||||
<Text style={styles.statLabel}>平均强度</Text>
|
<Text style={styles.statLabel}>{t('mood.history.averageIntensity')}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.statItem}>
|
<View style={styles.statItem}>
|
||||||
<Text style={styles.statValue}>{moodStats.mostFrequentMood}</Text>
|
<Text style={styles.statValue}>{moodStats.mostFrequentMood}</Text>
|
||||||
<Text style={styles.statLabel}>最常见</Text>
|
<Text style={styles.statLabel}>{t('mood.history.mostFrequent')}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 最近记录 */}
|
{/* 最近记录 */}
|
||||||
<View style={styles.recentContainer}>
|
<View style={styles.recentContainer}>
|
||||||
<Text style={styles.sectionTitle}>最近记录</Text>
|
<Text style={styles.sectionTitle}>{t('mood.history.recentRecords')}</Text>
|
||||||
{recentMoods.map((checkin, index) => {
|
{recentMoods.map((checkin, index) => {
|
||||||
const moodConfig = getMoodConfig(checkin.moodType);
|
const moodConfig = getMoodConfig(checkin.moodType, t);
|
||||||
return (
|
return (
|
||||||
<View key={checkin.id} style={styles.moodItem}>
|
<View key={checkin.id} style={styles.moodItem}>
|
||||||
<View style={styles.moodInfo}>
|
<View style={styles.moodInfo}>
|
||||||
<Text style={styles.moodEmoji}>{moodConfig?.emoji}</Text>
|
<Text style={styles.moodEmoji}>😊</Text>
|
||||||
<View style={styles.moodDetails}>
|
<View style={styles.moodDetails}>
|
||||||
<Text style={styles.moodLabel}>{moodConfig?.label}</Text>
|
<Text style={styles.moodLabel}>{moodConfig?.label}</Text>
|
||||||
<Text style={styles.moodDate}>
|
<Text style={styles.moodDate}>
|
||||||
{dayjs(checkin.createdAt).format('MM月DD日 HH:mm')}
|
{dayjs(checkin.createdAt).format(t('mood.history.dateTimeFormat'))}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.moodIntensity}>
|
<View style={styles.moodIntensity}>
|
||||||
<Text style={styles.intensityText}>强度 {checkin.intensity}</Text>
|
<Text style={styles.intensityText}>{t('mood.history.intensity')} {checkin.intensity}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Gesture,
|
Gesture,
|
||||||
GestureDetector,
|
GestureDetector,
|
||||||
@@ -38,6 +39,7 @@ export default function MoodIntensitySlider({
|
|||||||
width = 320,
|
width = 320,
|
||||||
height = 16, // 更粗的进度条
|
height = 16, // 更粗的进度条
|
||||||
}: MoodIntensitySliderProps) {
|
}: MoodIntensitySliderProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const thumbSize = 32; // 合适的触摸区域
|
const thumbSize = 32; // 合适的触摸区域
|
||||||
const translateX = useSharedValue(0);
|
const translateX = useSharedValue(0);
|
||||||
const isDragging = useSharedValue(0);
|
const isDragging = useSharedValue(0);
|
||||||
@@ -175,8 +177,8 @@ export default function MoodIntensitySlider({
|
|||||||
|
|
||||||
{/* 标签 */}
|
{/* 标签 */}
|
||||||
<View style={[styles.labelsContainer, { width: width }]}>
|
<View style={[styles.labelsContainer, { width: width }]}>
|
||||||
<Text style={styles.labelText}>轻微</Text>
|
<Text style={styles.labelText}>{t('mood.edit.intensityLow')}</Text>
|
||||||
<Text style={styles.labelText}>强烈</Text>
|
<Text style={styles.labelText}>{t('mood.edit.intensityHigh')}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 刻度 */}
|
{/* 刻度 */}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { useI18n } from '../hooks/useI18n';
|
||||||
import { useNotifications } from '../hooks/useNotifications';
|
import { useNotifications } from '../hooks/useNotifications';
|
||||||
import { ThemedText } from './ThemedText';
|
import { ThemedText } from './ThemedText';
|
||||||
import { ThemedView } from './ThemedView';
|
import { ThemedView } from './ThemedView';
|
||||||
|
|
||||||
export const NotificationTest: React.FC = () => {
|
export const NotificationTest: React.FC = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const {
|
const {
|
||||||
isInitialized,
|
isInitialized,
|
||||||
permissionStatus,
|
permissionStatus,
|
||||||
@@ -95,8 +97,8 @@ export const NotificationTest: React.FC = () => {
|
|||||||
|
|
||||||
const handleSendMoodCheckinReminder = async () => {
|
const handleSendMoodCheckinReminder = async () => {
|
||||||
try {
|
try {
|
||||||
await sendMoodCheckinReminder('心情打卡', '记得记录今天的心情状态哦');
|
await sendMoodCheckinReminder(t('notifications.moodReminder.title'), t('notifications.moodReminder.body'));
|
||||||
Alert.alert('成功', '心情打卡提醒已发送');
|
Alert.alert(t('common.success'), t('notifications.moodReminder.sent'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('错误', '发送心情打卡提醒失败');
|
Alert.alert('错误', '发送心情打卡提醒失败');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { fetchOxygenSaturation } from '@/utils/health';
|
import { ensureHealthPermissions, fetchOxygenSaturation } from '@/utils/health';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
import { HealthKitUtils } from '@/utils/healthKit';
|
||||||
|
import { useIsFocused } from '@react-navigation/native';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React, { useCallback, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import HealthDataCard from './HealthDataCard';
|
import HealthDataCard from './HealthDataCard';
|
||||||
|
|
||||||
@@ -15,42 +16,52 @@ const OxygenSaturationCard: React.FC<OxygenSaturationCardProps> = ({
|
|||||||
selectedDate
|
selectedDate
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isFocused = useIsFocused();
|
||||||
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const loadingRef = useRef(false);
|
const loadingRef = useRef(false);
|
||||||
|
|
||||||
// 获取血氧饱和度数据 - 在页面聚焦、日期变化时触发
|
useEffect(() => {
|
||||||
useFocusEffect(
|
const loadOxygenSaturationData = async () => {
|
||||||
useCallback(() => {
|
const dateToUse = selectedDate || new Date();
|
||||||
const loadOxygenSaturationData = async () => {
|
|
||||||
const dateToUse = selectedDate || new Date();
|
|
||||||
|
|
||||||
// 防止重复请求
|
if (!isFocused) return;
|
||||||
if (loadingRef.current) return;
|
if (!HealthKitUtils.isAvailable()) {
|
||||||
|
setOxygenSaturation(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
// 防止重复请求
|
||||||
loadingRef.current = true;
|
if (loadingRef.current) return;
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const options = {
|
try {
|
||||||
startDate: dayjs(dateToUse).startOf('day').toDate().toISOString(),
|
loadingRef.current = true;
|
||||||
endDate: dayjs(dateToUse).endOf('day').toDate().toISOString()
|
setLoading(true);
|
||||||
};
|
|
||||||
|
|
||||||
const data = await fetchOxygenSaturation(options);
|
const hasPermission = await ensureHealthPermissions();
|
||||||
setOxygenSaturation(data);
|
if (!hasPermission) {
|
||||||
} catch (error) {
|
|
||||||
console.error('OxygenSaturationCard: Failed to get blood oxygen data:', error);
|
|
||||||
setOxygenSaturation(null);
|
setOxygenSaturation(null);
|
||||||
} finally {
|
return;
|
||||||
setLoading(false);
|
|
||||||
loadingRef.current = false;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
loadOxygenSaturationData();
|
const options = {
|
||||||
}, [selectedDate])
|
startDate: dayjs(dateToUse).startOf('day').toDate().toISOString(),
|
||||||
);
|
endDate: dayjs(dateToUse).endOf('day').toDate().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await fetchOxygenSaturation(options);
|
||||||
|
setOxygenSaturation(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('OxygenSaturationCard: Failed to get blood oxygen data:', error);
|
||||||
|
setOxygenSaturation(null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
loadingRef.current = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadOxygenSaturationData();
|
||||||
|
}, [isFocused, selectedDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HealthDataCard
|
<HealthDataCard
|
||||||
|
|||||||
@@ -3,3 +3,12 @@ export const dateSelector = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const common = {
|
||||||
|
alert: 'Alert',
|
||||||
|
success: 'Success',
|
||||||
|
error: 'Error',
|
||||||
|
delete: 'Delete',
|
||||||
|
confirm: 'Confirm',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
};
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const statistics = {
|
|||||||
addButton: '+ {{amount}}ml',
|
addButton: '+ {{amount}}ml',
|
||||||
},
|
},
|
||||||
metabolism: {
|
metabolism: {
|
||||||
title: 'Basal Metabolism',
|
title: 'Metabolism',
|
||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
unit: 'kcal/day',
|
unit: 'kcal/day',
|
||||||
status: {
|
status: {
|
||||||
@@ -406,7 +406,7 @@ export const circumferenceDetail = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const basalMetabolismDetail = {
|
export const basalMetabolismDetail = {
|
||||||
title: 'Basal Metabolism',
|
title: 'Metabolism',
|
||||||
currentData: {
|
currentData: {
|
||||||
title: '{{date}} Basal Metabolism',
|
title: '{{date}} Basal Metabolism',
|
||||||
unit: 'kcal',
|
unit: 'kcal',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as Common from './common';
|
|||||||
import * as Diet from './diet';
|
import * as Diet from './diet';
|
||||||
import * as Health from './health';
|
import * as Health from './health';
|
||||||
import * as Medication from './medication';
|
import * as Medication from './medication';
|
||||||
|
import * as Mood from './mood';
|
||||||
import * as Personal from './personal';
|
import * as Personal from './personal';
|
||||||
import * as Weight from './weight';
|
import * as Weight from './weight';
|
||||||
|
|
||||||
@@ -13,5 +14,7 @@ export default {
|
|||||||
...Medication,
|
...Medication,
|
||||||
...Weight,
|
...Weight,
|
||||||
...Challenge,
|
...Challenge,
|
||||||
|
...Mood,
|
||||||
...Common,
|
...Common,
|
||||||
|
...Common.common, // 确保通用翻译被正确导出
|
||||||
};
|
};
|
||||||
|
|||||||
95
i18n/en/mood.ts
Normal file
95
i18n/en/mood.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
export const mood = {
|
||||||
|
calendar: {
|
||||||
|
title: 'Mood Calendar',
|
||||||
|
weekDays: {
|
||||||
|
monday: 'Mon',
|
||||||
|
tuesday: 'Tue',
|
||||||
|
wednesday: 'Wed',
|
||||||
|
thursday: 'Thu',
|
||||||
|
friday: 'Fri',
|
||||||
|
saturday: 'Sat',
|
||||||
|
sunday: 'Sun',
|
||||||
|
},
|
||||||
|
months: {
|
||||||
|
january: 'Jan',
|
||||||
|
february: 'Feb',
|
||||||
|
march: 'Mar',
|
||||||
|
april: 'Apr',
|
||||||
|
may: 'May',
|
||||||
|
june: 'Jun',
|
||||||
|
july: 'Jul',
|
||||||
|
august: 'Aug',
|
||||||
|
september: 'Sep',
|
||||||
|
october: 'Oct',
|
||||||
|
november: 'Nov',
|
||||||
|
december: 'Dec',
|
||||||
|
},
|
||||||
|
selectedDate: {
|
||||||
|
selectDate: 'Please select a date',
|
||||||
|
record: 'Record',
|
||||||
|
noRecord: 'No mood records',
|
||||||
|
noRecordHint: 'Click the "Record" button in the top right to add mood',
|
||||||
|
noDateSelected: 'Please select a date first',
|
||||||
|
noDateSelectedHint: 'Click a date in the calendar, then click the "Record" button to add mood',
|
||||||
|
intensity: 'Intensity',
|
||||||
|
dateFormat: 'MMMM D, YYYY',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
loadMonthDataFailed: 'Failed to load monthly mood data',
|
||||||
|
loadDailyDataFailed: 'Failed to load mood records',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
happy: 'Happy',
|
||||||
|
excited: 'Excited',
|
||||||
|
thrilled: 'Thrilled',
|
||||||
|
calm: 'Calm',
|
||||||
|
anxious: 'Anxious',
|
||||||
|
sad: 'Sad',
|
||||||
|
lonely: 'Lonely',
|
||||||
|
wronged: 'Wronged',
|
||||||
|
angry: 'Angry',
|
||||||
|
tired: 'Tired',
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: 'Record Mood',
|
||||||
|
editTitle: 'Edit Mood',
|
||||||
|
selectMood: 'Select Mood',
|
||||||
|
intensity: 'Mood Intensity',
|
||||||
|
intensityLow: 'Low',
|
||||||
|
intensityHigh: 'High',
|
||||||
|
diary: 'Mood Diary',
|
||||||
|
diarySubtitle: 'Record your feelings and cherish beautiful memories',
|
||||||
|
placeholder: `How are you feeling today?
|
||||||
|
|
||||||
|
Did you experience anything special?
|
||||||
|
What made you happy?
|
||||||
|
Or, what's bothering you?
|
||||||
|
|
||||||
|
Write down your feelings and let these moments become your precious memories...`,
|
||||||
|
save: 'Save Mood',
|
||||||
|
update: 'Update Mood',
|
||||||
|
saving: 'Saving...',
|
||||||
|
dateFormat: 'MMMM D, YYYY',
|
||||||
|
alerts: {
|
||||||
|
selectMood: 'Please select a mood',
|
||||||
|
saveSuccess: 'Mood record saved',
|
||||||
|
updateSuccess: 'Mood record updated',
|
||||||
|
deleteSuccess: 'Mood record deleted',
|
||||||
|
saveError: 'Failed to save mood, please try again',
|
||||||
|
deleteError: 'Failed to delete mood, please try again',
|
||||||
|
confirmDelete: 'Are you sure you want to delete this mood record?',
|
||||||
|
confirmDeleteTitle: 'Confirm Delete',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
title: 'Mood Records',
|
||||||
|
noRecords: 'No mood records',
|
||||||
|
totalRecords: 'Total Records',
|
||||||
|
averageIntensity: 'Average Intensity',
|
||||||
|
mostFrequent: 'Most Frequent',
|
||||||
|
recentRecords: 'Recent Records',
|
||||||
|
intensity: 'Intensity',
|
||||||
|
dateTimeFormat: 'MM/DD HH:mm',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -3,3 +3,12 @@ export const dateSelector = {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
confirm: '确定',
|
confirm: '确定',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const common = {
|
||||||
|
alert: '提示',
|
||||||
|
success: '成功',
|
||||||
|
error: '错误',
|
||||||
|
delete: '删除',
|
||||||
|
confirm: '确定',
|
||||||
|
cancel: '取消',
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as Common from './common';
|
|||||||
import * as Diet from './diet';
|
import * as Diet from './diet';
|
||||||
import * as Health from './health';
|
import * as Health from './health';
|
||||||
import * as Medication from './medication';
|
import * as Medication from './medication';
|
||||||
|
import * as Mood from './mood';
|
||||||
import * as Personal from './personal';
|
import * as Personal from './personal';
|
||||||
import * as Weight from './weight';
|
import * as Weight from './weight';
|
||||||
|
|
||||||
@@ -13,5 +14,7 @@ export default {
|
|||||||
...Medication,
|
...Medication,
|
||||||
...Weight,
|
...Weight,
|
||||||
...Challenge,
|
...Challenge,
|
||||||
|
...Mood,
|
||||||
...Common,
|
...Common,
|
||||||
|
...Common.common, // 确保通用翻译被正确导出
|
||||||
};
|
};
|
||||||
|
|||||||
95
i18n/zh/mood.ts
Normal file
95
i18n/zh/mood.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
export const mood = {
|
||||||
|
calendar: {
|
||||||
|
title: '心情日历',
|
||||||
|
weekDays: {
|
||||||
|
monday: '周一',
|
||||||
|
tuesday: '周二',
|
||||||
|
wednesday: '周三',
|
||||||
|
thursday: '周四',
|
||||||
|
friday: '周五',
|
||||||
|
saturday: '周六',
|
||||||
|
sunday: '周日',
|
||||||
|
},
|
||||||
|
months: {
|
||||||
|
january: '1月',
|
||||||
|
february: '2月',
|
||||||
|
march: '3月',
|
||||||
|
april: '4月',
|
||||||
|
may: '5月',
|
||||||
|
june: '6月',
|
||||||
|
july: '7月',
|
||||||
|
august: '8月',
|
||||||
|
september: '9月',
|
||||||
|
october: '10月',
|
||||||
|
november: '11月',
|
||||||
|
december: '12月',
|
||||||
|
},
|
||||||
|
selectedDate: {
|
||||||
|
selectDate: '请选择日期',
|
||||||
|
record: '记录',
|
||||||
|
noRecord: '暂无心情记录',
|
||||||
|
noRecordHint: '点击右上角"记录"按钮添加心情',
|
||||||
|
noDateSelected: '请先选择一个日期',
|
||||||
|
noDateSelectedHint: '点击日历中的日期,然后点击"记录"按钮添加心情',
|
||||||
|
intensity: '强度',
|
||||||
|
dateFormat: 'YYYY年M月D日',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
loadMonthDataFailed: '加载月份心情数据失败',
|
||||||
|
loadDailyDataFailed: '加载心情记录失败',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
happy: '开心',
|
||||||
|
excited: '心动',
|
||||||
|
thrilled: '兴奋',
|
||||||
|
calm: '平静',
|
||||||
|
anxious: '焦虑',
|
||||||
|
sad: '难过',
|
||||||
|
lonely: '孤独',
|
||||||
|
wronged: '委屈',
|
||||||
|
angry: '生气',
|
||||||
|
tired: '心累',
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: '记录心情',
|
||||||
|
editTitle: '编辑心情',
|
||||||
|
selectMood: '选择心情',
|
||||||
|
intensity: '心情强度',
|
||||||
|
intensityLow: '轻微',
|
||||||
|
intensityHigh: '强烈',
|
||||||
|
diary: '心情日记',
|
||||||
|
diarySubtitle: '记录你的心情,珍藏美好回忆',
|
||||||
|
placeholder: `今天的心情如何?
|
||||||
|
|
||||||
|
你经历过什么特别的事情吗?
|
||||||
|
有什么让你开心的事?
|
||||||
|
或者,有什么让你感到困扰?
|
||||||
|
|
||||||
|
写下你的感受,让这些时刻成为你珍贵的记忆...`,
|
||||||
|
save: '保存心情',
|
||||||
|
update: '更新心情',
|
||||||
|
saving: '保存中...',
|
||||||
|
dateFormat: 'YYYY年M月D日',
|
||||||
|
alerts: {
|
||||||
|
selectMood: '请选择心情',
|
||||||
|
saveSuccess: '心情记录已保存',
|
||||||
|
updateSuccess: '心情记录已更新',
|
||||||
|
deleteSuccess: '心情记录已删除',
|
||||||
|
saveError: '保存心情失败,请重试',
|
||||||
|
deleteError: '删除心情失败,请重试',
|
||||||
|
confirmDelete: '确定要删除这条心情记录吗?',
|
||||||
|
confirmDeleteTitle: '确认删除',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
title: '心情记录',
|
||||||
|
noRecords: '暂无心情记录',
|
||||||
|
totalRecords: '总记录',
|
||||||
|
averageIntensity: '平均强度',
|
||||||
|
mostFrequent: '最常见',
|
||||||
|
recentRecords: '最近记录',
|
||||||
|
intensity: '强度',
|
||||||
|
dateTimeFormat: 'MM月DD日 HH:mm',
|
||||||
|
},
|
||||||
|
};
|
||||||
216
ios/Podfile.lock
216
ios/Podfile.lock
@@ -2288,9 +2288,9 @@ PODS:
|
|||||||
- ReactCommon/turbomodule/core
|
- ReactCommon/turbomodule/core
|
||||||
- ReactNativeDependencies
|
- ReactNativeDependencies
|
||||||
- Yoga
|
- Yoga
|
||||||
- SDWebImage (5.21.3):
|
- SDWebImage (5.21.4):
|
||||||
- SDWebImage/Core (= 5.21.3)
|
- SDWebImage/Core (= 5.21.4)
|
||||||
- SDWebImage/Core (5.21.3)
|
- SDWebImage/Core (5.21.4)
|
||||||
- SDWebImageAVIFCoder (0.11.1):
|
- SDWebImageAVIFCoder (0.11.1):
|
||||||
- libavif/core (>= 0.11.0)
|
- libavif/core (>= 0.11.0)
|
||||||
- SDWebImage (~> 5.10)
|
- SDWebImage (~> 5.10)
|
||||||
@@ -2684,129 +2684,129 @@ 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: 111394d38f32be09385d4c7f70cc96d2da438d0d
|
Expo: e852e4b236f95ef9fee36ea9ab20bc6f59c76a10
|
||||||
ExpoAppleAuthentication: bc9de6e9ff3340604213ab9031d4c4f7f802623e
|
ExpoAppleAuthentication: 414e4316f8e25a2afbc3943cf725579c910f24b8
|
||||||
ExpoAsset: d839c8eae8124470332408427327e8f88beb2dfd
|
ExpoAsset: ee515c16290e521de1870dcdee66d78173fbc533
|
||||||
ExpoBackgroundTask: e0d201d38539c571efc5f9cb661fae8ab36ed61b
|
ExpoBackgroundTask: e048da30cd2d669c5ba20d5d704bee8dd6da320c
|
||||||
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f
|
ExpoBlur: b5b7a26572b3c33a11f0b2aa2f95c17c4c393b76
|
||||||
ExpoCamera: 2a87c210f8955350ea5c70f1d539520b2fc5d940
|
ExpoCamera: d1894aad960492f4881c1f83967167963365d937
|
||||||
ExpoClipboard: af650d14765f19c60ce2a1eaf9dfe6445eff7365
|
ExpoClipboard: 99109306a2d9ed2fbd16f6b856e6267b2afa8472
|
||||||
ExpoFileSystem: 77157a101e03150a4ea4f854b4dd44883c93ae0a
|
ExpoFileSystem: 73a9f3f2e0affc61eba5b9326153f64870438af1
|
||||||
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961
|
ExpoFont: b881d43057dceb7b31ff767b24f612609e80f60f
|
||||||
ExpoGlassEffect: 265fa3d75b46bc58262e4dfa513135fa9dfe4aac
|
ExpoGlassEffect: 3d147d753d3bfe1a5d6b7920560e206e3e98c19e
|
||||||
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84
|
ExpoHaptics: b48d913e7e5f23816c6f130e525c9a6501b160b5
|
||||||
ExpoHead: 95a6ee0be1142320bccf07961d6a1502ded5d6ac
|
ExpoHead: 16bab3395d4328e88e4282a4f6ef4f44b1225c8d
|
||||||
ExpoImage: 9c3428921c536ab29e5c6721d001ad5c1f469566
|
ExpoImage: 6eb842cd07817402640545c41884dd7f5fbfbca5
|
||||||
ExpoImagePicker: d251aab45a1b1857e4156fed88511b278b4eee1c
|
ExpoImagePicker: bd0a5c81d7734548f6908a480609257e85d19ea8
|
||||||
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe
|
ExpoKeepAwake: 3f5e3ac53627849174f3603271df8e08f174ed4a
|
||||||
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27
|
ExpoLinearGradient: f9e7182e5253d53b2de4134b69d70bbfc2d50588
|
||||||
ExpoLinking: 77455aa013e9b6a3601de03ecfab09858ee1b031
|
ExpoLinking: 50a65cd7beb6051ffc82f84ffb33961dd5e55a5b
|
||||||
ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca
|
ExpoLocalization: 6c6f0f89ad2822001ab0bc2eb6d4d980c77f080c
|
||||||
ExpoMediaLibrary: 641a6952299b395159ccd459bd8f5f6764bf55fe
|
ExpoMediaLibrary: 648cee3f5dcba13410ec9cc8ac9a426e89a61a31
|
||||||
ExpoModulesCore: e8ec7f8727caf51a49d495598303dd420ca994bf
|
ExpoModulesCore: e37f2bfc6f5b553989e1a67e15b7c5c8bbcac0cc
|
||||||
ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f
|
ExpoQuickActions: 62b9db8a20618be1cc19efa3b562ac963c803d58
|
||||||
ExpoSplashScreen: 268b2f128dc04284c21010540a6c4dd9f95003e3
|
ExpoSplashScreen: f46795cd52cdad65d30e54043f04c86401c4f85d
|
||||||
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: a4837eebc889b973668af685d6c23b89a038461d
|
PurchasesHybridCommon: a4837eebc889b973668af685d6c23b89a038461d
|
||||||
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: 1e61140a343a77dc286f171b3ffab99ca09a4b57
|
RevenueCat: 1e61140a343a77dc286f171b3ffab99ca09a4b57
|
||||||
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
|
RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4
|
||||||
RNCMaskedView: d2578d41c59b936db122b2798ba37e4722d21035
|
RNCMaskedView: 3c9d7586e2b9bbab573591dcb823918bc4668005
|
||||||
RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
|
RNCPicker: e0149590451d5eae242cf686014a6f6d808f93c7
|
||||||
RNDateTimePicker: 19ffa303c4524ec0a2dfdee2658198451c16b7f1
|
RNDateTimePicker: 5e0a759109b63ebc661a4714712361d2d07142fe
|
||||||
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
|
RNDeviceInfo: 8b6fa8379062949dd79a009cf3d6b02a9c03ca59
|
||||||
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
|
RNGestureHandler: 6a488ce85c88e82d8610db1108daf04e9b2d5162
|
||||||
RNPurchases: 5f3cd4fea5ef2b3914c925b2201dd5cecd31922f
|
RNPurchases: 121c761620615ae624ba7a74e2e5b94254995e6c
|
||||||
RNReanimated: 1442a577e066e662f0ce1cd1864a65c8e547aee0
|
RNReanimated: 00771d2ba7c810124852092eb29c0d1edf50cc58
|
||||||
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
|
RNScreens: dd61bc3a3e6f6901ad833efa411917d44827cf51
|
||||||
RNSentry: 1d7b9fdae7a01ad8f9053335b5d44e75c39a955e
|
RNSentry: 54f8041cd06d7ccf484171edde72f1b07323fb2e
|
||||||
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34
|
RNSVG: 2825ee146e0f6a16221e852299943e4cceef4528
|
||||||
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1
|
RNWorklets: 83609071441ac7d623f1e0e63b9043f4f345e2a2
|
||||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
SDWebImage: d0184764be51240d49c761c37f53dd017e1ccaaf
|
||||||
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
|
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
|
||||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||||
|
|||||||
@@ -146,14 +146,26 @@ export type MoodOption = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 获取心情配置
|
// 获取心情配置
|
||||||
export function getMoodConfig(moodType: MoodType) {
|
export function getMoodConfig(moodType: MoodType, t?: (key: string) => string) {
|
||||||
return MOOD_CONFIG[moodType];
|
const config = MOOD_CONFIG[moodType];
|
||||||
|
|
||||||
|
// When translation function is provided, prefer localized labels.
|
||||||
|
if (t) {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
label: t(`mood.types.${moodType}`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有心情选项
|
// 获取所有心情选项
|
||||||
export function getMoodOptions(): MoodOption[] {
|
export function getMoodOptions(t?: (key: string) => string): MoodOption[] {
|
||||||
return Object.entries(MOOD_CONFIG).map(([type, config]) => ({
|
return Object.entries(MOOD_CONFIG).map(([type, config]) => ({
|
||||||
type: type as MoodType,
|
type: type as MoodType,
|
||||||
...config,
|
...config,
|
||||||
|
// 如果提供了翻译函数,则使用翻译后的标签,否则使用默认的中文标签
|
||||||
|
label: t ? t(`mood.types.${type}`) : config.label,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useI18n } from '@/hooks/useI18n';
|
import i18n from '@/i18n';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import HealthKitManager, { HealthKitUtils } from './healthKit';
|
import HealthKitManager, { HealthKitUtils } from './healthKit';
|
||||||
|
|
||||||
@@ -341,27 +341,25 @@ export const calculateSleepScore = (
|
|||||||
* 获取睡眠质量描述和建议
|
* 获取睡眠质量描述和建议
|
||||||
*/
|
*/
|
||||||
export const getSleepQualityInfo = (sleepScore: number): { description: string; recommendation: string } => {
|
export const getSleepQualityInfo = (sleepScore: number): { description: string; recommendation: string } => {
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
if (sleepScore >= 85) {
|
if (sleepScore >= 85) {
|
||||||
return {
|
return {
|
||||||
description: t('sleepQuality.excellent.description'),
|
description: i18n.t('sleepQuality.excellent.description'),
|
||||||
recommendation: t('sleepQuality.excellent.recommendation')
|
recommendation: i18n.t('sleepQuality.excellent.recommendation')
|
||||||
};
|
};
|
||||||
} else if (sleepScore >= 70) {
|
} else if (sleepScore >= 70) {
|
||||||
return {
|
return {
|
||||||
description: t('sleepQuality.good.description'),
|
description: i18n.t('sleepQuality.good.description'),
|
||||||
recommendation: t('sleepQuality.good.recommendation')
|
recommendation: i18n.t('sleepQuality.good.recommendation')
|
||||||
};
|
};
|
||||||
} else if (sleepScore >= 50) {
|
} else if (sleepScore >= 50) {
|
||||||
return {
|
return {
|
||||||
description: t('sleepQuality.fair.description'),
|
description: i18n.t('sleepQuality.fair.description'),
|
||||||
recommendation: t('sleepQuality.fair.recommendation')
|
recommendation: i18n.t('sleepQuality.fair.recommendation')
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
description: t('sleepQuality.poor.description'),
|
description: i18n.t('sleepQuality.poor.description'),
|
||||||
recommendation: t('sleepQuality.poor.recommendation')
|
recommendation: i18n.t('sleepQuality.poor.recommendation')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user