feat: 更新应用版本和集成腾讯云 COS 上传功能

- 将应用版本更新至 1.0.2,修改相关配置文件
- 集成腾讯云 COS 上传功能,新增相关服务和钩子
- 更新 AI 体态评估页面,支持照片上传和评估结果展示
- 添加雷达图组件以展示评估结果
- 更新样式以适应新功能的展示和交互
- 修改登录页面背景效果,提升用户体验
This commit is contained in:
richarjiang
2025-08-13 15:21:54 +08:00
parent 5814044cee
commit 321947db98
20 changed files with 1664 additions and 342 deletions

View File

@@ -0,0 +1,279 @@
import { Ionicons } from '@expo/vector-icons';
import { BlurView } from 'expo-blur';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
import React, { useEffect } from 'react';
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
export default function AIPostureProcessingScreen() {
const insets = useSafeAreaInsets();
const router = useRouter();
const theme = Colors.dark;
// Core looping animations
const spin = useSharedValue(0);
const pulse = useSharedValue(0);
const scanY = useSharedValue(0);
const particle = useSharedValue(0);
useEffect(() => {
spin.value = withRepeat(withTiming(1, { duration: 6000, easing: Easing.linear }), -1);
pulse.value = withRepeat(withSequence(
withTiming(1, { duration: 1600, easing: Easing.inOut(Easing.quad) }),
withTiming(0, { duration: 1600, easing: Easing.inOut(Easing.quad) })
), -1, true);
scanY.value = withRepeat(withTiming(1, { duration: 3800, easing: Easing.inOut(Easing.cubic) }), -1, false);
particle.value = withDelay(400, withRepeat(withTiming(1, { duration: 5200, easing: Easing.inOut(Easing.quad) }), -1, true));
}, []);
const ringStyleOuter = useAnimatedStyle(() => ({
transform: [{ rotate: `${spin.value * 360}deg` }],
opacity: 0.8,
}));
const ringStyleInner = useAnimatedStyle(() => ({
transform: [{ rotate: `${-spin.value * 360}deg` }, { scale: 0.98 + pulse.value * 0.04 }],
}));
const scannerStyle = useAnimatedStyle(() => ({
transform: [{ translateY: (scanY.value * (SCREEN_HEIGHT * 0.45)) - (SCREEN_HEIGHT * 0.225) }],
opacity: 0.6 + Math.sin(scanY.value * Math.PI) * 0.2,
}));
const particleStyleA = useAnimatedStyle(() => ({
transform: [
{ translateX: Math.sin(particle.value * Math.PI * 2) * 40 },
{ translateY: Math.cos(particle.value * Math.PI * 2) * 24 },
{ rotate: `${particle.value * 360}deg` },
],
opacity: 0.5 + 0.5 * Math.abs(Math.sin(particle.value * Math.PI)),
}));
return (
<View style={[styles.screen, { backgroundColor: theme.background }]}>
<HeaderBar title="AI评估进行中" onBack={() => router.back()} tone="light" transparent />
{/* Layered background */}
<View style={[StyleSheet.absoluteFill, { zIndex: -1 }]} pointerEvents="none">
<LinearGradient
colors={["#F7FFE8", "#F0FBFF", "#FFF6E8"]}
start={{ x: 0.1, y: 0 }}
end={{ x: 0.9, y: 1 }}
style={StyleSheet.absoluteFill}
/>
<BlurView intensity={20} tint="light" style={styles.blurBlobA} />
<BlurView intensity={20} tint="light" style={styles.blurBlobB} />
</View>
{/* Hero visualization */}
<View style={styles.hero}>
<View style={styles.heroBackdrop} />
<Animated.View style={[styles.ringOuter, ringStyleOuter]} />
<Animated.View style={[styles.ringInner, ringStyleInner]} />
<View style={styles.grid}>
{Array.from({ length: 9 }).map((_, i) => (
<View key={`row-${i}`} style={styles.gridRow}>
{Array.from({ length: 9 }).map((__, j) => (
<View key={`cell-${i}-${j}`} style={styles.gridCell} />
))}
</View>
))}
<Animated.View style={[styles.scanner, scannerStyle]} />
</View>
<Animated.View style={[styles.particleA, particleStyleA]} />
<Animated.View style={[styles.particleB, particleStyleA, { right: undefined, left: SCREEN_WIDTH * 0.2, top: undefined, bottom: 60 }]} />
</View>
{/* Copy & actions */}
<View style={[styles.panel, { paddingBottom: insets.bottom + 16 }]}>
<Text style={styles.title}></Text>
<Text style={styles.subtitle}> 10-30 </Text>
<View style={styles.actions}>
<TouchableOpacity
style={[styles.primaryBtn, { backgroundColor: theme.primary }]}
activeOpacity={0.9}
// TODO: 评估完成后恢复为停留当前页面等待结果(不要跳转)
onPress={() => router.replace('/ai-posture-result')}
>
<Ionicons name="time-outline" size={16} color="#192126" />
<Text style={styles.primaryBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity style={styles.secondaryBtn} activeOpacity={0.9} onPress={() => router.replace('/(tabs)/personal')}>
<Text style={styles.secondaryBtnText}></Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
const RING_SIZE = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.62;
const INNER_RING_SIZE = RING_SIZE * 0.72;
const styles = StyleSheet.create({
screen: {
flex: 1,
},
hero: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
blurBlobA: {
position: 'absolute',
top: -80,
right: -60,
width: 240,
height: 240,
borderRadius: 120,
backgroundColor: 'rgba(187,242,70,0.20)',
},
blurBlobB: {
position: 'absolute',
bottom: 120,
left: -40,
width: 220,
height: 220,
borderRadius: 110,
backgroundColor: 'rgba(89, 198, 255, 0.16)',
},
heroBackdrop: {
position: 'absolute',
width: RING_SIZE * 1.08,
height: RING_SIZE * 1.08,
borderRadius: (RING_SIZE * 1.08) / 2,
backgroundColor: 'rgba(25,33,38,0.25)',
},
ringOuter: {
position: 'absolute',
width: RING_SIZE,
height: RING_SIZE,
borderRadius: RING_SIZE / 2,
borderWidth: 1,
borderColor: 'rgba(25,33,38,0.16)',
},
ringInner: {
position: 'absolute',
width: INNER_RING_SIZE,
height: INNER_RING_SIZE,
borderRadius: INNER_RING_SIZE / 2,
borderWidth: 2,
borderColor: 'rgba(187,242,70,0.65)',
shadowColor: '#BBF246',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.35,
shadowRadius: 24,
},
grid: {
width: RING_SIZE * 0.9,
height: RING_SIZE * 0.9,
borderRadius: RING_SIZE * 0.45,
overflow: 'hidden',
padding: 10,
backgroundColor: 'rgba(25,33,38,0.08)',
},
gridRow: {
flexDirection: 'row',
},
gridCell: {
flex: 1,
aspectRatio: 1,
margin: 2,
borderRadius: 3,
backgroundColor: 'rgba(255,255,255,0.16)',
},
scanner: {
position: 'absolute',
left: 0,
right: 0,
top: '50%',
height: 60,
marginTop: -30,
backgroundColor: 'rgba(187,242,70,0.10)',
borderWidth: 1,
borderColor: 'rgba(187,242,70,0.25)',
},
particleA: {
position: 'absolute',
right: SCREEN_WIDTH * 0.18,
top: 40,
width: 14,
height: 14,
borderRadius: 7,
backgroundColor: '#BBF246',
shadowColor: '#BBF246',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.4,
shadowRadius: 16,
},
particleB: {
position: 'absolute',
right: SCREEN_WIDTH * 0.08,
top: 120,
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(89, 198, 255, 1)',
shadowColor: 'rgba(89, 198, 255, 1)',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.4,
shadowRadius: 12,
},
panel: {
paddingHorizontal: 20,
paddingTop: 8,
},
title: {
color: '#ECEDEE',
fontSize: 18,
fontWeight: '800',
marginBottom: 8,
},
subtitle: {
color: 'rgba(255,255,255,0.75)',
fontSize: 14,
lineHeight: 20,
},
actions: {
flexDirection: 'row',
gap: 10,
marginTop: 14,
},
primaryBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 6,
height: 44,
paddingHorizontal: 16,
borderRadius: 12,
},
primaryBtnText: {
color: '#192126',
fontSize: 14,
fontWeight: '800',
},
secondaryBtn: {
flex: 1,
height: 44,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.18)',
},
secondaryBtnText: {
color: 'rgba(255,255,255,0.85)',
fontSize: 14,
fontWeight: '700',
},
});