diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index 035bbfa..4de2030 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -166,7 +166,7 @@ export default function AskScreen() { > - MemoWake + { router.push('/owner') }}>MemoWake diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 44dd58d..975b366 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,17 +1,182 @@ import IP from '@/assets/icons/svg/ip.svg'; import { checkAuthStatus } from '@/lib/auth'; import { useRouter } from 'expo-router'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Text, TouchableOpacity, View } from 'react-native'; +import { Animated, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function HomeScreen() { const router = useRouter(); const { t } = useTranslation(); const insets = useSafeAreaInsets(); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(false); + // 动画值 + const fadeAnim = useRef(new Animated.Value(0)).current; // IP图标的淡入动画 + const shakeAnim = useRef(new Animated.Value(0)).current; // IP图标的摇晃动画 + const animationRef = useRef(null); // 动画引用 + const descriptionAnim = useRef(new Animated.Value(0)).current; // 描述文本的淡入动画 + const buttonAnim = useRef(new Animated.Value(0)).current; // 按钮的淡入动画 + const buttonShakeAnim = useRef(new Animated.Value(0)).current; // 按钮的摇晃动画 + const buttonLoopAnim = useRef(null); // 按钮循环动画引用 + + // 文本行动画值 + const [textAnimations] = useState(() => ({ + line1: new Animated.Value(0), // 第一行文本动画 + line2: new Animated.Value(0), // 第二行文本动画 + line3: new Animated.Value(0), // 第三行文本动画 + subtitle: new Animated.Value(0), // 副标题动画 + })); + + // 启动IP图标摇晃动画 + const startShaking = () => { + // 停止任何正在进行的动画 + if (animationRef.current) { + animationRef.current.stop(); + } + + // 创建动画序列 + const sequence = Animated.sequence([ + // 第一次左右摇晃 + Animated.timing(shakeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(shakeAnim, { + toValue: -1, + duration: 300, + useNativeDriver: true, + }), + // 第二次左右摇晃 + Animated.timing(shakeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(shakeAnim, { + toValue: -1, + duration: 300, + useNativeDriver: true, + }), + // 回到中心位置 + Animated.timing(shakeAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + // 1秒延迟 + Animated.delay(1000), + ]); + + // 循环播放动画序列 + animationRef.current = Animated.loop(sequence); + animationRef.current.start(); + }; + + // 启动文本动画 + const startTextAnimations = () => { + // 按顺序延迟启动每行文本动画 + return new Promise((resolve) => { + Animated.stagger(300, [ + Animated.timing(textAnimations.line1, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }), + Animated.timing(textAnimations.line2, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }), + Animated.timing(textAnimations.line3, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }), + Animated.timing(textAnimations.subtitle, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }), + ]).start(() => resolve()); + }); + }; + + // 启动描述文本动画 + const startDescriptionAnimation = () => { + // IP图标显示后淡入描述文本 + return new Promise((resolve) => { + Animated.sequence([ + Animated.delay(200), // IP图标显示后延迟200ms + Animated.timing(descriptionAnim, { + toValue: 1, + duration: 800, + useNativeDriver: true, + }) + ]).start(() => resolve()); + }); + }; + + // 启动按钮动画 + const startButtonAnimation = () => { + // 首先淡入按钮 + Animated.sequence([ + Animated.timing(buttonAnim, { + toValue: 1, + duration: 800, + useNativeDriver: true, + }) + ]).start(() => { + // 淡入完成后开始循环摇晃动画 + startButtonShakeLoop(); + }); + }; + + // 启动按钮循环摇晃动画 + const startButtonShakeLoop = () => { + // 停止任何正在进行的动画 + if (buttonLoopAnim.current) { + buttonLoopAnim.current.stop(); + } + + // 创建摇晃动画序列 + const shakeSequence = Animated.sequence([ + // 向右摇晃 + Animated.timing(buttonShakeAnim, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }), + // 向左摇晃 + Animated.timing(buttonShakeAnim, { + toValue: -1, + duration: 100, + useNativeDriver: true, + }), + // 再次向右摇晃 + Animated.timing(buttonShakeAnim, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }), + // 回到中心位置 + Animated.timing(buttonShakeAnim, { + toValue: 0, + duration: 100, + useNativeDriver: true, + }), + // 暂停3秒 + Animated.delay(3000) + ]); + + // 循环播放动画序列 + buttonLoopAnim.current = Animated.loop(shakeSequence); + buttonLoopAnim.current.start(); + }; + + // 组件挂载时启动动画 useEffect(() => { setIsLoading(true); checkAuthStatus(router, () => { @@ -21,58 +186,266 @@ export default function HomeScreen() { }).catch(() => { setIsLoading(false); }); + // IP图标的淡入动画 + Animated.timing(fadeAnim, { + toValue: 1, + duration: 1000, + useNativeDriver: true, + }).start(() => { + // 淡入完成后开始摇晃动画 + startShaking(); + // IP显示后开始文本动画 + startTextAnimations() + .then(() => startDescriptionAnimation()) + .then(() => startButtonAnimation()) + .catch(console.error); + }); + + // 组件卸载时清理动画 + return () => { + if (buttonLoopAnim.current) { + buttonLoopAnim.current.stop(); + } + if (animationRef.current) { + animationRef.current.stop(); + } + }; }, []); + // 动画样式 + const animatedStyle = { + opacity: fadeAnim, + transform: [ + { + translateX: shakeAnim.interpolate({ + inputRange: [-1, 1], + outputRange: [-2, 2], + }) + }, + { + rotate: shakeAnim.interpolate({ + inputRange: [-1, 1], + outputRange: ['-2deg', '2deg'], + }), + }, + ], + }; + if (isLoading) { return ( - - 加载中... + + {t('common.loading')} ); } return ( - - + + {/* 标题区域 */} - - + + {t('auth.welcomeAwaken.awaken', { ns: 'login' })} - {"\n"} + + {t('auth.welcomeAwaken.your', { ns: 'login' })} - {"\n"} + + {t('auth.welcomeAwaken.pm', { ns: 'login' })} - - + + {t('auth.welcomeAwaken.slogan', { ns: 'login' })} - + - {/* Memo 形象区域 */} - - + {/* Animated IP */} + + + + {/* 介绍文本 */} - + {t('auth.welcomeAwaken.gallery', { ns: 'login' })} {"\n"} {t('auth.welcomeAwaken.back', { ns: 'login' })} - + {/* 唤醒按钮 */} - { - router.push('/login') + - - {t('auth.welcomeAwaken.awake', { ns: 'login' })} - - + { + router.push('/login'); + }} + activeOpacity={0.8} + > + + {t('auth.welcomeAwaken.awake', { ns: 'login' })} + + + - + ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#FFB645', + }, + 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: 30, + fontWeight: 'bold', + marginBottom: 12, + textAlign: 'left', + lineHeight: 36, + }, + subtitleText: { + color: 'rgba(255, 255, 255, 0.85)', + fontSize: 16, + textAlign: 'left', + lineHeight: 24, + }, + 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, + }, + awakenButton: { + backgroundColor: '#FFFFFF', + borderRadius: 28, + paddingVertical: 16, + paddingHorizontal: 40, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + width: '100%', + alignItems: 'center', + marginTop: 24, + }, + buttonText: { + color: '#4C320C', + fontWeight: 'bold', + fontSize: 18, + }, +}); \ No newline at end of file diff --git a/components/owner/carousel.tsx b/components/owner/carousel.tsx index b59955b..c1168ef 100644 --- a/components/owner/carousel.tsx +++ b/components/owner/carousel.tsx @@ -74,14 +74,14 @@ function CarouselComponent(props: Props) { height={width * 0.75} data={carouselDataValue || []} mode="parallax" - defaultIndex={ - carouselDataValue?.length - ? Math.max(0, Math.min( - carouselDataValue.length - 1, - carouselDataValue.findIndex((item) => item?.key === 'total_count') - 1 - )) - : 0 - } + // defaultIndex={ + // carouselDataValue?.length + // ? Math.max(0, Math.min( + // carouselDataValue.length - 1, + // carouselDataValue.findIndex((item) => item?.key === 'total_count') - 1 + // )) + // : 0 + // } modeConfig={{ parallaxScrollingScale: 1, parallaxScrollingOffset: 150,