feat: 更新心情编辑页面,优化心情描述输入框,增加日记标题和副标题,调整样式和布局,提升用户体验;修改MoodIntensitySlider组件,优化滑块样式和交互效果

This commit is contained in:
2025-09-05 22:56:00 +08:00
parent c37c3a16b1
commit 8d6a848918
2 changed files with 170 additions and 134 deletions

View File

@@ -199,28 +199,36 @@ export default function MoodEditScreen() {
onValueChange={handleIntensityChange} onValueChange={handleIntensityChange}
min={1} min={1}
max={10} max={10}
width={320} width={280}
height={12}
/> />
</View> </View>
{/* 心情描述 */} {/* 心情描述 */}
{selectedMood && (
<View style={styles.descriptionSection}> <View style={styles.descriptionSection}>
<Text style={styles.sectionTitle}></Text> <Text style={styles.sectionTitle}></Text>
<TextInput <Text style={styles.diarySubtitle}></Text>
style={styles.descriptionInput} <TextInput
placeholder="描述一下你的心情..." style={styles.descriptionInput}
placeholderTextColor="#777f8c" placeholder={`今天的心情如何?
value={description}
onChangeText={setDescription} 你经历过什么特别的事情吗?
multiline 有什么让你开心的事?
maxLength={200} 或者,有什么让你感到困扰?
textAlignVertical="top"
/> 写下你的感受,让这些时刻成为你珍贵的记忆...`}
<Text style={styles.characterCount}>{description.length}/200</Text> placeholderTextColor="#a8a8a8"
</View> value={description}
)} onChangeText={setDescription}
multiline
maxLength={1000}
textAlignVertical="top"
/>
<Text style={styles.characterCount}>{description.length}/1000</Text>
</View>
</ScrollView> </ScrollView>
{/* 底部按钮 */} {/* 底部按钮 */}
@@ -242,7 +250,7 @@ export default function MoodEditScreen() {
onPress={handleDelete} onPress={handleDelete}
disabled={isDeleting} disabled={isDeleting}
> >
<Ionicons name="trash-outline" size={24} color="#f95555" /> <Ionicons name="trash-outline" size={20} color="#f95555" />
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
@@ -265,21 +273,21 @@ const styles = StyleSheet.create({
}, },
decorativeCircle1: { decorativeCircle1: {
position: 'absolute', position: 'absolute',
top: 40, top: 30,
right: 20, right: 15,
width: 60, width: 45,
height: 60, height: 45,
borderRadius: 30, borderRadius: 22.5,
backgroundColor: '#7a5af8', backgroundColor: '#7a5af8',
opacity: 0.08, opacity: 0.06,
}, },
decorativeCircle2: { decorativeCircle2: {
position: 'absolute', position: 'absolute',
bottom: -15, bottom: -10,
left: -15, left: -10,
width: 40, width: 30,
height: 40, height: 30,
borderRadius: 20, borderRadius: 15,
backgroundColor: '#7a5af8', backgroundColor: '#7a5af8',
opacity: 0.04, opacity: 0.04,
}, },
@@ -292,38 +300,38 @@ const styles = StyleSheet.create({
}, },
dateSection: { dateSection: {
backgroundColor: 'rgba(255,255,255,0.95)', backgroundColor: 'rgba(255,255,255,0.95)',
margin: 16, margin: 12,
borderRadius: 20, borderRadius: 16,
padding: 20, padding: 16,
alignItems: 'center', alignItems: 'center',
shadowColor: '#7a5af8', shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.1, shadowOpacity: 0.08,
shadowRadius: 12, shadowRadius: 8,
elevation: 6, elevation: 4,
}, },
dateTitle: { dateTitle: {
fontSize: 26, fontSize: 20,
fontWeight: '800', fontWeight: '700',
color: '#192126', color: '#192126',
}, },
moodSection: { moodSection: {
backgroundColor: 'rgba(255,255,255,0.95)', backgroundColor: 'rgba(255,255,255,0.95)',
margin: 16, margin: 12,
marginTop: 0, marginTop: 0,
borderRadius: 20, borderRadius: 16,
padding: 20, padding: 16,
shadowColor: '#7a5af8', shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.1, shadowOpacity: 0.08,
shadowRadius: 12, shadowRadius: 8,
elevation: 6, elevation: 4,
}, },
sectionTitle: { sectionTitle: {
fontSize: 20, fontSize: 16,
fontWeight: '700', fontWeight: '600',
color: '#192126', color: '#192126',
marginBottom: 20, marginBottom: 16,
}, },
moodOptions: { moodOptions: {
flexDirection: 'row', flexDirection: 'row',
@@ -333,9 +341,9 @@ const styles = StyleSheet.create({
moodOption: { moodOption: {
width: '18%', width: '18%',
alignItems: 'center', alignItems: 'center',
paddingVertical: 16, paddingVertical: 12,
marginBottom: 12, marginBottom: 8,
borderRadius: 16, borderRadius: 12,
backgroundColor: 'rgba(122,90,248,0.05)', backgroundColor: 'rgba(122,90,248,0.05)',
borderWidth: 1, borderWidth: 1,
borderColor: 'rgba(122,90,248,0.1)', borderColor: 'rgba(122,90,248,0.1)',
@@ -346,55 +354,63 @@ const styles = StyleSheet.create({
borderColor: '#7a5af8', borderColor: '#7a5af8',
shadowColor: '#7a5af8', shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15, shadowOpacity: 0.12,
shadowRadius: 4, shadowRadius: 3,
elevation: 2, elevation: 2,
}, },
moodImage: { moodImage: {
width: 40, width: 32,
height: 40, height: 32,
marginBottom: 10, marginBottom: 6,
}, },
moodLabel: { moodLabel: {
fontSize: 14, fontSize: 12,
color: '#192126', color: '#192126',
fontWeight: '600', fontWeight: '500',
}, },
intensitySection: { intensitySection: {
backgroundColor: 'rgba(255,255,255,0.95)', backgroundColor: 'rgba(255,255,255,0.95)',
margin: 16, margin: 12,
marginTop: 0, marginTop: 0,
borderRadius: 20, borderRadius: 16,
padding: 20, padding: 16,
shadowColor: '#7a5af8', shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.1, shadowOpacity: 0.08,
shadowRadius: 12, shadowRadius: 8,
elevation: 6, elevation: 4,
}, },
descriptionSection: { descriptionSection: {
backgroundColor: 'rgba(255,255,255,0.95)', backgroundColor: 'rgba(255,255,255,0.95)',
margin: 16, margin: 12,
marginTop: 0, marginTop: 0,
borderRadius: 20, borderRadius: 16,
padding: 20, padding: 16,
shadowColor: '#7a5af8', shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.1, shadowOpacity: 0.08,
shadowRadius: 12, shadowRadius: 8,
elevation: 6, elevation: 4,
},
diarySubtitle: {
fontSize: 13,
color: '#666',
fontWeight: '500',
marginBottom: 12,
lineHeight: 18,
}, },
descriptionInput: { descriptionInput: {
borderWidth: 1.5, borderWidth: 1.5,
borderColor: 'rgba(122,90,248,0.2)', borderColor: 'rgba(122,90,248,0.2)',
borderRadius: 12, borderRadius: 10,
padding: 16, padding: 12,
fontSize: 16, fontSize: 14,
minHeight: 100, minHeight: 120,
textAlignVertical: 'top', textAlignVertical: 'top',
backgroundColor: 'rgba(122,90,248,0.02)', backgroundColor: 'rgba(122,90,248,0.02)',
color: '#192126', color: '#192126',
lineHeight: 20,
}, },
characterCount: { characterCount: {
fontSize: 12, fontSize: 12,
@@ -404,9 +420,9 @@ const styles = StyleSheet.create({
fontWeight: '500', fontWeight: '500',
}, },
footer: { footer: {
padding: 16, padding: 12,
position: 'absolute', position: 'absolute',
bottom: 24, bottom: 20,
right: 8, right: 8,
}, },
buttonRow: { buttonRow: {
@@ -416,24 +432,24 @@ const styles = StyleSheet.create({
}, },
saveButton: { saveButton: {
backgroundColor: '#7a5af8', backgroundColor: '#7a5af8',
borderRadius: 12, borderRadius: 10,
paddingVertical: 12, paddingVertical: 10,
paddingHorizontal: 24, paddingHorizontal: 20,
alignItems: 'center', alignItems: 'center',
marginLeft: 12, marginLeft: 8,
shadowColor: '#7a5af8', shadowColor: '#7a5af8',
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2, shadowOpacity: 0.15,
shadowRadius: 4, shadowRadius: 3,
elevation: 3, elevation: 2,
}, },
deleteIconButton: { deleteIconButton: {
width: 36, width: 32,
height: 36, height: 32,
borderRadius: 18, borderRadius: 16,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
marginLeft: 12, marginLeft: 8,
}, },
deleteButton: { deleteButton: {
backgroundColor: '#f95555', backgroundColor: '#f95555',
@@ -454,7 +470,7 @@ const styles = StyleSheet.create({
}, },
saveButtonText: { saveButtonText: {
color: '#fff', color: '#fff',
fontSize: 14, fontSize: 13,
fontWeight: '600', fontWeight: '600',
}, },
deleteButtonText: { deleteButtonText: {

View File

@@ -39,10 +39,11 @@ export default function MoodIntensitySlider({
width = 320, width = 320,
height = 16, // 更粗的进度条 height = 16, // 更粗的进度条
}: MoodIntensitySliderProps) { }: MoodIntensitySliderProps) {
const thumbSize = 32; // 合适的触摸区域
const translateX = useSharedValue(0); const translateX = useSharedValue(0);
const sliderWidth = width - 40; // 减去thumb的宽度 const isDragging = useSharedValue(0);
const thumbSize = 36; // 更大的thumb const sliderWidth = width - thumbSize; // thumb中心移动的有效范围
// 计算初始位置 // 计算初始位置
React.useEffect(() => { React.useEffect(() => {
const initialPosition = ((value - min) / (max - min)) * sliderWidth; const initialPosition = ((value - min) / (max - min)) * sliderWidth;
@@ -60,16 +61,17 @@ export default function MoodIntensitySlider({
onStart: (_, context) => { onStart: (_, context) => {
context.startX = translateX.value; context.startX = translateX.value;
context.lastValue = value; context.lastValue = value;
isDragging.value = withSpring(1);
runOnJS(triggerHaptics)(); runOnJS(triggerHaptics)();
}, },
onActive: (event, context) => { onActive: (event, context) => {
const newX = context.startX + event.translationX; const newX = context.startX + event.translationX;
const clampedX = Math.max(0, Math.min(sliderWidth, newX)); const clampedX = Math.max(0, Math.min(sliderWidth, newX));
translateX.value = clampedX; translateX.value = clampedX;
// 计算当前值 // 计算当前值
const currentValue = Math.round((clampedX / sliderWidth) * (max - min) + min); const currentValue = Math.round((clampedX / sliderWidth) * (max - min) + min);
// 当值改变时触发震动和回调 // 当值改变时触发震动和回调
if (currentValue !== context.lastValue) { if (currentValue !== context.lastValue) {
context.lastValue = currentValue; context.lastValue = currentValue;
@@ -81,28 +83,52 @@ export default function MoodIntensitySlider({
// 计算最终值并吸附到最近的步长 // 计算最终值并吸附到最近的步长
const currentValue = Math.round((translateX.value / sliderWidth) * (max - min) + min); const currentValue = Math.round((translateX.value / sliderWidth) * (max - min) + min);
const snapPosition = ((currentValue - min) / (max - min)) * sliderWidth; const snapPosition = ((currentValue - min) / (max - min)) * sliderWidth;
translateX.value = withSpring(snapPosition); translateX.value = withSpring(snapPosition);
isDragging.value = withSpring(0);
runOnJS(triggerHaptics)(); runOnJS(triggerHaptics)();
runOnJS(onValueChange)(currentValue); runOnJS(onValueChange)(currentValue);
}, },
}); });
const thumbStyle = useAnimatedStyle(() => { const thumbStyle = useAnimatedStyle(() => {
const scale = interpolate( const positionScale = interpolate(
translateX.value, translateX.value,
[0, sliderWidth], [0, sliderWidth],
[1, 1.05] [1, 1.1]
); );
const dragScale = interpolate(
isDragging.value,
[0, 1],
[1, 1.2]
);
const finalScale = positionScale * dragScale;
// 让thumb在滑动条中正确居中
const thumbPosition = translateX.value + thumbSize / 2;
return { return {
transform: [ transform: [
{ translateX: translateX.value }, { translateX: thumbPosition },
{ scale: withSpring(scale) } { scale: withSpring(finalScale) }
], ],
}; };
}); });
const thumbInnerStyle = useAnimatedStyle(() => {
const borderColor = interpolateColor(
isDragging.value,
[0, 1],
['#7a5af8', '#ff6b6b']
);
return {
borderColor: borderColor,
};
});
const progressStyle = useAnimatedStyle(() => { const progressStyle = useAnimatedStyle(() => {
const progressWidth = translateX.value + thumbSize / 2; const progressWidth = translateX.value + thumbSize / 2;
return { return {
@@ -146,7 +172,7 @@ export default function MoodIntensitySlider({
end={{ x: 1, y: 0 }} end={{ x: 1, y: 0 }}
/> />
</View> </View>
{/* 进度条 - 动态颜色 */} {/* 进度条 - 动态颜色 */}
<Animated.View style={[styles.progress, { height }, progressStyle, progressColorsStyle]}> <Animated.View style={[styles.progress, { height }, progressStyle, progressColorsStyle]}>
<LinearGradient <LinearGradient
@@ -156,7 +182,7 @@ export default function MoodIntensitySlider({
end={{ x: 0, y: 0 }} end={{ x: 0, y: 0 }}
/> />
</Animated.View> </Animated.View>
{/* 可拖拽的thumb */} {/* 可拖拽的thumb */}
<PanGestureHandler onGestureEvent={gestureHandler}> <PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.thumb, { width: thumbSize, height: thumbSize }, thumbStyle]}> <Animated.View style={[styles.thumb, { width: thumbSize, height: thumbSize }, thumbStyle]}>
@@ -166,17 +192,17 @@ export default function MoodIntensitySlider({
start={{ x: 0, y: 0 }} start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }} end={{ x: 0, y: 1 }}
/> */} /> */}
<View style={styles.thumbInner} /> <Animated.View style={[styles.thumbInner, thumbInnerStyle]} />
</Animated.View> </Animated.View>
</PanGestureHandler> </PanGestureHandler>
</View> </View>
{/* 标签 */} {/* 标签 */}
<View style={[styles.labelsContainer, { width: width }]}> <View style={[styles.labelsContainer, { width: width }]}>
<Text style={styles.labelText}></Text> <Text style={styles.labelText}></Text>
<Text style={styles.labelText}></Text> <Text style={styles.labelText}></Text>
</View> </View>
{/* 刻度 */} {/* 刻度 */}
<View style={[styles.scaleContainer, { width: width }]}> <View style={[styles.scaleContainer, { width: width }]}>
{Array.from({ length: max - min + 1 }, (_, i) => i + min).map((num) => ( {Array.from({ length: max - min + 1 }, (_, i) => i + min).map((num) => (
@@ -192,39 +218,38 @@ export default function MoodIntensitySlider({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
alignItems: 'center', alignItems: 'center',
paddingVertical: 16, paddingTop: 8,
paddingBottom: 4,
}, },
sliderContainer: { sliderContainer: {
height: 50, height: 10,
justifyContent: 'center', justifyContent: 'center',
position: 'relative', position: 'relative',
paddingHorizontal: 20,
}, },
track: { track: {
position: 'absolute', position: 'absolute',
left: 20, left: 0,
right: 20, right: 0,
borderRadius: 8, borderRadius: 6,
overflow: 'hidden', overflow: 'hidden',
}, },
trackGradient: { trackGradient: {
flex: 1, flex: 1,
borderRadius: 8, borderRadius: 6,
}, },
progress: { progress: {
position: 'absolute', position: 'absolute',
left: 20, left: 0,
borderRadius: 8, borderRadius: 6,
overflow: 'hidden', overflow: 'hidden',
}, },
progressGradient: { progressGradient: {
flex: 1, flex: 1,
borderRadius: 8, borderRadius: 6,
}, },
thumb: { thumb: {
position: 'absolute', position: 'absolute',
left: 22, left: 0,
elevation: 6,
overflow: 'hidden', overflow: 'hidden',
}, },
thumbGradient: { thumbGradient: {
@@ -234,14 +259,11 @@ const styles = StyleSheet.create({
}, },
thumbInner: { thumbInner: {
width: 16, width: 16,
height: 32, height: 28,
borderRadius: 8, borderRadius: 8,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
shadowColor: '#ffffff', borderWidth: 2,
shadowOffset: { width: 0, height: 1 }, borderColor: '#7a5af8',
shadowOpacity: 0.3,
shadowRadius: 2,
elevation: 2,
}, },
valueContainer: { valueContainer: {
marginTop: 20, marginTop: 20,
@@ -266,33 +288,31 @@ const styles = StyleSheet.create({
labelsContainer: { labelsContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
marginTop: 8, marginTop: 6,
paddingHorizontal: 20,
}, },
labelText: { labelText: {
fontSize: 14, fontSize: 12,
color: '#5d6676', color: '#5d6676',
fontWeight: '600', fontWeight: '500',
}, },
scaleContainer: { scaleContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
marginTop: 16, marginTop: 12,
paddingHorizontal: 20,
}, },
scaleItem: { scaleItem: {
alignItems: 'center', alignItems: 'center',
flex: 1, flex: 1,
}, },
scaleMark: { scaleMark: {
width: 2, width: 1.5,
height: 10, height: 8,
backgroundColor: '#e5e7eb', backgroundColor: '#e5e7eb',
borderRadius: 1, borderRadius: 0.75,
}, },
scaleMarkActive: { scaleMarkActive: {
backgroundColor: '#7a5af8', backgroundColor: '#7a5af8',
width: 3, width: 2,
height: 12, height: 10,
}, },
}); });