Files
digital-pilates/components/developer/FastingNotificationTestPanel.tsx
richarjiang cf069f3537 feat(fasting): 重构断食通知系统并增强可靠性
- 新增 useFastingNotifications hook 统一管理通知状态和同步逻辑
- 实现四阶段通知提醒:开始前30分钟、开始时、结束前30分钟、结束时
- 添加通知验证机制,确保通知正确设置和避免重复
- 新增 NotificationErrorAlert 组件显示通知错误并提供重试选项
- 实现断食计划持久化存储,应用重启后自动恢复
- 添加开发者测试面板用于验证通知系统可靠性
- 优化通知同步策略,支持选择性更新减少不必要的操作
- 修复个人页面编辑按钮样式问题
- 更新应用版本号至 1.0.18
2025-10-14 15:05:11 +08:00

229 lines
5.7 KiB
TypeScript
Raw Permalink 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 { FASTING_PLANS, FastingPlan } from '@/constants/Fasting';
import { fastingNotificationTester } from '@/utils/fastingNotificationTest';
import React, { useState } from 'react';
import { Alert, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
interface FastingNotificationTestPanelProps {
onClose: () => void;
}
export const FastingNotificationTestPanel: React.FC<FastingNotificationTestPanelProps> = ({
onClose,
}) => {
const [isRunning, setIsRunning] = useState(false);
const [testResults, setTestResults] = useState<Array<{
testName: string;
passed: boolean;
message: string;
timestamp: Date;
}>>([]);
const runTest = async (plan: FastingPlan) => {
setIsRunning(true);
setTestResults([]);
try {
const results = await fastingNotificationTester.runFullTestSuite(plan);
setTestResults(results);
const passedCount = results.filter(r => r.passed).length;
const totalCount = results.length;
Alert.alert(
'测试完成',
`测试结果: ${passedCount}/${totalCount} 通过`,
[{ text: '确定', onPress: () => { } }]
);
} catch (error) {
Alert.alert(
'测试失败',
`测试过程中发生错误: ${error instanceof Error ? error.message : '未知错误'}`,
[{ text: '确定', onPress: () => { } }]
);
} finally {
setIsRunning(false);
}
};
const clearResults = () => {
setTestResults([]);
fastingNotificationTester.clearResults();
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}></Text>
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
<Text style={styles.closeButtonText}>×</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.content}>
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
{FASTING_PLANS.map((plan) => (
<TouchableOpacity
key={plan.id}
style={[styles.planButton, isRunning && styles.disabledButton]}
onPress={() => runTest(plan)}
disabled={isRunning}
>
<Text style={styles.planButtonText}>{plan.title}</Text>
<Text style={styles.planSubtitle}>{plan.subtitle}</Text>
</TouchableOpacity>
))}
</View>
{testResults.length > 0 && (
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}></Text>
<TouchableOpacity style={styles.clearButton} onPress={clearResults}>
<Text style={styles.clearButtonText}></Text>
</TouchableOpacity>
</View>
{testResults.map((result, index) => (
<View key={index} style={[
styles.resultItem,
result.passed ? styles.passedResult : styles.failedResult
]}>
<Text style={styles.resultTestName}>{result.testName}</Text>
<Text style={styles.resultMessage}>{result.message}</Text>
<Text style={styles.resultTime}>
{result.timestamp.toLocaleTimeString()}
</Text>
</View>
))}
</View>
)}
{isRunning && (
<View style={styles.section}>
<Text style={styles.runningText}>...</Text>
</View>
)}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0',
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#333333',
},
closeButton: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#F0F0F0',
justifyContent: 'center',
alignItems: 'center',
},
closeButtonText: {
fontSize: 18,
fontWeight: '600',
color: '#666666',
},
content: {
flex: 1,
padding: 16,
},
section: {
marginBottom: 24,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#333333',
marginBottom: 12,
},
planButton: {
backgroundColor: '#FFFFFF',
padding: 12,
borderRadius: 8,
marginBottom: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
disabledButton: {
opacity: 0.5,
},
planButtonText: {
fontSize: 14,
fontWeight: '500',
color: '#333333',
},
planSubtitle: {
fontSize: 12,
color: '#666666',
marginTop: 2,
},
clearButton: {
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: '#F0F0F0',
borderRadius: 4,
},
clearButtonText: {
fontSize: 12,
color: '#666666',
},
resultItem: {
padding: 12,
borderRadius: 8,
marginBottom: 8,
borderLeftWidth: 4,
},
passedResult: {
backgroundColor: '#F0F9F0',
borderLeftColor: '#4CAF50',
},
failedResult: {
backgroundColor: '#FFF3F3',
borderLeftColor: '#F44336',
},
resultTestName: {
fontSize: 14,
fontWeight: '500',
color: '#333333',
},
resultMessage: {
fontSize: 12,
color: '#666666',
marginTop: 2,
},
resultTime: {
fontSize: 10,
color: '#999999',
marginTop: 4,
},
runningText: {
fontSize: 14,
color: '#666666',
textAlign: 'center',
fontStyle: 'italic',
},
});