feat: 更新心情记录功能及相关组件

- 在心情日历中新增心情圆环展示,显示心情强度
- 修改心情记录编辑页面,支持使用图标替代表情
- 优化心情类型配置,使用图片资源替代原有表情
- 新增多种心情图标,丰富用户选择
- 更新相关样式,提升用户体验和界面美观性
- 更新文档,详细描述新功能和使用方法
This commit is contained in:
richarjiang
2025-08-25 09:33:54 +08:00
parent 23aa15f76e
commit 4f2d47c23f
17 changed files with 298 additions and 144 deletions

View File

@@ -3,8 +3,9 @@ import { Colors } from '@/constants/Colors';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
import { useMoodData } from '@/hooks/useMoodData';
import { getMoodOptions } from '@/services/moodCheckins';
import { getMoodOptions, MoodOption } from '@/services/moodCheckins';
import { selectLatestMoodRecordByDate } from '@/store/moodSlice';
import { Image } from 'react-native';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
import { router, useFocusEffect, useLocalSearchParams } from 'expo-router';
@@ -181,7 +182,7 @@ export default function MoodCalendarScreen() {
});
};
const renderMoodIcon = (day: number | null, isSelected: boolean) => {
const renderMoodRing = (day: number | null, isSelected: boolean) => {
if (!day) return null;
// 检查该日期是否有心情记录 - 现在从 Redux store 中获取
@@ -189,20 +190,40 @@ export default function MoodCalendarScreen() {
const dayRecords = moodRecords[dayDateString] || [];
const moodRecord = dayRecords.length > 0 ? dayRecords[0] : null;
const isToday = day === new Date().getDate() &&
month === new Date().getMonth() + 1 &&
year === new Date().getFullYear();
if (moodRecord) {
const mood = moodOptions.find(m => m.type === moodRecord.moodType);
const intensity = moodRecord.intensity;
const color = mood?.color || '#7a5af8';
// 计算圆环的填充比例 (0-1)
const fillRatio = intensity / 10;
return (
<View style={[styles.moodIconContainer, { backgroundColor: mood?.color }]}>
<View style={styles.moodIcon}>
<Text style={styles.moodEmoji}>{mood?.emoji || '😊'}</Text>
<View style={isToday ? styles.todayMoodRingContainer : styles.moodRingContainer}>
<View style={[isToday ? styles.todayMoodRing : styles.moodRing, { borderColor: color }]}>
<View style={[
styles.moodRingFill,
{
backgroundColor: color,
height: `${fillRatio * 100}%`,
opacity: 0.7,
}
]} />
<Text style={[styles.moodIntensityText, { color: '#fff', fontSize: isToday ? 7 : 8 }]}>
{intensity}
</Text>
</View>
</View>
);
}
return (
<View style={styles.defaultMoodIcon}>
<Text style={styles.defaultMoodEmoji}>😊</Text>
<View style={isToday ? styles.todayDefaultMoodRing : styles.defaultMoodRing}>
<View style={isToday ? styles.todayDefaultMoodRingBorder : styles.defaultMoodRingBorder} />
</View>
);
};
@@ -285,7 +306,7 @@ export default function MoodCalendarScreen() {
]}>
{day.toString().padStart(2, '0')}
</Text>
{renderMoodIcon(day, isSelected)}
{renderMoodRing(day, isSelected)}
</View>
</TouchableOpacity>
)}
@@ -318,9 +339,10 @@ export default function MoodCalendarScreen() {
>
<View style={styles.recordIcon}>
<View style={styles.moodIcon}>
<Text style={styles.moodEmoji}>
{moodOptions.find(m => m.type === selectedDateMood.moodType)?.emoji || '😊'}
</Text>
<Image
source={moodOptions.find(m => m.type === selectedDateMood.moodType)?.image}
style={styles.moodIconImage}
/>
</View>
</View>
<View style={styles.recordContent}>
@@ -524,8 +546,10 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
moodEmoji: {
fontSize: 11,
moodIconImage: {
width: 18,
height: 18,
borderRadius: 9,
},
defaultMoodIcon: {
position: 'absolute',
@@ -545,6 +569,104 @@ const styles = StyleSheet.create({
opacity: 0.4,
color: '#7a5af8',
},
moodRingContainer: {
position: 'absolute',
bottom: 2,
width: 22,
height: 22,
justifyContent: 'center',
alignItems: 'center',
},
moodRing: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 1.5,
justifyContent: 'flex-end',
alignItems: 'center',
overflow: 'hidden',
backgroundColor: 'rgba(255,255,255,0.95)',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
},
moodRingFill: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
},
moodIntensityText: {
fontSize: 8,
fontWeight: '800',
textAlign: 'center',
position: 'absolute',
zIndex: 1,
textShadowColor: 'rgba(0,0,0,0.3)',
textShadowOffset: { width: 0, height: 0.5 },
textShadowRadius: 1,
},
defaultMoodRing: {
position: 'absolute',
bottom: 2,
width: 22,
height: 22,
justifyContent: 'center',
alignItems: 'center',
},
defaultMoodRingBorder: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 1.5,
borderColor: 'rgba(122,90,248,0.3)',
borderStyle: 'dashed',
backgroundColor: 'rgba(122,90,248,0.05)',
},
todayMoodRingContainer: {
position: 'absolute',
bottom: 1,
width: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
},
todayMoodRing: {
width: 18,
height: 18,
borderRadius: 9,
borderWidth: 1.5,
justifyContent: 'flex-end',
alignItems: 'center',
overflow: 'hidden',
backgroundColor: 'rgba(255,255,255,0.95)',
shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
},
todayDefaultMoodRing: {
position: 'absolute',
bottom: 1,
width: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
},
todayDefaultMoodRingBorder: {
width: 18,
height: 18,
borderRadius: 9,
borderWidth: 1.5,
borderColor: 'rgba(122,90,248,0.4)',
borderStyle: 'dashed',
backgroundColor: 'rgba(122,90,248,0.08)',
},
selectedDateSection: {
backgroundColor: 'rgba(255,255,255,0.95)',
margin: 16,

View File

@@ -11,20 +11,21 @@ import {
selectMoodRecordsByDate,
updateMoodRecord
} from '@/store/moodSlice';
import { Ionicons } from '@expo/vector-icons';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
import { router, useLocalSearchParams } from 'expo-router';
import React, { useEffect, useState } from 'react';
import {
Alert,
SafeAreaView,
Alert, Image,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
View
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function MoodEditScreen() {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
@@ -149,11 +150,11 @@ export default function MoodEditScreen() {
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
/>
{/* 装饰性圆圈 */}
<View style={styles.decorativeCircle1} />
<View style={styles.decorativeCircle2} />
<SafeAreaView style={styles.safeArea}>
<SafeAreaView style={styles.safeArea} edges={['top']}>
<HeaderBar
title={existingMood ? '编辑心情' : '记录心情'}
onBack={() => router.back()}
@@ -183,7 +184,7 @@ export default function MoodEditScreen() {
]}
onPress={() => setSelectedMood(mood.type)}
>
<Text style={styles.moodEmoji}>{mood.emoji}</Text>
<Image source={mood.image} style={styles.moodImage} />
<Text style={styles.moodLabel}>{mood.label}</Text>
</TouchableOpacity>
))}
@@ -224,26 +225,27 @@ export default function MoodEditScreen() {
{/* 底部按钮 */}
<View style={styles.footer}>
{existingMood && (
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.deleteButton, isDeleting && styles.disabledButton]}
onPress={handleDelete}
disabled={isDeleting}
style={[styles.saveButton, (!selectedMood || isLoading) && styles.disabledButton]}
onPress={handleSave}
disabled={!selectedMood || isLoading}
>
<Text style={styles.deleteButtonText}>
{isDeleting ? '删除中...' : '删除记录'}
<Text style={styles.saveButtonText}>
{isLoading ? '保存中...' : existingMood ? '更新心情' : '保存心情'}
</Text>
</TouchableOpacity>
)}
<TouchableOpacity
style={[styles.saveButton, (!selectedMood || isLoading) && styles.disabledButton]}
onPress={handleSave}
disabled={!selectedMood || isLoading}
>
<Text style={styles.saveButtonText}>
{isLoading ? '保存中...' : existingMood ? '更新心情' : '保存心情'}
</Text>
</TouchableOpacity>
{existingMood && (
<TouchableOpacity
style={[styles.deleteIconButton, isDeleting && styles.disabledButton]}
onPress={handleDelete}
disabled={isDeleting}
>
<Ionicons name="trash-outline" size={24} color="#f95555" />
</TouchableOpacity>
)}
</View>
</View>
</SafeAreaView>
</View>
@@ -329,10 +331,10 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
},
moodOption: {
width: '30%',
width: '18%',
alignItems: 'center',
paddingVertical: 20,
marginBottom: 16,
paddingVertical: 16,
marginBottom: 12,
borderRadius: 16,
backgroundColor: 'rgba(122,90,248,0.05)',
borderWidth: 1,
@@ -348,8 +350,9 @@ const styles = StyleSheet.create({
shadowRadius: 4,
elevation: 2,
},
moodEmoji: {
fontSize: 28,
moodImage: {
width: 40,
height: 40,
marginBottom: 10,
},
moodLabel: {
@@ -401,30 +404,42 @@ const styles = StyleSheet.create({
fontWeight: '500',
},
footer: {
padding: 20,
backgroundColor: 'rgba(255,255,255,0.95)',
shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: -4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 6,
padding: 16,
position: 'absolute',
bottom: 24,
right: 8,
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
},
saveButton: {
backgroundColor: '#7a5af8',
borderRadius: 16,
paddingVertical: 18,
borderRadius: 12,
paddingVertical: 12,
paddingHorizontal: 24,
alignItems: 'center',
marginTop: 12,
marginLeft: 12,
shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
elevation: 3,
},
deleteIconButton: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 12,
},
deleteButton: {
backgroundColor: '#f95555',
borderRadius: 16,
paddingVertical: 18,
borderRadius: 12,
paddingVertical: 12,
paddingHorizontal: 24,
alignItems: 'center',
shadowColor: '#f95555',
shadowOffset: { width: 0, height: 2 },
@@ -439,12 +454,12 @@ const styles = StyleSheet.create({
},
saveButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '700',
fontSize: 14,
fontWeight: '600',
},
deleteButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '700',
fontSize: 14,
fontWeight: '600',
},
});