<think>Let me analyze the staged changes to generate a proper conventional commit message.
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 3m16s
Frontend CI / ota-android (push) Successful in 11m42s
Frontend CI / build-android-apk (push) Successful in 59m5s

Looking at the changes:

1. **New files**:
   - `app/(app)/(tabs)/profile/account-deletion.tsx` - account deletion screen
   - `app/(app)/(tabs)/profile/privacy-settings.tsx` - privacy settings screen
   - `src/screens/profile/AccountDeletionScreen.tsx` - account deletion implementation
   - `src/screens/profile/PrivacySettingsScreen.tsx` - privacy settings implementation

2. **Modified files**:
   - Database changes: `LocalDataSource.ts`, `DatabaseManager.ts`, `DatabaseConfig.ts`, `MigrationManager.ts` - refactoring database layer (removing LockManager, simplified initialization, added retry logic)
   - Auth store: `authStore.ts` - changed `initDatabase` to `switchDatabase`
   - Auth service: `authService.ts` - added privacy settings and account deletion methods
   - DTOs: `dto.ts` - added PrivacySettingsDTO, DeletionStatusDTO, VoteOptionDTO, VoteResultDTO types
   - Navigation: `hrefs.ts` - added new href functions for privacy settings and account deletion
   - Profile screens: `SettingsScreen.tsx`, `index.ts`, `_layout.tsx` - added new menu items and routes
   - Various message screens - added keyboard handling props
   - `app/_layout.tsx` - added CSS for mobile touch handling
   - `CreatePostScreen.tsx` - fixed vote_options format
   - `PostDetailScreen.tsx` - added type annotations for vote handling
   - `MediaCacheManager.ts` - updated to use new expo-file-system API
   - `VerificationSettingsScreen.tsx` - fixed type safety
   - `AccountSecurityScreen.tsx` - styling refactoring

This is a multi-faceted change involving:

Database refactoring, new privacy/account deletion features, bug fixes, and UI improvements. The database changes appear to be refactoring without new functionality. The most significant changes are the privacy settings and account deletion features, plus bug fixes like the vote options format and mobile touch handling.
</think>

feat(profile): add privacy settings and account deletion screens

Add privacy settings and account deletion functionality with new screens and APIs. Refactor database initialization to use switchDatabase for user switching. Fix vote options format in post creation and add type safety to vote handling. Improve mobile touch handling in layout and message screens. Update media cache to use new expo-file-system API.

BREAKING CHANGE: Database initialization now uses switchDatabase instead of initDatabase for user switching
This commit is contained in:
lafay
2026-04-08 16:48:19 +08:00
parent 96a5207cf8
commit be8f6de8cf
28 changed files with 1092 additions and 420 deletions

View File

