Files
digital-pilates/app/ai-posture-processing.tsx
richarjiang d76ba48424 feat(ui): 统一应用主题色为天空蓝并优化渐变背景
将应用主色调从 '#BBF246' 更改为 '#87CEEB'(天空蓝),并更新所有相关组件和页面中的颜色引用。同时为多个页面添加统一的渐变背景,提升视觉效果和用户体验。新增压力分析模态框组件,并优化压力计组件的交互与显示逻辑。更新应用图标和启动图资源。
2025-08-20 09:38:25 +08:00

280 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: Colors.light.accentGreen,
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: Colors.light.accentGreen,
shadowColor: Colors.light.accentGreen,
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',
},
});