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,