Files
digital-pilates/app/health-data-permissions.tsx
richarjiang 416d144387 feat(i18n): 添加国际化支持和中英文切换功能
- 实现完整的中英文国际化系统,支持动态语言切换
- 新增健康数据权限说明页面,提供HealthKit数据使用说明
- 为服药记录添加庆祝动画效果,提升用户体验
- 优化药品添加页面的阴影效果和视觉层次
- 更新个人页面以支持多语言显示和语言选择模态框
2025-11-13 09:05:23 +08:00

249 lines
6.7 KiB
TypeScript

import { HeaderBar } from '@/components/ui/HeaderBar';
import { Ionicons } from '@expo/vector-icons';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Linking, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type CardConfig = {
key: string;
icon: React.ComponentProps<typeof Ionicons>['name'];
color: string;
title: string;
items: string[];
};
export default function HealthDataPermissionsScreen() {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const cards = useMemo<CardConfig[]>(() => ([
{
key: 'usage',
icon: 'pulse-outline',
color: '#34D399',
title: t('healthPermissions.cards.usage.title'),
items: t('healthPermissions.cards.usage.items', { returnObjects: true }) as string[],
},
{
key: 'purpose',
icon: 'bulb-outline',
color: '#FBBF24',
title: t('healthPermissions.cards.purpose.title'),
items: t('healthPermissions.cards.purpose.items', { returnObjects: true }) as string[],
},
{
key: 'control',
icon: 'shield-checkmark-outline',
color: '#60A5FA',
title: t('healthPermissions.cards.control.title'),
items: t('healthPermissions.cards.control.items', { returnObjects: true }) as string[],
},
{
key: 'privacy',
icon: 'lock-closed-outline',
color: '#A78BFA',
title: t('healthPermissions.cards.privacy.title'),
items: t('healthPermissions.cards.privacy.items', { returnObjects: true }) as string[],
},
]), [t]);
const calloutItems = useMemo(() => (
t('healthPermissions.callout.items', { returnObjects: true }) as string[]
), [t]);
const contactDescription = t('healthPermissions.contact.description');
const contactEmail = t('healthPermissions.contact.email');
const handleContactPress = () => {
if (!contactEmail) return;
void Linking.openURL(`mailto:${contactEmail}`);
};
const contentTopPadding = insets.top + 72;
return (
<View style={styles.container}>
<HeaderBar
title={t('healthPermissions.title')}
variant="elevated"
transparent={true}
/>
<ScrollView
style={styles.scrollView}
contentContainerStyle={{
paddingTop: contentTopPadding,
paddingBottom: insets.bottom + 32,
paddingHorizontal: 20,
}}
showsVerticalScrollIndicator={false}
>
<View style={styles.heroCard}>
<Text style={styles.heroTitle}>{t('healthPermissions.title')}</Text>
<Text style={styles.heroSubtitle}>{t('healthPermissions.subtitle')}</Text>
</View>
{cards.map((card) => (
<View key={card.key} style={styles.infoCard}>
<View style={styles.cardHeader}>
<View style={[styles.cardIcon, { backgroundColor: `${card.color}22` }]}>
<Ionicons name={card.icon} size={20} color={card.color} />
</View>
<Text style={styles.cardTitle}>{card.title}</Text>
</View>
{card.items.map((item, index) => (
<View key={`${card.key}-${index}`} style={styles.cardItemRow}>
<View style={styles.bullet} />
<Text style={styles.cardItemText}>{item}</Text>
</View>
))}
</View>
))}
<View style={styles.calloutCard}>
<View style={styles.cardHeader}>
<View style={[styles.cardIcon, { backgroundColor: '#F472B622' }]}>
<Ionicons name="alert-circle-outline" size={20} color="#F472B6" />
</View>
<Text style={styles.cardTitle}>{t('healthPermissions.callout.title')}</Text>
</View>
{calloutItems.map((item, index) => (
<View key={`callout-${index}`} style={styles.cardItemRow}>
<View style={styles.bullet} />
<Text style={styles.cardItemText}>{item}</Text>
</View>
))}
</View>
<View style={styles.contactCard}>
<Text style={styles.contactTitle}>{t('healthPermissions.contact.title')}</Text>
<Text style={styles.contactDescription}>{contactDescription}</Text>
{contactEmail ? (
<TouchableOpacity style={styles.contactButton} onPress={handleContactPress} activeOpacity={0.85}>
<Ionicons name="mail-outline" size={18} color="#fff" />
<Text style={styles.contactButtonText}>{contactEmail}</Text>
</TouchableOpacity>
) : null}
</View>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F9FAFB',
},
scrollView: {
flex: 1,
},
heroCard: {
backgroundColor: '#fff',
borderRadius: 20,
padding: 20,
marginBottom: 16,
shadowColor: '#000',
shadowOpacity: 0.05,
shadowRadius: 10,
shadowOffset: { width: 0, height: 8 },
elevation: 2,
},
heroTitle: {
fontSize: 24,
fontWeight: '700',
color: '#111827',
marginBottom: 12,
},
heroSubtitle: {
fontSize: 16,
color: '#4B5563',
lineHeight: 22,
},
infoCard: {
backgroundColor: '#fff',
borderRadius: 18,
padding: 18,
marginBottom: 14,
borderWidth: 1,
borderColor: '#F3F4F6',
},
cardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
cardIcon: {
width: 36,
height: 36,
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
marginRight: 10,
},
cardTitle: {
fontSize: 16,
fontWeight: '600',
color: '#111827',
},
cardItemRow: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: 8,
},
bullet: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: '#9370DB',
marginTop: 8,
marginRight: 10,
},
cardItemText: {
flex: 1,
fontSize: 14,
color: '#374151',
lineHeight: 20,
},
calloutCard: {
backgroundColor: '#FEF3F2',
borderRadius: 18,
padding: 18,
marginBottom: 14,
borderWidth: 1,
borderColor: '#FECACA',
},
contactCard: {
backgroundColor: '#fff',
borderRadius: 18,
padding: 18,
borderWidth: 1,
borderColor: '#F3F4F6',
},
contactTitle: {
fontSize: 16,
fontWeight: '600',
color: '#111827',
marginBottom: 8,
},
contactDescription: {
fontSize: 14,
color: '#4B5563',
lineHeight: 20,
marginBottom: 12,
},
contactButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#111827',
paddingHorizontal: 16,
paddingVertical: 12,
borderRadius: 12,
},
contactButtonText: {
marginLeft: 8,
color: '#fff',
fontWeight: '600',
},
});