feat: 新增动画资源与庆祝效果,优化布局与标签页配置

This commit is contained in:
richarjiang
2025-09-03 15:03:26 +08:00
parent 8b6ef378d0
commit 951c02f644
14 changed files with 373 additions and 267 deletions

View File

@@ -0,0 +1,62 @@
import LottieView from 'lottie-react-native';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import {
Dimensions,
StyleSheet,
View,
} from 'react-native';
export interface CelebrationAnimationRef {
play: () => void;
}
interface CelebrationAnimationProps {
visible?: boolean;
}
const CelebrationAnimation = forwardRef<CelebrationAnimationRef, CelebrationAnimationProps>(
({ visible = true }, ref) => {
const animationRef = useRef<LottieView>(null);
useImperativeHandle(ref, () => ({
play: () => {
animationRef.current?.play();
},
}));
if (!visible) return null;
return (
<View style={styles.container} pointerEvents="none">
<LottieView
ref={animationRef}
autoPlay={false}
loop={false}
source={require('@/assets/lottie/Confetti.json')}
style={styles.animation}
/>
</View>
);
}
);
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 9999,
alignItems: 'center',
justifyContent: 'center',
},
animation: {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
});
CelebrationAnimation.displayName = 'CelebrationAnimation';
export default CelebrationAnimation;

View File

@@ -1,6 +1,7 @@
import { MoodCheckin, getMoodConfig } from '@/services/moodCheckins';
import dayjs from 'dayjs';
import React from 'react';
import LottieView from 'lottie-react-native';
import React, { useEffect, useRef } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
interface MoodCardProps {
@@ -11,10 +12,26 @@ interface MoodCardProps {
export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardProps) {
const moodConfig = moodCheckin ? getMoodConfig(moodCheckin.moodType) : null;
const animationRef = useRef<LottieView>(null);
useEffect(() => {
if (animationRef.current) {
animationRef.current.play();
}
}, []);
return (
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}>
<Text style={styles.cardTitle}></Text>
<View style={styles.moodCardHeader}>
<Text style={styles.cardTitle}></Text>
<LottieView
ref={animationRef}
source={require('@/assets/lottie/mood/mood_demo.json')}
autoPlay
loop
style={styles.lottieAnimation}
/>
</View>
{isLoading ? (
<View style={styles.moodPreview}>
<Text style={styles.moodLoadingText}>...</Text>
@@ -40,11 +57,22 @@ const styles = StyleSheet.create({
width: '100%',
},
moodCardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
cardTitle: {
fontSize: 14,
color: '#192126',
},
lottieAnimation: {
width: 40,
height: 40,
},
moodPreview: {
flexDirection: 'row',
justifyContent: 'space-between',

View File

@@ -10,10 +10,12 @@ import { Alert, Animated, Image, StyleSheet, Text, TouchableOpacity, View } from
interface TaskCardProps {
task: TaskListItem;
onTaskCompleted?: (task: TaskListItem) => void; // 任务完成回调
}
export const TaskCard: React.FC<TaskCardProps> = ({
task,
onTaskCompleted,
}) => {
const theme = useColorScheme() ?? 'light';
const colorTokens = Colors[theme];
@@ -98,6 +100,9 @@ export const TaskCard: React.FC<TaskCardProps> = ({
notes: '通过任务卡片完成'
}
})).unwrap();
// 触发任务完成回调
onTaskCompleted?.(task);
} catch (error) {
Alert.alert('错误', '完成任务失败,请重试');

View File

@@ -2,7 +2,8 @@ import { useWaterDataByDate } from '@/hooks/useWaterData';
import { getQuickWaterAmount } from '@/utils/userPreferences';
import dayjs from 'dayjs';
import * as Haptics from 'expo-haptics';
import React, { useEffect, useMemo, useState } from 'react';
import LottieView from 'lottie-react-native';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Animated,
StyleSheet,
@@ -31,6 +32,8 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
const currentIntake = waterStats?.totalAmount || 0;
const targetIntake = dailyWaterGoal || 2000;
const animationRef = useRef<LottieView>(null);
// 为每个时间点创建独立的动画值
const animatedValues = useMemo(() =>
Array.from({ length: 24 }, () => new Animated.Value(0))
@@ -119,6 +122,8 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
}
animationRef.current?.play();
// 使用用户配置的快速添加饮水量
const waterAmount = quickWaterAmount;
// 如果有选中日期,则为该日期添加记录;否则为今天添加记录
@@ -156,6 +161,20 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
onPress={handleCardPress}
activeOpacity={0.8}
>
<LottieView
ref={animationRef}
autoPlay={false}
loop={false}
source={require('@/assets/lottie/Confetti.json')}
style={{
width: 150,
height: 150,
position: 'absolute',
left: '15%',
}}
/>
{/* 标题和加号按钮 */}
<View style={styles.header}>
<Text style={styles.title}></Text>
@@ -166,6 +185,7 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
)}
</View>
{/* 柱状图 */}
<View style={styles.chartContainer}>
<View style={styles.chartWrapper}>