@@ -0,0 +1,410 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Alert,
ScrollView,
StyleSheet,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
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 { Text } from '../../components/common';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
import { useAuthStore } from '../../stores';
import type { DeletionStatusDTO } from '../../types/dto';
import * as hrefs from '../../navigation/hrefs';
const CONTENT_MAX_WIDTH = 720;
// 胡萝卜橙主题色
const THEME_COLORS = {
primary: '#FF6B35',
primaryLight: '#FF8C5A',
primaryDark: '#E55A2B',
};
function createAccountDeletionStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.paper,
},
loadingWrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
scrollContent: {
paddingVertical: spacing.lg,
},
content: {
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
section: {
marginBottom: spacing['2xl'],
},
// 警告卡片 - 扁平化风格
warningCard: {
backgroundColor: colors.error.light + '20',
borderRadius: 14,
padding: spacing.lg,
marginHorizontal: spacing['2xl'],
marginBottom: spacing.xl,
},
warningTitle: {
fontWeight: '700',
fontSize: fontSizes.md,
marginBottom: spacing.sm,
color: colors.error.main,
},
warningText: {
color: colors.error.dark,
marginBottom: spacing.xs,
fontSize: fontSizes.sm,
},
// 状态卡片 - 扁平化风格
statusCard: {
backgroundColor: colors.warning.light + '30',
borderRadius: 14,
padding: spacing.lg,
marginHorizontal: spacing['2xl'],
marginBottom: spacing.xl,
},
statusTitle: {
fontWeight: '700',
fontSize: fontSizes.md,
marginBottom: spacing.sm,
color: colors.warning.dark,
},
daysText: {
fontSize: 32,
fontWeight: '700',
color: colors.warning.dark,
textAlign: 'center',
marginVertical: spacing.md,
},
// 内容区域
sectionContent: {
marginHorizontal: spacing['2xl'],
marginBottom: spacing.xl,
},
sectionTitle: {
fontWeight: '600',
fontSize: fontSizes.sm,
color: colors.text.secondary,
textTransform: 'uppercase',
letterSpacing: 0.5,
marginBottom: spacing.md,
},
listItem: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: spacing.sm,
},
listBullet: {
marginRight: spacing.sm,
marginTop: 2,
},
// 输入框 - 扁平化风格
input: {
backgroundColor: '#F5F5F7',
borderRadius: 14,
padding: spacing.md,
fontSize: 16,
color: colors.text.primary,
height: 56,
},
// 按钮行
buttonRow: {
flexDirection: 'row',
gap: spacing.md,
marginTop: spacing.lg,
marginHorizontal: spacing['2xl'],
},
// 扁平化按钮
primaryButton: {
flex: 1,
height: 56,
borderRadius: 14,
backgroundColor: THEME_COLORS.primary,
alignItems: 'center',
justifyContent: 'center',
},
primaryButtonText: {
color: '#fff',
fontSize: fontSizes.md,
fontWeight: '600',
},
secondaryButton: {
flex: 1,
height: 56,
borderRadius: 14,
backgroundColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1.5,
borderColor: colors.divider,
},
secondaryButtonText: {
color: colors.text.primary,
fontSize: fontSizes.md,
fontWeight: '600',
},
dangerButton: {
flex: 1,
height: 56,
borderRadius: 14,
backgroundColor: colors.error.main,
alignItems: 'center',
justifyContent: 'center',
},
dangerButtonText: {
color: '#fff',
fontSize: fontSizes.md,
fontWeight: '600',
},
buttonDisabled: {
opacity: 0.6,
},
cancelButton: {
marginTop: spacing.lg,
marginHorizontal: spacing['2xl'],
},
});
}
export const AccountDeletionScreen: React.FC = () => {
const colors = useAppColors();
const styles = useMemo(() => createAccountDeletionStyles(colors), [colors]);
const router = useRouter();
const { isMobile } = useResponsive();
const insets = useSafeAreaInsets();
const { logout } = useAuthStore();
const [status, setStatus] = useState<DeletionStatusDTO | null>(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [password, setPassword] = useState('');
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
const loadStatus = useCallback(async () => {
try {
const result = await authService.getDeletionStatus();
setStatus(result);
} catch (error) {
console.error('加载注销状态失败:', error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadStatus();
}, [loadStatus]);
const handleRequestDeletion = useCallback(async () => {
if (!password.trim()) {
Alert.alert('提示', '请输入密码确认');
return;
}
Alert.alert(
'确认注销',
'注销后您的账号将在90天后永久删除。在此期间您可以通过重新登录来取消注销。确定要继续吗',
[
{ text: '取消', style: 'cancel' },
{
text: '确定注销',
style: 'destructive',
onPress: async () => {
setSubmitting(true);
try {
const result = await authService.requestAccountDeletion(password);
if (result) {
setStatus(result);
setPassword('');
Alert.alert('已申请注销', '您的账号将在90天后永久删除。在此期间登录即可取消注销。');
await logout();
router.replace(hrefs.hrefAuthLogin());
} else {
Alert.alert('失败', '密码错误或申请失败,请重试');
}
} catch (error) {
console.error('申请注销失败:', error);
Alert.alert('失败', '申请注销失败,请稍后重试');
} finally {
setSubmitting(false);
}
},
},
]
);
}, [password, logout, router]);
const handleCancelDeletion = useCallback(async () => {
Alert.alert(
'取消注销',
'确定要取消注销申请吗?您的账号将恢复正常使用。',
[
{ text: '继续注销', style: 'cancel' },
{
text: '确定取消',
onPress: async () => {
setSubmitting(true);
try {
const ok = await authService.cancelAccountDeletion();
if (ok) {
setStatus({ is_pending_deletion: false });
Alert.alert('已取消', '注销申请已取消,您的账号已恢复正常使用');
} else {
Alert.alert('失败', '取消注销失败,请稍后重试');
}
} catch (error) {
console.error('取消注销失败:', error);
Alert.alert('失败', '取消注销失败,请稍后重试');
} finally {
setSubmitting(false);
}
},
},
]
);
}, []);
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<View style={styles.loadingWrap}>
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
<View style={styles.content}>
{status?.is_pending_deletion ? (
<View style={styles.section}>
<View style={styles.statusCard}>
<Text variant="body" style={styles.statusTitle}>
</Text>
<Text variant="body" color={colors.text.secondary}>
</Text>
<Text variant="body" style={styles.daysText}>
{status.cool_down_days || 90}
</Text>
<Text variant="caption" color={colors.text.secondary}>
</Text>
<TouchableOpacity
style={[styles.secondaryButton, styles.cancelButton, submitting && styles.buttonDisabled]}
onPress={handleCancelDeletion}
disabled={submitting}
>
{submitting ? (
<ActivityIndicator size="small" color={colors.text.primary} />
) : (
<Text style={styles.secondaryButtonText}></Text>
)}
</TouchableOpacity>
</View>
</View>
) : (
<View style={styles.section}>
{/* 警告卡片 */}
<View style={styles.warningCard}>
<Text variant="body" style={styles.warningTitle}>
</Text>
<Text variant="body" style={styles.warningText}>
90
</Text>
<Text variant="body" style={styles.warningText}>
</Text>
</View>
{/* 注销说明 */}
<View style={styles.sectionContent}>
<Text style={styles.sectionTitle}></Text>
<View style={styles.listItem}>
<MaterialCommunityIcons name="close-circle" size={16} color={colors.error.main} style={styles.listBullet} />
<Text variant="body" color={colors.text.secondary}>
</Text>
</View>
<View style={styles.listItem}>
<MaterialCommunityIcons name="information" size={16} color={colors.warning.main} style={styles.listBullet} />
<Text variant="body" color={colors.text.secondary}>
</Text>
</View>
<View style={styles.listItem}>
<MaterialCommunityIcons name="close-circle" size={16} color={colors.error.main} style={styles.listBullet} />
<Text variant="body" color={colors.text.secondary}>
</Text>
</View>
<View style={styles.listItem}>
<MaterialCommunityIcons name="close-circle" size={16} color={colors.error.main} style={styles.listBullet} />
<Text variant="body" color={colors.text.secondary}>
</Text>
</View>
</View>
{/* 密码确认 */}
<View style={styles.sectionContent}>
<Text style={styles.sectionTitle}></Text>
<TextInput
style={styles.input}
placeholder="请输入密码"
placeholderTextColor={colors.text.hint}
secureTextEntry
value={password}
onChangeText={setPassword}
/>
</View>
{/* 按钮行 */}
<View style={styles.buttonRow}>
<TouchableOpacity
style={styles.secondaryButton}
onPress={() => router.back()}
>
<Text style={styles.secondaryButtonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.dangerButton, submitting && styles.buttonDisabled]}
onPress={handleRequestDeletion}
disabled={submitting || !password.trim()}
>
{submitting ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.dangerButtonText}></Text>
)}
</TouchableOpacity>
</View>
</View>
)}
</View>
</ScrollView>
</SafeAreaView>
);
};
export default AccountDeletionScreen;

