2025-08-08 19:05:43 +08:00

336 lines
11 KiB
TypeScript

import { Fonts } from '@/constants/Fonts';
import { checkAuthStatus } from '@/lib/auth';
import { useRouter } from 'expo-router';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Dimensions, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import Animated, {
interpolate,
useAnimatedStyle,
useSharedValue,
withDelay,
withRepeat,
withSequence,
withTiming
} from 'react-native-reanimated';
import { useSafeAreaInsets } from "react-native-safe-area-context";
// Worklet function for animations
const runShakeAnimation = (value: Animated.SharedValue<number>) => {
'worklet';
return withRepeat(
withSequence(
withTiming(1, { duration: 300 }),
withTiming(-1, { duration: 300 }),
withTiming(1, { duration: 300 }),
withTiming(-1, { duration: 300 }),
withTiming(0, { duration: 200 }),
withDelay(1000, withTiming(0, { duration: 0 }))
),
-1
);
};
const runWaveAnimation = (value: Animated.SharedValue<number>) => {
'worklet';
return withRepeat(
withSequence(
withTiming(1, { duration: 500 }),
withTiming(-1, { duration: 500 }),
withTiming(0, { duration: 500 }),
withDelay(1000, withTiming(0, { duration: 0 }))
),
-1
);
};
export default function HomeScreen() {
const router = useRouter();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const [isLoading, setIsLoading] = React.useState(false);
const screenWidth = Dimensions.get('window').width;
// Animation values
const fadeAnim = useSharedValue(0);
const shakeAnim = useSharedValue(0);
const waveAnim = useSharedValue(0);
const buttonShakeAnim = useSharedValue(0);
const fadeInAnim = useSharedValue(0);
const descriptionAnim = useSharedValue(0);
const textAnimations = {
line1: useSharedValue(0),
line2: useSharedValue(0),
line3: useSharedValue(0),
subtitle: useSharedValue(0),
};
// Animation styles
const ipAnimatedStyle = useAnimatedStyle(() => ({
opacity: fadeAnim.value,
transform: [
{ translateX: interpolate(shakeAnim.value, [-1, 1], [-2, 2]) },
{ rotate: `${interpolate(shakeAnim.value, [-1, 1], [-2, 2])}deg` },
],
}));
const waveAnimatedStyle = useAnimatedStyle(() => ({
transform: [
{ rotate: `${interpolate(waveAnim.value, [-1, 0, 1], [-15, 0, 15])}deg` },
],
}));
const buttonStyle = useAnimatedStyle(() => ({
opacity: fadeInAnim.value,
transform: [
{ translateY: interpolate(fadeInAnim.value, [0, 1], [20, 0]) },
{ translateX: interpolate(buttonShakeAnim.value, [-1, 0, 1], [-5, 0, 5]) }
]
}));
const welcomeStyle = useAnimatedStyle(() => ({
opacity: fadeInAnim.value,
transform: [{ translateY: interpolate(fadeInAnim.value, [0, 1], [20, 0]) }]
}));
const descriptionStyle = useAnimatedStyle(() => ({
opacity: descriptionAnim.value,
transform: [{ translateY: interpolate(descriptionAnim.value, [0, 1], [20, 0]) }]
}));
const textLine1Style = useAnimatedStyle(() => ({
opacity: textAnimations.line1.value,
transform: [{ translateY: interpolate(textAnimations.line1.value, [0, 1], [10, 0]) }]
}));
const textLine2Style = useAnimatedStyle(() => ({
opacity: textAnimations.line2.value,
transform: [{ translateY: interpolate(textAnimations.line2.value, [0, 1], [10, 0]) }]
}));
const textLine3Style = useAnimatedStyle(() => ({
opacity: textAnimations.line3.value,
transform: [{ translateY: interpolate(textAnimations.line3.value, [0, 1], [10, 0]) }]
}));
const subtitleStyle = useAnimatedStyle(() => ({
opacity: textAnimations.subtitle.value,
transform: [{ translateY: interpolate(textAnimations.subtitle.value, [0, 1], [10, 0]) }]
}));
// Start animations
useEffect(() => {
setIsLoading(true);
checkAuthStatus(router, () => {
router.replace('/ask');
}, false).then(() => {
setIsLoading(false);
}).catch(() => {
setIsLoading(false);
});
// Start fade in animation
fadeAnim.value = withTiming(1, { duration: 1000 }, () => {
// Start shake animation
shakeAnim.value = runShakeAnimation(shakeAnim);
// Start text animations with delays
textAnimations.line1.value = withDelay(0, withTiming(1, { duration: 500 }));
textAnimations.line2.value = withDelay(300, withTiming(1, { duration: 500 }));
textAnimations.line3.value = withDelay(600, withTiming(1, { duration: 500 }));
textAnimations.subtitle.value = withDelay(900, withTiming(1, { duration: 500 }));
// Start welcome animation
fadeInAnim.value = withDelay(200, withTiming(1, { duration: 800 }));
// Start description animation
descriptionAnim.value = withDelay(200, withTiming(1, { duration: 800 }));
// Start button animation
fadeInAnim.value = withDelay(200, withTiming(1, { duration: 800 }, () => {
// Start button shake animation
buttonShakeAnim.value = withRepeat(
withSequence(
withTiming(1, { duration: 100 }),
withTiming(-1, { duration: 100 }),
withTiming(1, { duration: 100 }),
withTiming(0, { duration: 100 }),
withDelay(3000, withTiming(0, { duration: 0 }))
),
-1
);
}));
// Start wave animation
waveAnim.value = runWaveAnimation(waveAnim);
});
// Cleanup
return () => {
fadeAnim.value = 0;
shakeAnim.value = 0;
waveAnim.value = 0;
buttonShakeAnim.value = 0;
fadeInAnim.value = 0;
descriptionAnim.value = 0;
Object.values(textAnimations).forEach(anim => anim.value = 0);
};
}, []);
if (isLoading) {
return (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>{t('common.loading')}</Text>
</View>
);
}
return (
<View style={styles.container}>
<View style={[styles.contentContainer, { paddingTop: insets.top + 16 }]}>
<View style={styles.headerContainer}>
<Animated.Text style={[styles.titleText, textLine1Style]}>
{t('auth.welcomeAwaken.awaken', { ns: 'login' })}
</Animated.Text>
<Animated.Text style={[styles.titleText, textLine2Style]}>
{t('auth.welcomeAwaken.your', { ns: 'login' })}
</Animated.Text>
<Animated.Text style={[styles.titleText, textLine3Style]}>
{t('auth.welcomeAwaken.pm', { ns: 'login' })}
</Animated.Text>
<Animated.Text style={[styles.subtitleText, subtitleStyle]}>
{t('auth.welcomeAwaken.slogan', { ns: 'login' })}
</Animated.Text>
</View>
<View style={{ alignItems: 'flex-end' }}>
<Animated.View style={[{
height: screenWidth * 0.3,
width: screenWidth * 0.3,
marginTop: -screenWidth * 0.08,
}, welcomeStyle]}>
<Image
source={require('@/assets/images/png/icon/think.png')}
style={{
width: '100%',
height: '100%',
resizeMode: 'contain'
}}
/>
</Animated.View>
</View>
<View style={styles.ipContainer}>
<Animated.View style={[styles.ipWrapper, waveAnimatedStyle]}>
<Image
source={require('@/assets/images/png/icon/ip.png')}
style={{ width: screenWidth * 0.9, marginBottom: -screenWidth * 0.18, marginTop: -screenWidth * 0.22 }}
/>
</Animated.View>
</View>
<Animated.Text style={[styles.descriptionText, descriptionStyle]}>
{t('auth.welcomeAwaken.gallery', { ns: 'login' })}
{"\n"}
{t('auth.welcomeAwaken.back', { ns: 'login' })}
</Animated.Text>
<Animated.View style={[{ alignItems: "center" }, buttonStyle]}>
<TouchableOpacity
style={styles.awakenButton}
onPress={() => router.push('/login')}
activeOpacity={0.8}
>
<Text style={styles.buttonText}>
{t('auth.welcomeAwaken.awake', { ns: 'login' })}
</Text>
</TouchableOpacity>
</Animated.View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFB645',
fontFamily: 'english'
},
loadingContainer: {
flex: 1,
backgroundColor: '#FFB645',
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
color: '#FFFFFF',
fontSize: 16,
},
contentContainer: {
flex: 1,
backgroundColor: '#FFB645',
paddingHorizontal: 16,
paddingBottom: 32,
},
headerContainer: {
marginBottom: 40,
width: '100%',
paddingHorizontal: 20,
},
titleText: {
color: '#FFFFFF',
fontSize: 32,
fontWeight: 'bold',
marginBottom: 12,
textAlign: 'left',
lineHeight: 36,
fontFamily: Fonts['quicksand']
},
subtitleText: {
color: 'rgba(255, 255, 255, 0.85)',
fontSize: 16,
textAlign: 'left',
lineHeight: 24,
fontFamily: Fonts['inter']
},
ipContainer: {
alignItems: 'center',
marginBottom: 16,
minHeight: 200,
},
ipWrapper: {
alignItems: 'center',
justifyContent: 'center',
},
descriptionText: {
color: '#FFFFFF',
fontSize: 16,
textAlign: 'center',
lineHeight: 24,
opacity: 0.9,
paddingHorizontal: 40,
marginTop: -16,
fontFamily: Fonts['inter']
},
awakenButton: {
backgroundColor: '#FFFFFF',
borderRadius: 28,
paddingVertical: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
width: '86%',
alignItems: 'center',
marginTop: 24,
},
buttonText: {
color: '#4C320C',
fontWeight: 'bold',
fontSize: 18,
fontFamily: Fonts['quicksand']
},
});