- 添加推送通知管理器和设备令牌管理 - 实现推送通知权限请求和令牌注册 - 新增推送通知设置页面 - 集成推送通知初始化到应用启动流程 - 添加推送通知API服务和本地存储管理 - 更新个人页面添加推送通知设置入口
328 lines
9.3 KiB
TypeScript
328 lines
9.3 KiB
TypeScript
import { ThemedText } from '@/components/ThemedText';
|
||
import { ThemedView } from '@/components/ThemedView';
|
||
import { getTokenStatusDescription, isPushNotificationAvailable, usePushNotifications } from '@/hooks/usePushNotifications';
|
||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||
import { TokenStatus } from '@/services/pushNotificationManager';
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
ActivityIndicator,
|
||
Alert,
|
||
ScrollView,
|
||
StyleSheet,
|
||
Text,
|
||
TouchableOpacity,
|
||
View
|
||
} from 'react-native';
|
||
|
||
export default function PushNotificationSettingsScreen() {
|
||
const backgroundColor = useThemeColor({}, 'background');
|
||
const textColor = useThemeColor({}, 'text');
|
||
const borderColor = useThemeColor({}, 'border');
|
||
const primaryColor = useThemeColor({}, 'primary');
|
||
|
||
const {
|
||
isInitialized,
|
||
tokenStatus,
|
||
isLoading,
|
||
registerToken,
|
||
getCurrentToken,
|
||
updateTokenStatus,
|
||
clearAllData,
|
||
} = usePushNotifications();
|
||
|
||
const [isRegistering, setIsRegistering] = useState(false);
|
||
const [currentToken, setCurrentToken] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
updateTokenStatus();
|
||
setCurrentToken(getCurrentToken());
|
||
}, [isInitialized, tokenStatus]);
|
||
|
||
const handleRegisterToken = async () => {
|
||
setIsRegistering(true);
|
||
try {
|
||
const success = await registerToken();
|
||
if (success) {
|
||
Alert.alert('成功', '设备令牌注册成功');
|
||
} else {
|
||
Alert.alert('失败', '设备令牌注册失败,请检查网络连接和权限设置');
|
||
}
|
||
} catch (error) {
|
||
console.error('注册设备令牌失败:', error);
|
||
Alert.alert('错误', '注册设备令牌时发生错误');
|
||
} finally {
|
||
setIsRegistering(false);
|
||
}
|
||
};
|
||
|
||
const handleClearData = () => {
|
||
Alert.alert(
|
||
'确认清除',
|
||
'这将清除所有本地推送通知数据,包括设备令牌。确定要继续吗?',
|
||
[
|
||
{ text: '取消', style: 'cancel' },
|
||
{
|
||
text: '确定',
|
||
style: 'destructive',
|
||
onPress: async () => {
|
||
try {
|
||
await clearAllData();
|
||
Alert.alert('成功', '推送通知数据已清除');
|
||
} catch (error) {
|
||
console.error('清除数据失败:', error);
|
||
Alert.alert('错误', '清除数据时发生错误');
|
||
}
|
||
},
|
||
},
|
||
]
|
||
);
|
||
};
|
||
|
||
const copyTokenToClipboard = () => {
|
||
if (currentToken) {
|
||
// 这里可以使用 Clipboard API 复制到剪贴板
|
||
Alert.alert('令牌已复制', '设备令牌已复制到剪贴板(功能待实现)');
|
||
}
|
||
};
|
||
|
||
const getStatusColor = () => {
|
||
switch (tokenStatus) {
|
||
case TokenStatus.REGISTERED:
|
||
return '#4CAF50';
|
||
case TokenStatus.GRANTED:
|
||
return '#2196F3';
|
||
case TokenStatus.DENIED:
|
||
return '#F44336';
|
||
case TokenStatus.FAILED:
|
||
return '#FF9800';
|
||
default:
|
||
return '#9E9E9E';
|
||
}
|
||
};
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<ThemedView style={[styles.container, { backgroundColor }]}>
|
||
<View style={styles.loadingContainer}>
|
||
<ActivityIndicator size="large" color={primaryColor} />
|
||
<ThemedText style={styles.loadingText}>加载推送通知设置...</ThemedText>
|
||
</View>
|
||
</ThemedView>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<ThemedView style={[styles.container, { backgroundColor }]}>
|
||
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
|
||
<View style={styles.header}>
|
||
<ThemedText style={styles.title}>推送通知设置</ThemedText>
|
||
<ThemedText style={styles.subtitle}>
|
||
管理您的推送通知权限和设备令牌
|
||
</ThemedText>
|
||
</View>
|
||
|
||
<View style={[styles.card, { borderColor }]}>
|
||
<ThemedText style={styles.cardTitle}>状态信息</ThemedText>
|
||
|
||
<View style={styles.statusRow}>
|
||
<ThemedText style={styles.statusLabel}>初始化状态:</ThemedText>
|
||
<View style={styles.statusValue}>
|
||
<Text style={[styles.statusText, { color: isInitialized ? '#4CAF50' : '#F44336' }]}>
|
||
{isInitialized ? '已初始化' : '未初始化'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View style={styles.statusRow}>
|
||
<ThemedText style={styles.statusLabel}>令牌状态:</ThemedText>
|
||
<View style={styles.statusValue}>
|
||
<Text style={[styles.statusText, { color: getStatusColor() }]}>
|
||
{getTokenStatusDescription(tokenStatus)}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View style={styles.statusRow}>
|
||
<ThemedText style={styles.statusLabel}>推送可用:</ThemedText>
|
||
<View style={styles.statusValue}>
|
||
<Text style={[styles.statusText, { color: isPushNotificationAvailable(tokenStatus) ? '#4CAF50' : '#F44336' }]}>
|
||
{isPushNotificationAvailable(tokenStatus) ? '是' : '否'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{currentToken && (
|
||
<View style={[styles.card, { borderColor }]}>
|
||
<ThemedText style={styles.cardTitle}>设备令牌</ThemedText>
|
||
<View style={styles.tokenContainer}>
|
||
<Text style={[styles.tokenText, { color: textColor }]}>
|
||
{currentToken.substring(0, 20)}...{currentToken.substring(currentToken.length - 10)}
|
||
</Text>
|
||
<TouchableOpacity
|
||
style={[styles.copyButton, { borderColor }]}
|
||
onPress={copyTokenToClipboard}
|
||
>
|
||
<Text style={[styles.copyButtonText, { color: primaryColor }]}>复制</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
<View style={[styles.card, { borderColor }]}>
|
||
<ThemedText style={styles.cardTitle}>操作</ThemedText>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.actionButton, { backgroundColor: primaryColor }]}
|
||
onPress={handleRegisterToken}
|
||
disabled={isRegistering}
|
||
>
|
||
{isRegistering ? (
|
||
<ActivityIndicator size="small" color="#ffffff" />
|
||
) : (
|
||
<Text style={styles.actionButtonText}>注册设备令牌</Text>
|
||
)}
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.actionButton, styles.secondaryButton, { borderColor }]}
|
||
onPress={() => updateTokenStatus()}
|
||
>
|
||
<Text style={[styles.secondaryButtonText, { color: primaryColor }]}>刷新状态</Text>
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
style={[styles.actionButton, styles.dangerButton]}
|
||
onPress={handleClearData}
|
||
>
|
||
<Text style={styles.dangerButtonText}>清除所有数据</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
<View style={[styles.card, { borderColor }]}>
|
||
<ThemedText style={styles.cardTitle}>说明</ThemedText>
|
||
<ThemedText style={styles.description}>
|
||
• 推送通知需要在真实iOS设备上使用{'\n'}
|
||
• 设备令牌用于接收远程推送通知{'\n'}
|
||
• 如果令牌状态显示"权限被拒绝",请在系统设置中开启通知权限{'\n'}
|
||
• 清除数据后需要重新初始化推送通知
|
||
</ThemedText>
|
||
</View>
|
||
</ScrollView>
|
||
</ThemedView>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
},
|
||
scrollView: {
|
||
flex: 1,
|
||
padding: 16,
|
||
},
|
||
loadingContainer: {
|
||
flex: 1,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
loadingText: {
|
||
marginTop: 16,
|
||
fontSize: 16,
|
||
},
|
||
header: {
|
||
marginBottom: 24,
|
||
},
|
||
title: {
|
||
fontSize: 28,
|
||
fontWeight: 'bold',
|
||
marginBottom: 8,
|
||
},
|
||
subtitle: {
|
||
fontSize: 16,
|
||
opacity: 0.7,
|
||
},
|
||
card: {
|
||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||
borderRadius: 12,
|
||
padding: 16,
|
||
marginBottom: 16,
|
||
borderWidth: 1,
|
||
},
|
||
cardTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
marginBottom: 12,
|
||
},
|
||
statusRow: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
marginBottom: 8,
|
||
},
|
||
statusLabel: {
|
||
fontSize: 16,
|
||
flex: 1,
|
||
},
|
||
statusValue: {
|
||
flex: 1,
|
||
alignItems: 'flex-end',
|
||
},
|
||
statusText: {
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
},
|
||
tokenContainer: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
},
|
||
tokenText: {
|
||
fontSize: 14,
|
||
fontFamily: 'monospace',
|
||
flex: 1,
|
||
marginRight: 12,
|
||
},
|
||
copyButton: {
|
||
paddingHorizontal: 12,
|
||
paddingVertical: 6,
|
||
borderRadius: 6,
|
||
borderWidth: 1,
|
||
},
|
||
copyButtonText: {
|
||
fontSize: 14,
|
||
fontWeight: '500',
|
||
},
|
||
actionButton: {
|
||
borderRadius: 8,
|
||
paddingVertical: 12,
|
||
paddingHorizontal: 16,
|
||
alignItems: 'center',
|
||
marginBottom: 12,
|
||
},
|
||
actionButtonText: {
|
||
color: '#ffffff',
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
},
|
||
secondaryButton: {
|
||
backgroundColor: 'transparent',
|
||
borderWidth: 1,
|
||
},
|
||
secondaryButtonText: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
},
|
||
dangerButton: {
|
||
backgroundColor: '#F44336',
|
||
},
|
||
dangerButtonText: {
|
||
color: '#ffffff',
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
},
|
||
description: {
|
||
fontSize: 14,
|
||
lineHeight: 20,
|
||
},
|
||
}); |