View File

@@ -213,12 +213,13 @@ export const AccountSecurityScreen: React.FC = () => {
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
<View style={styles.content}>
{/* 邮箱验证分组 */}
<View style={styles.groupHeader}>
<Text variant="caption" style={styles.groupTitle}>
</Text>
</View>
<View style={styles.card}>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text variant="caption" style={styles.sectionTitle}>
</Text>
</View>
{/* 状态显示 */}
<View style={styles.statusRow}>
<Text style={styles.statusLabel}></Text>
@@ -285,12 +286,13 @@ export const AccountSecurityScreen: React.FC = () => {
</View>
{/* 修改密码分组 */}
<View style={styles.groupHeader}>
<Text variant="caption" style={styles.groupTitle}>
</Text>
</View>
<View style={styles.card}>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text variant="caption" style={styles.sectionTitle}>
</Text>
</View>
{/* 当前密码 */}
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
@@ -382,7 +384,7 @@ function createAccountSecurityStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: colors.background.paper,
},
scrollContent: {
paddingVertical: spacing.lg,
@@ -392,37 +394,33 @@ function createAccountSecurityStyles(colors: AppColors) {
alignSelf: 'center',
width: '100%',
},
// 分组标题
groupHeader: {
// 分组样式 - 扁平化风格
section: {
marginBottom: spacing['2xl'],
},
sectionHeader: {
paddingHorizontal: spacing['2xl'],
marginBottom: spacing.sm,
marginTop: spacing.sm,
},
groupTitle: {
sectionTitle: {
fontWeight: '600',
fontSize: fontSizes.sm,
color: colors.text.secondary,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// 卡片样式 - 统一
card: {
backgroundColor: '#F5F5F7',
borderRadius: 16,
padding: 6,
marginBottom: spacing['2xl'],
marginHorizontal: spacing['2xl'],
},
// 状态显示
// 状态显示 - 扁平化
statusRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: spacing.md,
paddingHorizontal: spacing['2xl'],
paddingVertical: spacing.md,
marginBottom: spacing.md,
backgroundColor: '#fff',
borderRadius: 12,
backgroundColor: colors.background.default,
borderRadius: 14,
marginHorizontal: spacing['2xl'],
},
statusLabel: {
fontSize: 15,
@@ -449,15 +447,16 @@ function createAccountSecurityStyles(colors: AppColors) {
statusUnverifiedText: {
color: '#E65100',
},
// 输入框样式 - 统一
// 输入框样式 - 扁平化风格,与登录页一致
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
backgroundColor: '#F5F5F7',
borderRadius: 14,
paddingHorizontal: spacing.md,
paddingHorizontal: spacing.lg,
height: 56,
marginBottom: spacing.sm,
marginHorizontal: spacing['2xl'],
marginBottom: spacing.md,
},
inputIcon: {
marginRight: spacing.sm,
@@ -473,13 +472,15 @@ function createAccountSecurityStyles(colors: AppColors) {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
marginBottom: spacing.sm,
marginHorizontal: spacing['2xl'],
marginBottom: spacing.md,
},
codeInput: {
flex: 1,
marginHorizontal: 0,
marginBottom: 0,
},
// 发送验证码按钮
// 发送验证码按钮 - 扁平化
sendCodeButton: {
height: 56,
minWidth: 110,
@@ -494,7 +495,7 @@ function createAccountSecurityStyles(colors: AppColors) {
fontSize: fontSizes.sm,
fontWeight: '600',
},
// 主按钮样式 - 统一
// 主按钮样式 - 扁平化
primaryButton: {
height: 56,
borderRadius: 14,
@@ -502,6 +503,7 @@ function createAccountSecurityStyles(colors: AppColors) {
alignItems: 'center',
justifyContent: 'center',
marginTop: spacing.xs,
marginHorizontal: spacing['2xl'],
},
primaryButtonText: {
color: '#fff',

View File

@@ -0,0 +1,239 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Alert,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
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 { Text } from '../../components/common';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
import type { PrivacySettingsDTO, VisibilityLevel } from '../../types/dto';
const CONTENT_MAX_WIDTH = 720;
// 胡萝卜橙主题色
const THEME_COLORS = {
primary: '#FF6B35',
primaryLight: '#FF8C5A',
primaryDark: '#E55A2B',
};
const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string; description: string }[] = [
{ value: 'everyone', label: '所有人可见', description: '任何人都可查看' },
{ value: 'following', label: '仅关注的人可见', description: '只有你关注的人可查看' },
{ value: 'self', label: '仅自己可见', description: '只有自己可查看' },
];
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: '谁可以查看你的收藏列表' },
];
function createPrivacySettingsStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.paper,
},
loadingWrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
scrollContent: {
paddingVertical: spacing.lg,
},
content: {
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
section: {
marginBottom: spacing['2xl'],
},
sectionHeader: {
paddingHorizontal: spacing['2xl'],
marginBottom: spacing.sm,
marginTop: spacing.sm,
},
sectionTitle: {
fontWeight: '600',
fontSize: fontSizes.sm,
color: colors.text.secondary,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// 隐私设置项 - 扁平化卡片风格
item: {
backgroundColor: '#F5F5F7',
borderRadius: 14,
padding: spacing.lg,
marginHorizontal: spacing['2xl'],
marginBottom: spacing.md,
},
itemHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: spacing.sm,
},
itemTitle: {
fontWeight: '600',
fontSize: fontSizes.md,
color: colors.text.primary,
},
itemDescription: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
marginBottom: spacing.md,
},
optionsRow: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
},
// 选项按钮 - 扁平化风格
optionButton: {
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
borderWidth: 1,
borderColor: colors.divider,
backgroundColor: colors.background.paper,
},
optionButtonActive: {
backgroundColor: THEME_COLORS.primary,
borderColor: THEME_COLORS.primary,
},
optionText: {
fontSize: 13,
color: colors.text.primary,
},
optionTextActive: {
color: '#fff',
fontWeight: '500',
},
});
}
export const PrivacySettingsScreen: React.FC = () => {
const colors = useAppColors();
const styles = useMemo(() => createPrivacySettingsStyles(colors), [colors]);
const router = useRouter();
const { isMobile } = useResponsive();
const insets = useSafeAreaInsets();
const [settings, setSettings] = useState<PrivacySettingsDTO | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
const loadSettings = useCallback(async () => {
try {
const result = await authService.getPrivacySettings();
if (result) {
setSettings(result);
}
} catch (error) {
console.error('加载隐私设置失败:', error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadSettings();
}, [loadSettings]);
const updateSetting = useCallback(async (key: keyof PrivacySettingsDTO, value: VisibilityLevel) => {
if (!settings) return;
const newSettings = { ...settings, [key]: value };
setSettings(newSettings);
setSaving(true);
try {
const ok = await authService.updatePrivacySettings({
[key]: value,
});
if (!ok) {
Alert.alert('失败', '保存设置失败,请稍后重试');
setSettings(settings);
}
} catch (error) {
console.error('保存隐私设置失败:', error);
setSettings(settings);
} finally {
setSaving(false);
}
}, [settings]);
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<View style={styles.loadingWrap}>
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
<View style={styles.content}>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text variant="caption" style={styles.sectionTitle}>
</Text>
</View>
{PRIVACY_ITEMS.map((item) => (
<View key={item.key} style={styles.item}>
<Text variant="body" style={styles.itemTitle}>
{item.title}
</Text>
<Text variant="caption" color={colors.text.secondary} style={styles.itemDescription}>
{item.description}
</Text>
<View style={styles.optionsRow}>
{VISIBILITY_OPTIONS.map((option) => {
const isActive = settings?.[item.key] === option.value;
return (
<TouchableOpacity
key={option.value}
style={[styles.optionButton, isActive && styles.optionButtonActive]}
onPress={() => updateSetting(item.key, option.value)}
activeOpacity={0.7}
>
<Text
variant="caption"
style={[styles.optionText, isActive ? styles.optionTextActive : {}]}
>
{option.label}
</Text>
</TouchableOpacity>
);
})}
</View>
</View>
))}
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
export default PrivacySettingsScreen;

View File

@@ -56,6 +56,7 @@ const SETTINGS_GROUPS_BASE = [
{ 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 },
],
},
{
@@ -264,6 +265,12 @@ export const SettingsScreen: React.FC = () => {
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(
'退出登录',

View File

@@ -105,7 +105,8 @@ export const VerificationSettingsScreen: React.FC = () => {
);
}
const statusConfig = STATUS_CONFIG[status?.verification_status || 'not_submitted'];
const statusKey = (status?.verification_status || 'not_submitted') as keyof typeof STATUS_CONFIG;
const statusConfig = STATUS_CONFIG[statusKey];
const identityText = IDENTITY_TEXT[status?.identity || 'general'];
return (

View File

@@ -19,6 +19,8 @@ export { TermsOfServiceScreen } from './TermsOfServiceScreen';
export { PrivacyPolicyScreen } from './PrivacyPolicyScreen';
export { VerificationSettingsScreen } from './VerificationSettingsScreen';
export { DataStorageScreen } from './DataStorageScreen';
export { PrivacySettingsScreen } from './PrivacySettingsScreen';
export { AccountDeletionScreen } from './AccountDeletionScreen';
// 导出 Hook 供需要自定义的场景使用
export { useUserProfile, createSharedProfileStyles, TABS, TAB_ICONS } from './useUserProfile';