- 添加新的processing任务标识符到iOS配置文件 - 重构BackgroundTaskBridge支持不同任务类型(refresh/processing) - 增强后台任务日志记录和调试信息 - 修复任务类型配置不匹配问题 - 改进任务调度逻辑和错误处理机制 - 添加任务执行时间戳记录用于调试 - 移除notification-settings中未使用的AuthGuard依赖
385 lines
11 KiB
TypeScript
385 lines
11 KiB
TypeScript
import { ThemedText } from '@/components/ThemedText';
|
|
import { useI18n } from '@/hooks/useI18n';
|
|
import { useNotifications } from '@/hooks/useNotifications';
|
|
import {
|
|
getMedicationReminderEnabled,
|
|
getNotificationEnabled,
|
|
setMedicationReminderEnabled,
|
|
setNotificationEnabled
|
|
} from '@/utils/userPreferences';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { router, useFocusEffect } from 'expo-router';
|
|
import React, { useCallback, useState } from 'react';
|
|
import { Alert, Linking, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
export default function NotificationSettingsScreen() {
|
|
const insets = useSafeAreaInsets();
|
|
const { t } = useI18n();
|
|
const { requestPermission, sendNotification } = useNotifications();
|
|
const isLgAvailable = isLiquidGlassAvailable();
|
|
|
|
// 通知设置状态
|
|
const [notificationEnabled, setNotificationEnabledState] = useState(false);
|
|
const [medicationReminderEnabled, setMedicationReminderEnabledState] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
// 加载通知设置
|
|
const loadNotificationSettings = useCallback(async () => {
|
|
try {
|
|
const [notification, medicationReminder] = await Promise.all([
|
|
getNotificationEnabled(),
|
|
getMedicationReminderEnabled(),
|
|
]);
|
|
setNotificationEnabledState(notification);
|
|
setMedicationReminderEnabledState(medicationReminder);
|
|
} catch (error) {
|
|
console.error('Failed to load notification settings:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 页面聚焦时加载设置
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
loadNotificationSettings();
|
|
}, [loadNotificationSettings])
|
|
);
|
|
|
|
// 处理总通知开关变化
|
|
const handleNotificationToggle = async (value: boolean) => {
|
|
if (value) {
|
|
try {
|
|
// 先检查系统权限
|
|
const status = await requestPermission();
|
|
if (status === 'granted') {
|
|
// 系统权限获取成功,保存用户偏好设置
|
|
await setNotificationEnabled(true);
|
|
setNotificationEnabledState(true);
|
|
|
|
// 发送测试通知
|
|
await sendNotification({
|
|
title: t('notificationSettings.alerts.notificationsEnabled.title'),
|
|
body: t('notificationSettings.alerts.notificationsEnabled.body'),
|
|
sound: true,
|
|
priority: 'normal',
|
|
});
|
|
} else {
|
|
// 系统权限被拒绝,不更新用户偏好设置
|
|
Alert.alert(
|
|
t('notificationSettings.alerts.permissionDenied.title'),
|
|
t('notificationSettings.alerts.permissionDenied.message'),
|
|
[
|
|
{ text: t('notificationSettings.alerts.permissionDenied.cancel'), style: 'cancel' },
|
|
{ text: t('notificationSettings.alerts.permissionDenied.goToSettings'), onPress: () => Linking.openSettings() }
|
|
]
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to enable push notifications:', error);
|
|
Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.message'));
|
|
}
|
|
} else {
|
|
try {
|
|
// 关闭推送,保存用户偏好设置
|
|
await setNotificationEnabled(false);
|
|
setNotificationEnabledState(false);
|
|
// 关闭总开关时,也关闭药品提醒
|
|
await setMedicationReminderEnabled(false);
|
|
setMedicationReminderEnabledState(false);
|
|
} catch (error) {
|
|
console.error('Failed to disable push notifications:', error);
|
|
Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.saveFailed'));
|
|
}
|
|
}
|
|
};
|
|
|
|
// 处理药品通知提醒开关变化
|
|
const handleMedicationReminderToggle = async (value: boolean) => {
|
|
try {
|
|
await setMedicationReminderEnabled(value);
|
|
setMedicationReminderEnabledState(value);
|
|
|
|
if (value) {
|
|
// 发送测试通知
|
|
await sendNotification({
|
|
title: t('notificationSettings.alerts.medicationReminderEnabled.title'),
|
|
body: t('notificationSettings.alerts.medicationReminderEnabled.body'),
|
|
sound: true,
|
|
priority: 'high',
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to set medication reminder:', error);
|
|
Alert.alert(t('notificationSettings.alerts.error.title'), t('notificationSettings.alerts.error.medicationReminderFailed'));
|
|
}
|
|
};
|
|
|
|
// 返回按钮
|
|
const BackButton = () => (
|
|
<TouchableOpacity
|
|
onPress={() => router.back()}
|
|
style={styles.backButton}
|
|
activeOpacity={0.7}
|
|
>
|
|
{isLgAvailable ? (
|
|
<GlassView
|
|
style={styles.glassButton}
|
|
glassEffectStyle="clear"
|
|
tintColor="rgba(255, 255, 255, 0.3)"
|
|
isInteractive={true}
|
|
>
|
|
<Ionicons name="chevron-back" size={24} color="#333" />
|
|
</GlassView>
|
|
) : (
|
|
<View style={[styles.glassButton, styles.fallbackButton]}>
|
|
<Ionicons name="chevron-back" size={24} color="#333" />
|
|
</View>
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
|
|
// 开关项组件
|
|
const SwitchItem = ({
|
|
title,
|
|
description,
|
|
value,
|
|
onValueChange,
|
|
disabled = false
|
|
}: {
|
|
title: string;
|
|
description: string;
|
|
value: boolean;
|
|
onValueChange: (value: boolean) => void;
|
|
disabled?: boolean;
|
|
}) => (
|
|
<View style={styles.switchItem}>
|
|
<View style={styles.switchItemLeft}>
|
|
<Text style={styles.switchItemTitle}>{title}</Text>
|
|
<Text style={styles.switchItemDescription}>{description}</Text>
|
|
</View>
|
|
<Switch
|
|
value={value}
|
|
onValueChange={onValueChange}
|
|
disabled={disabled}
|
|
trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
|
|
thumbColor="#FFFFFF"
|
|
style={styles.switch}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={styles.container}>
|
|
<StatusBar barStyle="dark-content" backgroundColor="transparent" translucent />
|
|
<LinearGradient
|
|
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#e6f6fcff']}
|
|
style={styles.gradientBackground}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 0, y: 1 }}
|
|
/>
|
|
<View style={styles.loadingContainer}>
|
|
<Text style={styles.loadingText}>{t('notificationSettings.loading')}</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<StatusBar barStyle="dark-content" backgroundColor="transparent" translucent />
|
|
|
|
{/* 背景渐变 */}
|
|
<LinearGradient
|
|
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#e6f6fcff']}
|
|
style={styles.gradientBackground}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 0, y: 1 }}
|
|
/>
|
|
|
|
{/* 装饰性圆圈 */}
|
|
<View style={styles.decorativeCircle1} />
|
|
<View style={styles.decorativeCircle2} />
|
|
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={{
|
|
paddingTop: insets.top + 20,
|
|
paddingBottom: insets.bottom + 20,
|
|
paddingHorizontal: 16,
|
|
}}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* 头部 */}
|
|
<View style={styles.header}>
|
|
<BackButton />
|
|
<ThemedText style={styles.title}>{t('notificationSettings.title')}</ThemedText>
|
|
</View>
|
|
|
|
{/* 通知设置部分 */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.notifications')}</Text>
|
|
<View style={styles.card}>
|
|
<SwitchItem
|
|
title={t('notificationSettings.items.pushNotifications.title')}
|
|
description={t('notificationSettings.items.pushNotifications.description')}
|
|
value={notificationEnabled}
|
|
onValueChange={handleNotificationToggle}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 药品提醒部分 */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.medicationReminder')}</Text>
|
|
<View style={styles.card}>
|
|
<SwitchItem
|
|
title={t('notificationSettings.items.medicationReminder.title')}
|
|
description={t('notificationSettings.items.medicationReminder.description')}
|
|
value={medicationReminderEnabled}
|
|
onValueChange={handleMedicationReminderToggle}
|
|
disabled={!notificationEnabled}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 说明部分 */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.description')}</Text>
|
|
<View style={styles.card}>
|
|
<Text style={styles.description}>
|
|
{t('notificationSettings.description.text')}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
gradientBackground: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
},
|
|
decorativeCircle1: {
|
|
position: 'absolute',
|
|
top: 40,
|
|
right: 20,
|
|
width: 60,
|
|
height: 60,
|
|
borderRadius: 30,
|
|
backgroundColor: '#0EA5E9',
|
|
opacity: 0.1,
|
|
},
|
|
decorativeCircle2: {
|
|
position: 'absolute',
|
|
bottom: -15,
|
|
left: -15,
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
backgroundColor: '#0EA5E9',
|
|
opacity: 0.05,
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
loadingText: {
|
|
fontSize: 16,
|
|
color: '#666',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: 24,
|
|
},
|
|
backButton: {
|
|
marginRight: 16,
|
|
},
|
|
glassButton: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
overflow: 'hidden',
|
|
},
|
|
fallbackButton: {
|
|
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
borderWidth: 1,
|
|
borderColor: 'rgba(255, 255, 255, 0.3)',
|
|
},
|
|
title: {
|
|
fontSize: 28,
|
|
fontWeight: 'bold',
|
|
color: '#2C3E50',
|
|
},
|
|
section: {
|
|
marginBottom: 24,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
color: '#2C3E50',
|
|
marginBottom: 12,
|
|
paddingHorizontal: 4,
|
|
},
|
|
card: {
|
|
backgroundColor: '#FFFFFF',
|
|
borderRadius: 12,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 1 },
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 4,
|
|
elevation: 2,
|
|
overflow: 'hidden',
|
|
},
|
|
switchItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingVertical: 16,
|
|
paddingHorizontal: 16,
|
|
},
|
|
switchItemLeft: {
|
|
flex: 1,
|
|
marginRight: 16,
|
|
},
|
|
switchItemTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '500',
|
|
color: '#2C3E50',
|
|
marginBottom: 4,
|
|
},
|
|
switchItemDescription: {
|
|
fontSize: 14,
|
|
color: '#6C757D',
|
|
lineHeight: 20,
|
|
},
|
|
switch: {
|
|
transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
|
|
},
|
|
description: {
|
|
fontSize: 14,
|
|
color: '#6C757D',
|
|
lineHeight: 22,
|
|
paddingVertical: 16,
|
|
paddingHorizontal: 16,
|
|
},
|
|
}); |