2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 通知设置页 NotificationSettingsScreen(响应式适配)
|
|
|
|
|
|
* 胡萝卜BBS - 通知相关设置
|
|
|
|
|
|
* 在宽屏下居中显示
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
import React, { useState, useEffect, useMemo } from 'react';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
Switch,
|
|
|
|
|
|
ScrollView,
|
|
|
|
|
|
} from 'react-native';
|
2026-03-21 03:22:28 +08:00
|
|
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
2026-03-25 05:16:54 +08:00
|
|
|
|
import { spacing, borderRadius, useAppColors, type AppColors } from '../../theme';
|
2026-03-29 02:34:13 +08:00
|
|
|
|
import { Text } from '../../components/common';
|
2026-03-25 01:30:00 +08:00
|
|
|
|
import {
|
|
|
|
|
|
loadNotificationPreferences,
|
|
|
|
|
|
setPushNotificationsPreference,
|
|
|
|
|
|
setSoundPreference,
|
|
|
|
|
|
setVibrationPreference,
|
|
|
|
|
|
} from '../../services/notificationPreferences';
|
2026-03-29 02:34:13 +08:00
|
|
|
|
import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
|
|
|
|
|
|
|
|
|
|
|
// 内容最大宽度
|
|
|
|
|
|
const CONTENT_MAX_WIDTH = 720;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
interface NotificationSettingItem {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
subtitle?: string;
|
|
|
|
|
|
icon: string;
|
|
|
|
|
|
value: boolean;
|
|
|
|
|
|
onValueChange: (value: boolean) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const NotificationSettingsScreen: React.FC = () => {
|
2026-03-25 05:16:54 +08:00
|
|
|
|
const colors = useAppColors();
|
|
|
|
|
|
const styles = useMemo(() => createNotificationSettingsStyles(colors), [colors]);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const [vibrationEnabled, setVibrationEnabledState] = useState(true);
|
|
|
|
|
|
const [pushEnabled, setPushEnabled] = useState(true);
|
|
|
|
|
|
const [soundEnabled, setSoundEnabled] = useState(true);
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const { isWideScreen, isMobile } = useResponsive();
|
|
|
|
|
|
const insets = useSafeAreaInsets();
|
|
|
|
|
|
|
|
|
|
|
|
// 底部间距,避免被 TabBar 遮挡
|
|
|
|
|
|
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-25 01:30:00 +08:00
|
|
|
|
// 加载设置(与启动时 hydrate 一致,避免从其它入口进页时 UI 与内存不一致)
|
2026-03-09 21:29:03 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const loadSettings = async () => {
|
|
|
|
|
|
try {
|
2026-03-25 01:30:00 +08:00
|
|
|
|
const prefs = await loadNotificationPreferences();
|
|
|
|
|
|
setVibrationEnabledState(prefs.vibrationEnabled);
|
|
|
|
|
|
setPushEnabled(prefs.pushEnabled);
|
|
|
|
|
|
setSoundEnabled(prefs.soundEnabled);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
2026-03-25 01:30:00 +08:00
|
|
|
|
console.error('加载通知设置失败:', error);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
loadSettings();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const toggleVibration = async (value: boolean) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setVibrationEnabledState(value);
|
2026-03-25 01:30:00 +08:00
|
|
|
|
await setVibrationPreference(value);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('保存震动设置失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 01:30:00 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const settings: NotificationSettingItem[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'push',
|
|
|
|
|
|
title: '接收推送通知',
|
|
|
|
|
|
subtitle: '接收新消息、点赞、评论等推送',
|
|
|
|
|
|
icon: 'bell-outline',
|
|
|
|
|
|
value: pushEnabled,
|
2026-03-25 01:30:00 +08:00
|
|
|
|
onValueChange: togglePush,
|
2026-03-09 21:29:03 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'vibration',
|
|
|
|
|
|
title: '消息震动',
|
|
|
|
|
|
subtitle: '收到新消息时震动提醒',
|
|
|
|
|
|
icon: 'vibrate',
|
|
|
|
|
|
value: vibrationEnabled,
|
|
|
|
|
|
onValueChange: toggleVibration,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'sound',
|
|
|
|
|
|
title: '消息提示音',
|
|
|
|
|
|
subtitle: '收到新消息时播放提示音',
|
|
|
|
|
|
icon: 'volume-high',
|
|
|
|
|
|
value: soundEnabled,
|
2026-03-25 01:30:00 +08:00
|
|
|
|
onValueChange: toggleSound,
|
2026-03-09 21:29:03 +08:00
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染内容
|
|
|
|
|
|
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>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-29 02:34:13 +08:00
|
|
|
|
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container} edges={['bottom']}>
|
2026-03-29 02:34:13 +08:00
|
|
|
|
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
|
|
|
|
|
|
{renderContent()}
|
|
|
|
|
|
</ScrollView>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
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',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
maxWidth: CONTENT_MAX_WIDTH,
|
|
|
|
|
|
alignSelf: 'center',
|
|
|
|
|
|
width: '100%',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
export default NotificationSettingsScreen;
|