Files
digital-pilates/components/medication/MedicationAddOptionsSheet.tsx
2025-12-18 16:36:53 +08:00

410 lines
10 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 { Image } from '@/components/ui/Image';
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import React, { useEffect, useRef, useState } from 'react';
import { Animated, Modal, Pressable, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
type Props = {
visible: boolean;
onClose: () => void;
onManualAdd: () => void;
onAiRecognize: () => void;
};
export function MedicationAddOptionsSheet({ visible, onClose, onManualAdd, onAiRecognize }: Props) {
const translateY = useRef(new Animated.Value(300)).current;
const opacity = useRef(new Animated.Value(0)).current;
const [modalVisible, setModalVisible] = useState(false);
useEffect(() => {
if (visible) {
// 打开时:先显示 Modal然后执行动画
setModalVisible(true);
Animated.parallel([
Animated.spring(translateY, {
toValue: 0,
tension: 65,
friction: 11,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
]).start();
} else if (modalVisible) {
// 关闭时:先执行动画,动画完成后隐藏 Modal
Animated.parallel([
Animated.timing(translateY, {
toValue: 300,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}),
]).start(({ finished }) => {
if (finished) {
setModalVisible(false);
}
});
}
}, [visible, modalVisible, opacity, translateY]);
const handleClose = () => {
// 触发关闭动画
onClose();
};
return (
<Modal visible={modalVisible} transparent animationType="none" onRequestClose={handleClose}>
<Pressable style={styles.overlay} onPress={onClose}>
<Animated.View style={[styles.backdrop, { opacity }]} />
</Pressable>
<Animated.View
style={[
styles.sheet,
{
transform: [{ translateY }],
},
]}
>
{/* Header */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.title}></Text>
<Text style={styles.subtitle}></Text>
</View>
<TouchableOpacity onPress={handleClose} style={styles.closeButton} activeOpacity={0.7}>
<Ionicons name="close" size={24} color="#64748b" />
</TouchableOpacity>
</View>
{/* AI 智能识别 - 主推荐 */}
<TouchableOpacity activeOpacity={0.95} onPress={onAiRecognize}>
<LinearGradient
colors={['#0ea5e9', '#0284c7']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.aiCard}
>
{/* 推荐标签 */}
<View style={styles.recommendBadge}>
<Ionicons name="sparkles" size={14} color="#fbbf24" />
<Text style={styles.recommendText}>使</Text>
</View>
<View style={styles.aiContent}>
<View style={styles.aiLeft}>
<View style={styles.aiIconWrapper}>
<Ionicons name="camera" size={32} color="#fff" />
</View>
<View style={styles.aiTexts}>
<Text style={styles.aiTitle}>AI </Text>
<Text style={styles.aiDescription}>
{'\n'}
</Text>
<View style={styles.aiFeatures}>
<View style={styles.featureItem}>
<Ionicons name="flash" size={14} color="#fff" />
<Text style={styles.featureText}></Text>
</View>
<View style={styles.featureItem}>
<Ionicons name="checkmark-circle" size={14} color="#fff" />
<Text style={styles.featureText}></Text>
</View>
</View>
</View>
</View>
<Image
source={require('@/assets/images/medicine/image-medicine.png')}
style={styles.aiImage}
contentFit="contain"
/>
</View>
{/* AI 说明 */}
<View style={styles.aiFooter}>
<Ionicons name="information-circle-outline" size={14} color="rgba(255,255,255,0.8)" />
<Text style={styles.aiFooterText}> AI · 线</Text>
</View>
</LinearGradient>
</TouchableOpacity>
{/* 分隔线 */}
<View style={styles.divider}>
<View style={styles.dividerLine} />
<Text style={styles.dividerText}></Text>
<View style={styles.dividerLine} />
</View>
{/* 手动录入 - 次要选项 */}
<TouchableOpacity activeOpacity={0.9} onPress={onManualAdd}>
<View style={styles.manualCard}>
<View style={styles.manualLeft}>
<View style={styles.manualIconWrapper}>
<Ionicons name="create-outline" size={24} color="#6366f1" />
</View>
<View style={styles.manualTexts}>
<Text style={styles.manualTitle}></Text>
<Text style={styles.manualDescription}>
</Text>
</View>
</View>
<View style={styles.manualRight}>
<View style={styles.manualBadge}>
<Text style={styles.manualBadgeText}></Text>
</View>
<Ionicons name="chevron-forward" size={20} color="#94a3b8" />
</View>
</View>
</TouchableOpacity>
{/* 底部安全距离 */}
<View style={styles.safeArea} />
</Animated.View>
</Modal>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'transparent',
},
backdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.4)',
},
sheet: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
borderTopLeftRadius: 32,
borderTopRightRadius: 32,
paddingTop: 24,
paddingHorizontal: 20,
shadowColor: '#000',
shadowOpacity: 0.15,
shadowRadius: 20,
shadowOffset: { width: 0, height: -8 },
elevation: 12,
},
// Header
header: {
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
marginBottom: 24,
},
headerLeft: {
flex: 1,
},
title: {
fontSize: 24,
fontWeight: '700',
color: '#0f172a',
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: '#64748b',
fontWeight: '500',
},
closeButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 12,
},
// AI 卡片 - 主推荐
aiCard: {
borderRadius: 24,
padding: 20,
marginBottom: 20,
overflow: 'hidden',
shadowColor: '#0ea5e9',
shadowOpacity: 0.3,
shadowRadius: 16,
shadowOffset: { width: 0, height: 8 },
elevation: 8,
},
recommendBadge: {
position: 'absolute',
top: 16,
right: 16,
flexDirection: 'row',
alignItems: 'center',
gap: 4,
backgroundColor: 'rgba(255,255,255,0.25)',
paddingHorizontal: 10,
paddingVertical: 6,
borderRadius: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.3)',
},
recommendText: {
fontSize: 12,
fontWeight: '700',
color: '#fff',
},
aiContent: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
},
aiLeft: {
flex: 1,
flexDirection: 'row',
gap: 16,
},
aiIconWrapper: {
width: 56,
height: 56,
borderRadius: 16,
backgroundColor: 'rgba(255,255,255,0.2)',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 2,
borderColor: 'rgba(255,255,255,0.3)',
},
aiTexts: {
flex: 1,
gap: 8,
},
aiTitle: {
fontSize: 20,
fontWeight: '700',
color: '#fff',
},
aiDescription: {
fontSize: 14,
color: 'rgba(255,255,255,0.9)',
lineHeight: 20,
},
aiFeatures: {
flexDirection: 'row',
gap: 12,
marginTop: 4,
},
featureItem: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
featureText: {
fontSize: 12,
fontWeight: '600',
color: '#fff',
},
aiImage: {
width: 80,
height: 80,
marginLeft: 12,
},
aiFooter: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: 'rgba(255,255,255,0.2)',
},
aiFooterText: {
fontSize: 12,
color: 'rgba(255,255,255,0.8)',
fontWeight: '500',
},
// 分隔线
divider: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
},
dividerLine: {
flex: 1,
height: 1,
backgroundColor: '#e2e8f0',
},
dividerText: {
fontSize: 13,
color: '#94a3b8',
fontWeight: '600',
marginHorizontal: 16,
},
// 手动录入卡片 - 次要选项
manualCard: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#f8fafc',
borderRadius: 20,
padding: 16,
borderWidth: 1.5,
borderColor: '#e2e8f0',
},
manualLeft: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
manualIconWrapper: {
width: 48,
height: 48,
borderRadius: 14,
backgroundColor: '#eef2ff',
alignItems: 'center',
justifyContent: 'center',
},
manualTexts: {
flex: 1,
gap: 4,
},
manualTitle: {
fontSize: 16,
fontWeight: '700',
color: '#0f172a',
},
manualDescription: {
fontSize: 13,
color: '#64748b',
lineHeight: 18,
},
manualRight: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
marginLeft: 12,
},
manualBadge: {
backgroundColor: '#dcfce7',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
},
manualBadgeText: {
fontSize: 11,
fontWeight: '700',
color: '#16a34a',
},
// 底部安全距离
safeArea: {
height: 32,
},
});