import React, { useEffect, useMemo, useState } from 'react';
import {
View,
StyleSheet,
TextInput,
TouchableOpacity,
ActivityIndicator,
ScrollView,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
import { Text } from '../../components/common';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
// 内容最大宽度
const CONTENT_MAX_WIDTH = 720;
import { authService, resolveAuthApiError } from '../../services/authService';
import { showPrompt } from '../../services/promptService';
import { useAuthStore } from '../../stores';
export const AccountSecurityScreen: React.FC = () => {
const colors = useAppColors();
const styles = useMemo(() => createAccountSecurityStyles(colors), [colors]);
const { isWideScreen, isMobile } = useResponsive();
const insets = useSafeAreaInsets();
const currentUser = useAuthStore((state) => state.currentUser);
const fetchCurrentUser = useAuthStore((state) => state.fetchCurrentUser);
// 底部间距,避免被 TabBar 遮挡
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
const [email, setEmail] = useState(currentUser?.email || '');
const [verificationCode, setVerificationCode] = useState('');
const [sendingCode, setSendingCode] = useState(false);
const [verifyingEmail, setVerifyingEmail] = useState(false);
const [countdown, setCountdown] = useState(0);
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [changePasswordCode, setChangePasswordCode] = useState('');
const [sendingChangePwdCode, setSendingChangePwdCode] = useState(false);
const [changePwdCountdown, setChangePwdCountdown] = useState(0);
const [updatingPassword, setUpdatingPassword] = useState(false);
useEffect(() => {
if (currentUser?.email) {
setEmail(currentUser.email);
}
}, [currentUser?.email]);
useEffect(() => {
if (countdown <= 0) {
return;
}
const timer = setInterval(() => {
setCountdown((prev) => (prev <= 1 ? 0 : prev - 1));
}, 1000);
return () => clearInterval(timer);
}, [countdown]);
useEffect(() => {
if (changePwdCountdown <= 0) {
return;
}
const timer = setInterval(() => {
setChangePwdCountdown((prev) => (prev <= 1 ? 0 : prev - 1));
}, 1000);
return () => clearInterval(timer);
}, [changePwdCountdown]);
const isEmailVerified = !!currentUser?.email_verified;
const emailStatusText = useMemo(() => {
if (!currentUser?.email) {
return '未绑定邮箱';
}
return isEmailVerified ? '已验证' : '未验证';
}, [currentUser?.email, isEmailVerified]);
const validateEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
const handleSendCode = async () => {
const targetEmail = email.trim();
if (!validateEmail(targetEmail)) {
showPrompt({ title: '提示', message: '请输入正确的邮箱地址', type: 'warning' });
return;
}
if (countdown > 0) {
return;
}
setSendingCode(true);
try {
const ok = await authService.sendCurrentUserEmailVerifyCode(targetEmail);
if (ok) {
setCountdown(60);
showPrompt({ title: '发送成功', message: '验证码已发送,请查收邮箱', type: 'success' });
}
} catch (error: any) {
showPrompt({ title: '发送失败', message: resolveAuthApiError(error, '验证码发送失败'), type: 'error' });
} finally {
setSendingCode(false);
}
};
const handleVerifyEmail = async () => {
const targetEmail = email.trim();
if (!validateEmail(targetEmail)) {
showPrompt({ title: '提示', message: '请输入正确的邮箱地址', type: 'warning' });
return;
}
if (!verificationCode.trim()) {
showPrompt({ title: '提示', message: '请输入验证码', type: 'warning' });
return;
}
setVerifyingEmail(true);
try {
const ok = await authService.verifyCurrentUserEmail({
email: targetEmail,
verification_code: verificationCode.trim(),
});
if (ok) {
setVerificationCode('');
await fetchCurrentUser();
showPrompt({ title: '验证成功', message: '邮箱已完成验证', type: 'success' });
}
} catch (error: any) {
showPrompt({ title: '验证失败', message: resolveAuthApiError(error, '邮箱验证失败'), type: 'error' });
} finally {
setVerifyingEmail(false);
}
};
const handleChangePassword = async () => {
if (!oldPassword) {
showPrompt({ title: '提示', message: '请输入当前密码', type: 'warning' });
return;
}
if (!changePasswordCode.trim()) {
showPrompt({ title: '提示', message: '请输入邮箱验证码', type: 'warning' });
return;
}
if (newPassword.length < 6) {
showPrompt({ title: '提示', message: '新密码至少 6 位', type: 'warning' });
return;
}
if (newPassword !== confirmPassword) {
showPrompt({ title: '提示', message: '两次输入的新密码不一致', type: 'warning' });
return;
}
setUpdatingPassword(true);
try {
const ok = await authService.changePassword(oldPassword, newPassword, changePasswordCode.trim());
if (ok) {
setOldPassword('');
setNewPassword('');
setConfirmPassword('');
setChangePasswordCode('');
showPrompt({ title: '修改成功', message: '密码已更新', type: 'success' });
} else {
showPrompt({ title: '修改失败', message: '请检查当前密码后重试', type: 'error' });
}
} catch (error: any) {
showPrompt({ title: '修改失败', message: resolveAuthApiError(error, '请稍后重试'), type: 'error' });
} finally {
setUpdatingPassword(false);
}
};
const handleSendChangePasswordCode = async () => {
if (changePwdCountdown > 0) {
return;
}
setSendingChangePwdCode(true);
try {
const ok = await authService.sendChangePasswordCode();
if (ok) {
setChangePwdCountdown(60);
showPrompt({ title: '发送成功', message: '改密验证码已发送到你的邮箱', type: 'success' });
}
} catch (error: any) {
showPrompt({ title: '发送失败', message: resolveAuthApiError(error, '验证码发送失败'), type: 'error' });
} finally {
setSendingChangePwdCode(false);
}
};
const content = (
<>
邮箱验证
当前状态
{emailStatusText}
0) && styles.buttonDisabled]}
onPress={handleSendCode}
disabled={sendingCode || countdown > 0}
>
{sendingCode ? (
) : (
{countdown > 0 ? `${countdown}s` : '发送验证码'}
)}
{verifyingEmail ? (
) : (
验证邮箱
)}
修改密码
0) && styles.buttonDisabled]}
onPress={handleSendChangePasswordCode}
disabled={sendingChangePwdCode || changePwdCountdown > 0}
>
{sendingChangePwdCode ? (
) : (
{changePwdCountdown > 0 ? `${changePwdCountdown}s` : '发送验证码'}
)}
{updatingPassword ? (
) : (
更新密码
)}
>
);
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
return (
{content}
);
};
function createAccountSecurityStyles(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,
padding: spacing.md,
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
statusRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.md,
},
statusBadge: {
paddingHorizontal: spacing.sm,
paddingVertical: 4,
borderRadius: borderRadius.sm,
},
statusVerified: {
backgroundColor: colors.success.light + '40',
},
statusUnverified: {
backgroundColor: colors.warning.light + '40',
},
statusVerifiedText: {
color: colors.success.dark,
},
statusUnverifiedText: {
color: colors.warning.dark,
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: borderRadius.lg,
borderWidth: 1,
borderColor: colors.divider,
paddingHorizontal: spacing.md,
height: 48,
marginBottom: spacing.sm,
},
inputIcon: {
marginRight: spacing.sm,
},
input: {
flex: 1,
color: colors.text.primary,
fontSize: fontSizes.md,
},
codeRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
marginBottom: spacing.sm,
},
codeInput: {
flex: 1,
marginBottom: 0,
},
sendCodeButton: {
height: 48,
minWidth: 110,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: spacing.sm,
},
sendCodeButtonText: {
color: colors.text.inverse,
fontSize: fontSizes.sm,
fontWeight: '600',
},
primaryButton: {
height: 48,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
marginTop: spacing.xs,
},
primaryButtonText: {
color: colors.text.inverse,
fontSize: fontSizes.md,
fontWeight: '700',
},
buttonDisabled: {
opacity: 0.6,
},
});
}
export default AccountSecurityScreen;