Refactor the navigation and header strategy by replacing native stack headers with a custom `SimpleHeader` component across most profile and detail screens. This provides a more consistent UI/UX and better control over layout behavior. - Implement `SimpleHeader` component in `src/components/common`. - Disable native header rendering in `app/(app)/(tabs)/profile/_layout.tsx` and `app/_layout.tsx`. - Update profile sub-screens to use `SimpleHeader` and `StatusBar` from `expo-status-bar`. - Refactor `PostDetailScreen` to use a custom header implementation for better integration with the post author information. - Update `UserScreen` to wrap content in `SafeAreaView` with the new header. - Adjust `AppBackButton` to support transparent backgrounds.
380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
/**
|
||
* 设置页 SettingsScreen - Twitter/X 风格
|
||
* 威友 - 应用设置
|
||
* 采用扁平化设计,全宽布局,简洁分割线
|
||
*/
|
||
|
||
import React, { useMemo } from 'react';
|
||
import {
|
||
View,
|
||
StyleSheet,
|
||
TouchableOpacity,
|
||
Alert,
|
||
ScrollView,
|
||
} from 'react-native';
|
||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||
import { StatusBar } from 'expo-status-bar';
|
||
import { useRouter } from 'expo-router';
|
||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||
import Constants from 'expo-constants';
|
||
import {
|
||
useAppColors,
|
||
spacing,
|
||
fontSizes,
|
||
borderRadius,
|
||
useThemePreference,
|
||
useSetThemePreference,
|
||
type AppColors,
|
||
} from '../../theme';
|
||
import { useAuthStore } from '../../stores';
|
||
import { Text, SimpleHeader } from '../../components/common';
|
||
import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||
import * as hrefs from '../../navigation/hrefs';
|
||
|
||
const APP_VERSION = Constants.expoConfig?.version || '1.0.0';
|
||
|
||
interface SettingsItem {
|
||
key: string;
|
||
title: string;
|
||
icon: string;
|
||
onPress?: () => void;
|
||
showArrow?: boolean;
|
||
danger?: boolean;
|
||
subtitle?: string;
|
||
}
|
||
|
||
const SETTINGS_GROUPS_BASE = [
|
||
{
|
||
title: '账号与安全',
|
||
items: [
|
||
{ key: 'edit_profile', title: '编辑资料', icon: 'account-edit-outline', showArrow: true },
|
||
{ key: 'verification', title: '身份认证', icon: 'check-decagram', showArrow: true, subtitle: '获得认证标识,解锁更多功能' },
|
||
{ key: 'privacy', title: '隐私设置', icon: 'shield-account-outline', showArrow: true },
|
||
{ key: 'security', title: '账号安全', icon: 'lock-outline', showArrow: true },
|
||
{ key: 'blocked_users', title: '黑名单', icon: 'account-off-outline', showArrow: true },
|
||
{ key: 'account_deletion', title: '注销账号', icon: 'account-remove-outline', showArrow: true, danger: true },
|
||
],
|
||
},
|
||
{
|
||
title: '通知与通用',
|
||
items: [
|
||
{ key: 'notification_settings', title: '通知设置', icon: 'bell-cog-outline', showArrow: true, subtitle: '推送、震动、提示音' },
|
||
{ key: 'data_storage', title: '数据与存储', icon: 'database-outline', showArrow: true },
|
||
],
|
||
},
|
||
{
|
||
title: '关于与帮助',
|
||
items: [
|
||
{ key: 'about', title: '关于我们', icon: 'information-outline', showArrow: true, subtitle: `版本 ${APP_VERSION}` },
|
||
{ key: 'help', title: '帮助与反馈', icon: 'help-circle-outline', showArrow: true },
|
||
],
|
||
},
|
||
];
|
||
|
||
function createSettingsStyles(colors: AppColors) {
|
||
return StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: colors.background.default,
|
||
},
|
||
scrollContent: {
|
||
paddingVertical: spacing.sm,
|
||
},
|
||
groupContainer: {
|
||
marginBottom: spacing.xl,
|
||
backgroundColor: colors.background.paper,
|
||
borderTopWidth: StyleSheet.hairlineWidth,
|
||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||
borderColor: colors.divider,
|
||
},
|
||
groupHeader: {
|
||
paddingHorizontal: spacing.lg,
|
||
paddingVertical: spacing.sm,
|
||
backgroundColor: colors.background.default,
|
||
},
|
||
groupTitle: {
|
||
fontWeight: '600',
|
||
fontSize: fontSizes.sm,
|
||
color: colors.text.secondary,
|
||
},
|
||
settingItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
paddingVertical: 14,
|
||
paddingHorizontal: spacing.lg,
|
||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||
borderBottomColor: colors.divider,
|
||
backgroundColor: colors.background.paper,
|
||
},
|
||
settingItemLast: {
|
||
borderBottomWidth: 0,
|
||
},
|
||
settingItemLeft: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
flex: 1,
|
||
},
|
||
iconContainer: {
|
||
width: 32,
|
||
height: 32,
|
||
borderRadius: borderRadius.md,
|
||
backgroundColor: colors.background.default,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginRight: spacing.md,
|
||
},
|
||
dangerIconContainer: {
|
||
backgroundColor: colors.error.light + '20',
|
||
},
|
||
settingContent: {
|
||
flex: 1,
|
||
},
|
||
subtitle: {
|
||
marginTop: 2,
|
||
},
|
||
logoutButton: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
marginTop: spacing.lg,
|
||
marginBottom: spacing.xl,
|
||
paddingVertical: 14,
|
||
marginHorizontal: spacing.lg,
|
||
borderRadius: borderRadius.full,
|
||
gap: spacing.sm,
|
||
backgroundColor: colors.background.paper,
|
||
borderWidth: 1,
|
||
borderColor: colors.divider,
|
||
},
|
||
logoutText: {
|
||
fontWeight: '700',
|
||
},
|
||
footer: {
|
||
alignItems: 'center',
|
||
marginTop: spacing.md,
|
||
paddingBottom: spacing.xl,
|
||
},
|
||
copyright: {
|
||
marginTop: spacing.xs,
|
||
},
|
||
});
|
||
}
|
||
|
||
export const SettingsScreen: React.FC = () => {
|
||
const router = useRouter();
|
||
const { logout } = useAuthStore();
|
||
const insets = useSafeAreaInsets();
|
||
const { isMobile } = useResponsive();
|
||
const responsivePadding = useResponsiveSpacing({ xs: 0, sm: 0, md: 0, lg: 24, xl: 32 });
|
||
const colors = useAppColors();
|
||
const styles = useMemo(() => createSettingsStyles(colors), [colors]);
|
||
const themePreference = useThemePreference();
|
||
const setThemePreference = useSetThemePreference();
|
||
|
||
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
|
||
|
||
const settingsGroups = useMemo(() => {
|
||
const themeSubtitle =
|
||
themePreference === 'system' ? '跟随系统' : themePreference === 'dark' ? '深色' : '浅色';
|
||
return [
|
||
{
|
||
title: '个性化',
|
||
items: [
|
||
{
|
||
key: 'chat_settings',
|
||
title: '聊天设置',
|
||
icon: 'message-text-outline',
|
||
showArrow: true,
|
||
subtitle: '字号、主题、壁纸',
|
||
},
|
||
{
|
||
key: 'theme',
|
||
title: '主题模式',
|
||
icon: 'theme-light-dark',
|
||
showArrow: true,
|
||
subtitle: themeSubtitle,
|
||
},
|
||
],
|
||
},
|
||
...SETTINGS_GROUPS_BASE,
|
||
];
|
||
}, [themePreference]);
|
||
|
||
const handleItemPress = (key: string) => {
|
||
switch (key) {
|
||
case 'chat_settings':
|
||
router.push(hrefs.hrefProfileChatSettings());
|
||
break;
|
||
case 'theme': {
|
||
Alert.alert(
|
||
'主题模式',
|
||
'选择应用界面明暗',
|
||
[
|
||
{ text: '取消', style: 'cancel' },
|
||
{
|
||
text: '跟随系统',
|
||
onPress: () => {
|
||
void setThemePreference('system');
|
||
},
|
||
},
|
||
{
|
||
text: '浅色',
|
||
onPress: () => {
|
||
void setThemePreference('light');
|
||
},
|
||
},
|
||
{
|
||
text: '深色',
|
||
onPress: () => {
|
||
void setThemePreference('dark');
|
||
},
|
||
},
|
||
]
|
||
);
|
||
break;
|
||
}
|
||
|
||
case 'data_storage':
|
||
router.push(hrefs.hrefProfileDataStorage());
|
||
break;
|
||
case 'about':
|
||
router.push(hrefs.hrefProfileAbout());
|
||
break;
|
||
case 'help':
|
||
Alert.alert('帮助与反馈', '帮助中心即将上线!');
|
||
break;
|
||
|
||
case 'edit_profile':
|
||
router.push(hrefs.hrefProfileEdit());
|
||
break;
|
||
case 'verification':
|
||
router.push(hrefs.hrefProfileVerification());
|
||
break;
|
||
case 'notification_settings':
|
||
router.push(hrefs.hrefProfileNotifications());
|
||
break;
|
||
case 'blocked_users':
|
||
router.push(hrefs.hrefProfileBlocked());
|
||
break;
|
||
case 'security':
|
||
router.push(hrefs.hrefProfileSecurity());
|
||
break;
|
||
case 'privacy':
|
||
router.push(hrefs.hrefProfilePrivacySettings());
|
||
break;
|
||
case 'account_deletion':
|
||
router.push(hrefs.hrefProfileDeletion());
|
||
break;
|
||
case 'logout':
|
||
Alert.alert(
|
||
'退出登录',
|
||
'确定要退出登录吗?',
|
||
[
|
||
{ text: '取消', style: 'cancel' },
|
||
{
|
||
text: '确定',
|
||
style: 'destructive',
|
||
onPress: async () => {
|
||
await logout();
|
||
router.replace(hrefs.hrefAuthLogin());
|
||
},
|
||
},
|
||
]
|
||
);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
};
|
||
|
||
const renderSettingItem = (item: SettingsItem, index: number, total: number) => (
|
||
<TouchableOpacity
|
||
key={item.key}
|
||
style={[
|
||
styles.settingItem,
|
||
index === total - 1 && styles.settingItemLast,
|
||
]}
|
||
onPress={() => (item.onPress ? item.onPress() : handleItemPress(item.key))}
|
||
activeOpacity={0.7}
|
||
>
|
||
<View style={styles.settingItemLeft}>
|
||
<View style={[styles.iconContainer, item.danger && styles.dangerIconContainer]}>
|
||
<MaterialCommunityIcons
|
||
name={item.icon as any}
|
||
size={20}
|
||
color={item.danger ? colors.error.main : colors.text.secondary}
|
||
/>
|
||
</View>
|
||
<View style={styles.settingContent}>
|
||
<Text variant="body" color={item.danger ? colors.error.main : colors.text.primary} style={{ fontWeight: '500' }}>
|
||
{item.title}
|
||
</Text>
|
||
{item.subtitle && (
|
||
<Text variant="caption" color={colors.text.hint} style={styles.subtitle}>
|
||
{item.subtitle}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
</View>
|
||
{item.showArrow && (
|
||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||
)}
|
||
</TouchableOpacity>
|
||
);
|
||
|
||
const renderGroup = (group: (typeof settingsGroups)[0]) => (
|
||
<View key={group.title}>
|
||
<View style={styles.groupHeader}>
|
||
<Text variant="caption" style={styles.groupTitle}>
|
||
{group.title}
|
||
</Text>
|
||
</View>
|
||
<View style={styles.groupContainer}>
|
||
{group.items.map((item, index) => renderSettingItem(item, index, group.items.length))}
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
const renderLogoutButton = () => (
|
||
<TouchableOpacity
|
||
style={styles.logoutButton}
|
||
onPress={() => handleItemPress('logout')}
|
||
activeOpacity={0.8}
|
||
>
|
||
<MaterialCommunityIcons name="logout-variant" size={18} color={colors.error.main} />
|
||
<Text variant="body" color={colors.error.main} style={styles.logoutText}>
|
||
退出登录
|
||
</Text>
|
||
</TouchableOpacity>
|
||
);
|
||
|
||
const renderContent = () => (
|
||
<>
|
||
{settingsGroups.map((group) => renderGroup(group))}
|
||
{renderLogoutButton()}
|
||
<View style={styles.footer}>
|
||
<Text variant="caption" color={colors.text.hint}>
|
||
威友 v{APP_VERSION}
|
||
</Text>
|
||
<Text variant="caption" color={colors.text.hint} style={styles.copyright}>
|
||
© 2026 青春之旅电子信息科技(威海)有限公司
|
||
</Text>
|
||
</View>
|
||
</>
|
||
);
|
||
|
||
return (
|
||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||
<StatusBar style="auto" />
|
||
<SimpleHeader title="设置" onBack={() => router.back()} />
|
||
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
|
||
{renderContent()}
|
||
</ScrollView>
|
||
</SafeAreaView>
|
||
);
|
||
};
|
||
|
||
export default SettingsScreen;
|