diff --git a/src/screens/profile/PrivacySettingsScreen.tsx b/src/screens/profile/PrivacySettingsScreen.tsx index d77d775..2bae9c3 100644 --- a/src/screens/profile/PrivacySettingsScreen.tsx +++ b/src/screens/profile/PrivacySettingsScreen.tsx @@ -12,31 +12,43 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { authService } from '../../services'; -import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme'; +import { + spacing, + borderRadius, + fontSizes, + useAppColors, + type AppColors, +} from '../../theme'; import { SimpleHeader, Text } from '../../components/common'; import { useResponsive, useResponsiveSpacing } from '../../hooks'; import type { PrivacySettingsDTO, VisibilityLevel } from '../../types/dto'; +import * as hrefs from '../../navigation/hrefs'; const CONTENT_MAX_WIDTH = 720; -const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string }[] = [ - { value: 'everyone', label: '所有人可见' }, - { value: 'following', label: '仅关注的人可见' }, - { value: 'self', label: '仅自己可见' }, +const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string; icon: string }[] = [ + { value: 'everyone', label: '所有人可见', icon: 'earth' }, + { value: 'following', label: '仅关注的人可见', icon: 'account-group-outline' }, + { value: 'self', label: '仅自己可见', icon: 'lock-outline' }, ]; -const PRIVACY_ITEMS: { key: keyof PrivacySettingsDTO; title: string; description: string }[] = [ - { key: 'followers_visibility', title: '粉丝列表', description: '谁可以查看你的粉丝列表' }, - { key: 'following_visibility', title: '关注列表', description: '谁可以查看你的关注列表' }, - { key: 'posts_visibility', title: '帖子列表', description: '谁可以查看你的帖子列表' }, - { key: 'favorites_visibility', title: '收藏列表', description: '谁可以查看你的收藏列表' }, +const PRIVACY_ITEMS: { key: keyof PrivacySettingsDTO; title: string; description: string; icon: string }[] = [ + { key: 'followers_visibility', title: '粉丝列表', description: '谁可以查看你的粉丝列表', icon: 'account-group' }, + { key: 'following_visibility', title: '关注列表', description: '谁可以查看你的关注列表', icon: 'account-heart' }, + { key: 'posts_visibility', title: '帖子列表', description: '谁可以查看你的帖子列表', icon: 'text-box-outline' }, + { key: 'favorites_visibility', title: '收藏列表', description: '谁可以查看你的收藏列表', icon: 'star-outline' }, +]; + +const PRIVACY_NAV_ITEMS: { key: string; title: string; description: string; icon: string; danger?: boolean }[] = [ + { key: 'blocked_users', title: '黑名单', description: '管理你屏蔽的用户', icon: 'account-off-outline' }, + { key: 'account_deletion', title: '注销账号', description: '永久删除你的账号', icon: 'account-remove-outline', danger: true }, ]; function createPrivacySettingsStyles(colors: AppColors) { return StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.background.paper, + backgroundColor: colors.background.default, }, loadingWrap: { flex: 1, @@ -44,56 +56,68 @@ function createPrivacySettingsStyles(colors: AppColors) { justifyContent: 'center', }, scrollContent: { - paddingVertical: spacing.lg, + paddingVertical: spacing.md, }, content: { maxWidth: CONTENT_MAX_WIDTH, alignSelf: 'center', width: '100%', }, - section: { - marginBottom: spacing['2xl'], + groupHeader: { + paddingHorizontal: spacing.lg, + paddingVertical: spacing.sm, }, - sectionHeader: { - paddingHorizontal: spacing['2xl'], - marginBottom: spacing.sm, - marginTop: spacing.sm, - }, - sectionTitle: { + groupTitle: { fontWeight: '600', fontSize: fontSizes.sm, color: colors.text.secondary, - textTransform: 'uppercase', - letterSpacing: 0.5, }, - // 扁平式布局:整行横向排列,底部边框分隔 + groupContainer: { + backgroundColor: colors.background.paper, + borderTopWidth: StyleSheet.hairlineWidth, + borderBottomWidth: StyleSheet.hairlineWidth, + borderColor: colors.divider, + overflow: 'visible', + }, item: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', - paddingVertical: spacing.lg, - marginHorizontal: spacing['2xl'], + paddingVertical: 14, + paddingHorizontal: spacing.lg, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.divider, + backgroundColor: colors.background.paper, }, itemLast: { borderBottomWidth: 0, }, - itemTextWrap: { + itemLeft: { + flexDirection: 'row', + alignItems: 'center', flex: 1, + }, + iconContainer: { + width: 34, + height: 34, + borderRadius: borderRadius.md, + backgroundColor: colors.background.default, + justifyContent: 'center', + alignItems: 'center', marginRight: spacing.md, }, + dangerIconContainer: { + backgroundColor: colors.error.light + '20', + }, + itemContent: { + flex: 1, + }, itemTitle: { - fontWeight: '600', - fontSize: fontSizes.md, - color: colors.text.primary, - marginBottom: spacing.xs, + fontWeight: '500', }, itemDescription: { - fontSize: fontSizes.sm, - color: colors.text.secondary, + marginTop: 1, }, - // 无背景色的选择框,紧贴右侧 selectBox: { flexDirection: 'row', alignItems: 'center', @@ -104,10 +128,9 @@ function createPrivacySettingsStyles(colors: AppColors) { color: colors.text.secondary, marginRight: spacing.xs, }, - // 原位展开的下拉菜单 dropdownMenu: { position: 'absolute', - right: 0, + right: spacing.lg, top: '100%', marginTop: 4, backgroundColor: colors.background.paper, @@ -120,13 +143,17 @@ function createPrivacySettingsStyles(colors: AppColors) { shadowRadius: 4, elevation: 4, zIndex: 100, - minWidth: 160, + minWidth: 180, + overflow: 'hidden', }, dropdownOption: { + flexDirection: 'row', + alignItems: 'center', paddingHorizontal: spacing.md, - paddingVertical: spacing.sm, + paddingVertical: spacing.md, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.divider, + gap: spacing.sm, }, dropdownOptionLast: { borderBottomWidth: 0, @@ -139,6 +166,22 @@ function createPrivacySettingsStyles(colors: AppColors) { color: colors.primary.main, fontWeight: '600', }, + dropdownOptionIcon: { + width: 22, + alignItems: 'center', + }, + navItem: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 14, + paddingHorizontal: spacing.lg, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.divider, + backgroundColor: colors.background.paper, + }, + navItemLast: { + borderBottomWidth: 0, + }, }); } @@ -152,7 +195,7 @@ export const PrivacySettingsScreen: React.FC = () => { const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [openPicker, setOpenPicker] = useState(null); - const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 }); + const responsivePadding = useResponsiveSpacing({ xs: 0, sm: 0, md: 0, lg: 24, xl: 32 }); const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md; @@ -216,42 +259,52 @@ export const PrivacySettingsScreen: React.FC = () => { router.back()} /> - + {/* 可见性设置分组 */} + + + 可见性设置 + + + {PRIVACY_ITEMS.map((item, index) => { const isOpen = openPicker === item.key; + const isLast = index === PRIVACY_ITEMS.length - 1; return ( - - - - {item.title} - - - {item.description} - - - + setOpenPicker(isOpen ? null : item.key)} activeOpacity={0.7} > - - {getLabel(settings?.[item.key])} - - + + + + + + + {item.title} + + + {item.description} + + + + + + {getLabel(settings?.[item.key])} + + + - {/* 原位展开的下拉选项 */} {isOpen && ( {VISIBILITY_OPTIONS.map((option, optIndex) => { @@ -269,11 +322,18 @@ export const PrivacySettingsScreen: React.FC = () => { }} activeOpacity={0.7} > + + + {option.label} @@ -287,6 +347,49 @@ export const PrivacySettingsScreen: React.FC = () => { ); })} + + {/* 账号管理分组 */} + + + 账号管理 + + + + {PRIVACY_NAV_ITEMS.map((item, index) => ( + { + if (item.key === 'blocked_users') router.push(hrefs.hrefProfileBlocked()); + else if (item.key === 'account_deletion') router.push(hrefs.hrefProfileDeletion()); + }} + activeOpacity={0.7} + > + + + + + + + {item.title} + + + {item.description} + + + + + + ))} + diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx index 2075247..c5a3ea1 100644 --- a/src/screens/profile/SettingsScreen.tsx +++ b/src/screens/profile/SettingsScreen.tsx @@ -4,7 +4,7 @@ * 采用扁平化设计,全宽布局,简洁分割线 */ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { View, StyleSheet, @@ -34,6 +34,12 @@ import * as hrefs from '../../navigation/hrefs'; const APP_VERSION = Constants.expoConfig?.version || '1.0.0'; +const THEME_OPTIONS: { value: 'system' | 'light' | 'dark'; label: string }[] = [ + { value: 'system', label: '跟随系统' }, + { value: 'light', label: '浅色' }, + { value: 'dark', label: '深色' }, +]; + interface SettingsItem { key: string; title: string; @@ -42,6 +48,7 @@ interface SettingsItem { showArrow?: boolean; danger?: boolean; subtitle?: string; + isThemePicker?: boolean; } const SETTINGS_GROUPS_BASE = [ @@ -52,8 +59,6 @@ const SETTINGS_GROUPS_BASE = [ { 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 }, ], }, { @@ -87,6 +92,8 @@ function createSettingsStyles(colors: AppColors) { borderTopWidth: StyleSheet.hairlineWidth, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: colors.divider, + overflow: 'visible', + zIndex: 1, }, groupHeader: { paddingHorizontal: spacing.lg, @@ -134,6 +141,53 @@ function createSettingsStyles(colors: AppColors) { subtitle: { marginTop: 2, }, + // 无背景色的选择框,紧贴右侧 + selectBox: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: spacing.xs, + }, + selectBoxText: { + fontSize: fontSizes.sm, + color: colors.text.secondary, + marginRight: spacing.xs, + }, + // 原位展开的下拉菜单 + dropdownMenu: { + position: 'absolute', + right: 0, + top: '100%', + marginTop: 4, + backgroundColor: colors.background.paper, + borderRadius: borderRadius.md, + borderWidth: StyleSheet.hairlineWidth, + borderColor: colors.divider, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 4, + zIndex: 100, + minWidth: 120, + overflow: 'hidden', + }, + dropdownOption: { + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.divider, + }, + dropdownOptionLast: { + borderBottomWidth: 0, + }, + dropdownOptionText: { + fontSize: fontSizes.sm, + color: colors.text.primary, + }, + dropdownOptionTextActive: { + color: colors.primary.main, + fontWeight: '600', + }, logoutButton: { flexDirection: 'row', alignItems: 'center', @@ -172,12 +226,11 @@ export const SettingsScreen: React.FC = () => { const styles = useMemo(() => createSettingsStyles(colors), [colors]); const themePreference = useThemePreference(); const setThemePreference = useSetThemePreference(); + const [openPicker, setOpenPicker] = useState(null); const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md; const settingsGroups = useMemo(() => { - const themeSubtitle = - themePreference === 'system' ? '跟随系统' : themePreference === 'dark' ? '深色' : '浅色'; return [ { title: '个性化', @@ -193,8 +246,7 @@ export const SettingsScreen: React.FC = () => { key: 'theme', title: '主题模式', icon: 'theme-light-dark', - showArrow: true, - subtitle: themeSubtitle, + isThemePicker: true, }, ], }, @@ -208,31 +260,7 @@ export const SettingsScreen: React.FC = () => { 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'); - }, - }, - ] - ); + setOpenPicker(openPicker === 'theme' ? null : 'theme'); break; } @@ -289,12 +317,19 @@ export const SettingsScreen: React.FC = () => { } }; - const renderSettingItem = (item: SettingsItem, index: number, total: number) => ( + const getThemeLabel = (value: string) => + THEME_OPTIONS.find((o) => o.value === value)?.label ?? '跟随系统'; + + const renderSettingItem = (item: SettingsItem, index: number, total: number) => { + const isThemeOpen = item.isThemePicker && openPicker === 'theme'; + const themeLabel = item.isThemePicker ? getThemeLabel(themePreference) : undefined; + + return ( (item.onPress ? item.onPress() : handleItemPress(item.key))} activeOpacity={0.7} @@ -318,14 +353,64 @@ export const SettingsScreen: React.FC = () => { )} - {item.showArrow && ( - + {item.isThemePicker ? ( + + + {themeLabel} + + + {isThemeOpen && ( + + {THEME_OPTIONS.map((option, optIndex) => { + const isActive = themePreference === option.value; + return ( + { + void setThemePreference(option.value); + setOpenPicker(null); + }} + activeOpacity={0.7} + > + + {option.label} + + + ); + })} + + )} + + ) : ( + item.showArrow && ( + + ) )} - ); + ); + }; - const renderGroup = (group: (typeof settingsGroups)[0]) => ( - + const renderGroup = (group: (typeof settingsGroups)[0], groupIndex: number) => ( + i.isThemePicker && openPicker === 'theme') ? 10 : groupIndex }, + ]} + > {group.title} @@ -352,7 +437,7 @@ export const SettingsScreen: React.FC = () => { const renderContent = () => ( <> - {settingsGroups.map((group) => renderGroup(group))} + {settingsGroups.map((group, index) => renderGroup(group, index))} {renderLogoutButton()}