336 lines
11 KiB
TypeScript
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']
|
|
},
|
|
}); |