Files
frontend/src/screens/profile/NotificationSettingsScreen.tsx

259 lines
7.6 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.
/**
* 通知设置页 NotificationSettingsScreen响应式适配
* 胡萝卜BBS - 通知相关设置
* 在宽屏下居中显示
*/
import React, { useState, useEffect, useMemo } from 'react';
import {
View,
StyleSheet,
Switch,
ScrollView,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, borderRadius, useAppColors, type AppColors } from '../../theme';
import { Text } from '../../components/common';
import {
loadNotificationPreferences,
setPushNotificationsPreference,
setSoundPreference,
setVibrationPreference,
} from '../../services/notificationPreferences';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
// 内容最大宽度
const CONTENT_MAX_WIDTH = 720;
interface NotificationSettingItem {
key: string;
title: string;
subtitle?: string;
icon: string;
value: boolean;
onValueChange: (value: boolean) => void;
}
export const NotificationSettingsScreen: React.FC = () => {
const colors = useAppColors();
const styles = useMemo(() => createNotificationSettingsStyles(colors), [colors]);
const [vibrationEnabled, setVibrationEnabledState] = useState(true);
const [pushEnabled, setPushEnabled] = useState(true);
const [soundEnabled, setSoundEnabled] = useState(true);
const { isWideScreen, isMobile } = useResponsive();
const insets = useSafeAreaInsets();
// 底部间距,避免被 TabBar 遮挡
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
// 加载设置(与启动时 hydrate 一致,避免从其它入口进页时 UI 与内存不一致)
useEffect(() => {
const loadSettings = async () => {
try {
const prefs = await loadNotificationPreferences();
setVibrationEnabledState(prefs.vibrationEnabled);
setPushEnabled(prefs.pushEnabled);
setSoundEnabled(prefs.soundEnabled);
} catch (error) {
console.error('加载通知设置失败:', error);
}
};
loadSettings();
}, []);
const toggleVibration = async (value: boolean) => {
try {
setVibrationEnabledState(value);
await setVibrationPreference(value);
} catch (error) {
console.error('保存震动设置失败:', error);
}
};
const togglePush = async (value: boolean) => {
try {
setPushEnabled(value);
await setPushNotificationsPreference(value);
} catch (error) {
console.error('保存推送开关失败:', error);
}
};
const toggleSound = async (value: boolean) => {
try {
setSoundEnabled(value);
await setSoundPreference(value);
} catch (error) {
console.error('保存提示音设置失败:', error);
}
};
const settings: NotificationSettingItem[] = [
{
key: 'push',
title: '接收推送通知',
subtitle: '接收新消息、点赞、评论等推送',
icon: 'bell-outline',
value: pushEnabled,
onValueChange: togglePush,
},
{
key: 'vibration',
title: '消息震动',
subtitle: '收到新消息时震动提醒',
icon: 'vibrate',
value: vibrationEnabled,
onValueChange: toggleVibration,
},
{
key: 'sound',
title: '消息提示音',
subtitle: '收到新消息时播放提示音',
icon: 'volume-high',
value: soundEnabled,
onValueChange: toggleSound,
},
];
// 渲染内容
const renderContent = () => (
<>
{/* 消息通知设置 */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<MaterialCommunityIcons name="message-text-outline" size={18} color={colors.primary.main} />
<Text variant="caption" color={colors.text.secondary} style={styles.sectionTitle}>
</Text>
</View>
<View style={styles.card}>
{settings.map((item, index) => (
<View key={item.key}>
<View style={styles.settingItem}>
<View style={styles.iconContainer}>
<MaterialCommunityIcons
name={item.icon as any}
size={20}
color={colors.primary.main}
/>
</View>
<View style={styles.settingContent}>
<Text variant="body" color={colors.text.primary}>
{item.title}
</Text>
{item.subtitle && (
<Text variant="caption" color={colors.text.secondary} style={styles.subtitle}>
{item.subtitle}
</Text>
)}
</View>
<Switch
value={item.value}
onValueChange={item.onValueChange}
trackColor={{ false: colors.divider, true: colors.primary.main }}
thumbColor={colors.background.paper}
/>
</View>
{index < settings.length - 1 && <View style={styles.divider} />}
</View>
))}
</View>
</View>
{/* 提示信息 */}
<View style={styles.tipContainer}>
<MaterialCommunityIcons name="information-outline" size={16} color={colors.text.hint} />
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
</Text>
</View>
</>
);
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
{renderContent()}
</ScrollView>
</SafeAreaView>
);
};
function createNotificationSettingsStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.default,
},
scrollContent: {
paddingVertical: spacing.md,
},
section: {
marginBottom: spacing.lg,
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: spacing.lg,
marginBottom: spacing.sm,
gap: spacing.xs,
},
sectionTitle: {
fontWeight: '600',
},
card: {
backgroundColor: colors.background.paper,
marginHorizontal: spacing.lg,
borderRadius: borderRadius.lg,
overflow: 'hidden',
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
settingItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.md,
paddingHorizontal: spacing.md,
},
iconContainer: {
width: 36,
height: 36,
borderRadius: borderRadius.md,
backgroundColor: colors.primary.light + '20',
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
},
settingContent: {
flex: 1,
},
subtitle: {
marginTop: 2,
},
divider: {
height: 1,
backgroundColor: colors.divider,
marginLeft: 36 + spacing.md + spacing.md,
},
tipContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
marginHorizontal: spacing.lg,
padding: spacing.md,
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
gap: spacing.sm,
},
tipText: {
flex: 1,
lineHeight: 20,
},
});
}
export default NotificationSettingsScreen;