expo-popcore-old/components/template-run/run-progress.tsx

275 lines
6.8 KiB
TypeScript

import { View, StyleSheet, Animated, TouchableOpacity, type ColorValue } from 'react-native';
import { ThemedView } from '@/components/themed-view';
import { ThemedText } from '@/components/themed-text';
import { LinearGradient } from 'expo-linear-gradient';
import { RunProgress } from '@/lib/types/template-run';
import { useEffect, useRef } from 'react';
interface RunProgressViewProps {
progress: RunProgress;
onCancel?: () => void;
}
export function RunProgressView({ progress, onCancel }: RunProgressViewProps) {
const animatedWidth = useRef(new Animated.Value(0)).current;
const pulseAnimation = useRef(new Animated.Value(1)).current;
// 进度条动画
useEffect(() => {
Animated.timing(animatedWidth, {
toValue: progress.progress,
duration: 500,
useNativeDriver: false,
}).start();
}, [progress.progress, animatedWidth]);
// 脉冲动画
useEffect(() => {
if (progress.status === 'running') {
const pulseLoop = Animated.loop(
Animated.sequence([
Animated.timing(pulseAnimation, {
toValue: 1.1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(pulseAnimation, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
])
);
pulseLoop.start();
return () => pulseLoop.stop();
} else {
pulseAnimation.setValue(1);
}
}, [progress.status, pulseAnimation]);
const getStatusColor = (): readonly [ColorValue, ColorValue] => {
switch (progress.status) {
case 'pending':
return ['#FFA500', '#FF8C00'] as const; // 橙色
case 'running':
return ['#4ECDC4', '#44A3A0'] as const; // 青色
case 'completed':
return ['#52B788', '#40916C'] as const; // 绿色
case 'failed':
return ['#FF6B6B', '#FF5252'] as const; // 红色
default:
return ['#999999', '#666666'] as const;
}
};
const getStatusText = () => {
switch (progress.status) {
case 'pending':
return '准备中...';
case 'running':
return '执行中...';
case 'completed':
return '已完成';
case 'failed':
return '执行失败';
default:
return '未知状态';
}
};
const getStatusIcon = () => {
switch (progress.status) {
case 'pending':
return '⏳';
case 'running':
return '🔄';
case 'completed':
return '✅';
case 'failed':
return '❌';
default:
return '❓';
}
};
const colors = getStatusColor();
return (
<ThemedView style={styles.container}>
{/* 状态头部 */}
<View style={styles.header}>
<Animated.View
style={[
styles.statusIcon,
{
transform: [{ scale: pulseAnimation }],
},
]}
>
<ThemedText style={styles.iconText}>
{getStatusIcon()}
</ThemedText>
</Animated.View>
<View style={styles.statusInfo}>
<ThemedText style={styles.statusText}>
{getStatusText()}
</ThemedText>
<ThemedText style={styles.messageText}>
{progress.message}
</ThemedText>
</View>
</View>
{/* 进度条 */}
<View style={styles.progressContainer}>
<View style={styles.progressBackground}>
<Animated.View
style={[
styles.progressFill,
{
width: animatedWidth.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%'],
extrapolate: 'clamp',
}),
},
]}
>
<LinearGradient
colors={colors}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.progressGradient}
/>
</Animated.View>
</View>
<ThemedText style={styles.progressText}>
{Math.round(progress.progress)}%
</ThemedText>
</View>
{/* 详细信息 */}
<View style={styles.details}>
<ThemedText style={styles.detailText}>
: <ThemedText style={styles.detailValue}>{getStatusText()}</ThemedText>
</ThemedText>
{progress.result?.creditsCost && (
<ThemedText style={styles.detailText}>
: <ThemedText style={styles.detailValue}>{progress.result.creditsCost}</ThemedText>
</ThemedText>
)}
{progress.result?.type && (
<ThemedText style={styles.detailText}>
: <ThemedText style={styles.detailValue}>
{progress.result.type === 'IMAGE' ? '图片' :
progress.result.type === 'VIDEO' ? '视频' :
progress.result.type === 'TEXT' ? '文本' : '未知'}
</ThemedText>
</ThemedText>
)}
</View>
{/* 操作按钮 */}
{onCancel && (progress.status === 'pending' || progress.status === 'running') && (
<View style={styles.actions}>
<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<ThemedText style={styles.cancelButtonText}></ThemedText>
</TouchableOpacity>
</View>
)}
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
header: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 24,
},
statusIcon: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: 'rgba(78, 205, 196, 0.1)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 16,
},
iconText: {
fontSize: 24,
},
statusInfo: {
flex: 1,
},
statusText: {
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
},
messageText: {
fontSize: 14,
opacity: 0.7,
},
progressContainer: {
marginBottom: 24,
},
progressBackground: {
height: 8,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
borderRadius: 4,
overflow: 'hidden',
marginBottom: 8,
},
progressFill: {
height: '100%',
borderRadius: 4,
overflow: 'hidden',
},
progressGradient: {
flex: 1,
},
progressText: {
fontSize: 14,
fontWeight: '600',
textAlign: 'center',
},
details: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
borderRadius: 12,
padding: 16,
marginBottom: 20,
},
detailText: {
fontSize: 14,
marginBottom: 8,
},
detailValue: {
fontWeight: '600',
},
actions: {
alignItems: 'center',
},
cancelButton: {
backgroundColor: 'rgba(255, 59, 48, 0.1)',
borderRadius: 8,
paddingVertical: 12,
paddingHorizontal: 24,
borderWidth: 1,
borderColor: 'rgba(255, 59, 48, 0.3)',
},
cancelButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#FF3B30',
},
});