refactor(database): migrate to new modular database layer and unify data access
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m44s
Frontend CI / ota-android (push) Successful in 12m51s
Frontend CI / build-android-apk (push) Successful in 1h1m26s

- Remove legacy database.ts, LocalDataSource.ts, and MessageRepository.ts
- Create new src/database/ module with messageRepository, userCacheRepository, conversationRepository, and groupCacheRepository
- Update all consumers to import from @/database instead of services/database
- Add web platform blur handling for modal components to fix focus issues
- Flatten SystemMessageItem and NotificationsScreen styles for consistent design
- Add draggable slider in ChatSettingsScreen and dynamic font size support
- Introduce 9 new chat color themes
- Add profile screens for about, terms, and privacy policy with navigation routes
- Add policy links to login and registration screens
- Fix post share URL format from /posts/ to /post/
This commit is contained in:
lafay
2026-04-04 08:01:45 +08:00
parent 189b977fac
commit 82c2970a85
76 changed files with 3382 additions and 2000 deletions

View File

@@ -15,6 +15,14 @@ import { useResponsive, useResponsiveSpacing } from '../../hooks';
// 内容最大宽度
const CONTENT_MAX_WIDTH = 720;
// 胡萝卜橙主题色
const THEME_COLORS = {
primary: '#FF6B35',
primaryLight: '#FF8C5A',
primaryDark: '#E55A2B',
};
import { authService, resolveAuthApiError } from '../../services/authService';
import { showPrompt } from '../../services/promptService';
import { useAuthStore } from '../../stores';
@@ -186,167 +194,173 @@ export const AccountSecurityScreen: React.FC = () => {
}
};
const content = (
<>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text variant="caption" style={styles.sectionTitle}>
</Text>
</View>
<View style={styles.card}>
<View style={styles.statusRow}>
<Text variant="body" color={colors.text.primary}></Text>
<View style={[styles.statusBadge, isEmailVerified ? styles.statusVerified : styles.statusUnverified]}>
<Text variant="caption" color={isEmailVerified ? '#1B5E20' : '#B26A00'}>
{emailStatusText}
</Text>
</View>
</View>
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="email-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="请输入邮箱"
placeholderTextColor={colors.text.hint}
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
/>
</View>
<View style={styles.codeRow}>
<View style={[styles.inputWrapper, styles.codeInput]}>
<MaterialCommunityIcons name="shield-key-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="验证码"
placeholderTextColor={colors.text.hint}
value={verificationCode}
onChangeText={setVerificationCode}
keyboardType="number-pad"
maxLength={6}
/>
</View>
<TouchableOpacity
style={[styles.sendCodeButton, (sendingCode || countdown > 0) && styles.buttonDisabled]}
onPress={handleSendCode}
disabled={sendingCode || countdown > 0}
>
{sendingCode ? (
<ActivityIndicator size="small" color={colors.text.inverse} />
) : (
<Text style={styles.sendCodeButtonText}>{countdown > 0 ? `${countdown}s` : '发送验证码'}</Text>
)}
</TouchableOpacity>
</View>
<TouchableOpacity
style={[styles.primaryButton, verifyingEmail && styles.buttonDisabled]}
onPress={handleVerifyEmail}
disabled={verifyingEmail}
>
{verifyingEmail ? (
<ActivityIndicator size="small" color={colors.text.inverse} />
) : (
<Text style={styles.primaryButtonText}></Text>
)}
</TouchableOpacity>
</View>
</View>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text variant="caption" style={styles.sectionTitle}>
</Text>
</View>
<View style={styles.card}>
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="当前密码"
placeholderTextColor={colors.text.hint}
value={oldPassword}
onChangeText={setOldPassword}
secureTextEntry
/>
</View>
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-plus-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="新密码至少6位"
placeholderTextColor={colors.text.hint}
value={newPassword}
onChangeText={setNewPassword}
secureTextEntry
/>
</View>
<View style={styles.codeRow}>
<View style={[styles.inputWrapper, styles.codeInput]}>
<MaterialCommunityIcons name="shield-key-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="邮箱验证码"
placeholderTextColor={colors.text.hint}
value={changePasswordCode}
onChangeText={setChangePasswordCode}
keyboardType="number-pad"
maxLength={6}
/>
</View>
<TouchableOpacity
style={[styles.sendCodeButton, (sendingChangePwdCode || changePwdCountdown > 0) && styles.buttonDisabled]}
onPress={handleSendChangePasswordCode}
disabled={sendingChangePwdCode || changePwdCountdown > 0}
>
{sendingChangePwdCode ? (
<ActivityIndicator size="small" color={colors.text.inverse} />
) : (
<Text style={styles.sendCodeButtonText}>
{changePwdCountdown > 0 ? `${changePwdCountdown}s` : '发送验证码'}
</Text>
)}
</TouchableOpacity>
</View>
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-check-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="确认新密码"
placeholderTextColor={colors.text.hint}
value={confirmPassword}
onChangeText={setConfirmPassword}
secureTextEntry
/>
</View>
<TouchableOpacity
style={[styles.primaryButton, updatingPassword && styles.buttonDisabled]}
onPress={handleChangePassword}
disabled={updatingPassword}
>
{updatingPassword ? (
<ActivityIndicator size="small" color={colors.text.inverse} />
) : (
<Text style={styles.primaryButtonText}></Text>
)}
</TouchableOpacity>
</View>
</View>
</>
);
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
{content}
<View style={styles.content}>
{/* 邮箱验证分组 */}
<View style={styles.groupHeader}>
<Text variant="caption" style={styles.groupTitle}>
</Text>
</View>
<View style={styles.card}>
{/* 状态显示 */}
<View style={styles.statusRow}>
<Text style={styles.statusLabel}></Text>
<View style={[styles.statusBadge, isEmailVerified ? styles.statusVerified : styles.statusUnverified]}>
<Text style={[styles.statusText, isEmailVerified ? styles.statusVerifiedText : styles.statusUnverifiedText]}>
{emailStatusText}
</Text>
</View>
</View>
{/* 邮箱输入框 */}
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="email-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="请输入邮箱"
placeholderTextColor={colors.text.hint}
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
/>
</View>
{/* 验证码行 */}
<View style={styles.codeRow}>
<View style={[styles.inputWrapper, styles.codeInput]}>
<MaterialCommunityIcons name="shield-key-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="验证码"
placeholderTextColor={colors.text.hint}
value={verificationCode}
onChangeText={setVerificationCode}
keyboardType="number-pad"
maxLength={6}
/>
</View>
<TouchableOpacity
style={[styles.sendCodeButton, (sendingCode || countdown > 0) && styles.buttonDisabled]}
onPress={handleSendCode}
disabled={sendingCode || countdown > 0}
>
{sendingCode ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.sendCodeButtonText}>{countdown > 0 ? `${countdown}s` : '发送验证码'}</Text>
)}
</TouchableOpacity>
</View>
{/* 验证按钮 */}
<TouchableOpacity
style={[styles.primaryButton, verifyingEmail && styles.buttonDisabled]}
onPress={handleVerifyEmail}
disabled={verifyingEmail}
>
{verifyingEmail ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.primaryButtonText}></Text>
)}
</TouchableOpacity>
</View>
{/* 修改密码分组 */}
<View style={styles.groupHeader}>
<Text variant="caption" style={styles.groupTitle}>
</Text>
</View>
<View style={styles.card}>
{/* 当前密码 */}
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="当前密码"
placeholderTextColor={colors.text.hint}
value={oldPassword}
onChangeText={setOldPassword}
secureTextEntry
/>
</View>
{/* 新密码 */}
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-plus-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="新密码至少6位"
placeholderTextColor={colors.text.hint}
value={newPassword}
onChangeText={setNewPassword}
secureTextEntry
/>
</View>
{/* 确认新密码 */}
<View style={styles.inputWrapper}>
<MaterialCommunityIcons name="lock-check-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="确认新密码"
placeholderTextColor={colors.text.hint}
value={confirmPassword}
onChangeText={setConfirmPassword}
secureTextEntry
/>
</View>
{/* 验证码行 */}
<View style={styles.codeRow}>
<View style={[styles.inputWrapper, styles.codeInput]}>
<MaterialCommunityIcons name="shield-key-outline" size={20} color={colors.text.secondary} style={styles.inputIcon} />
<TextInput
style={styles.input}
placeholder="邮箱验证码"
placeholderTextColor={colors.text.hint}
value={changePasswordCode}
onChangeText={setChangePasswordCode}
keyboardType="number-pad"
maxLength={6}
/>
</View>
<TouchableOpacity
style={[styles.sendCodeButton, (sendingChangePwdCode || changePwdCountdown > 0) && styles.buttonDisabled]}
onPress={handleSendChangePasswordCode}
disabled={sendingChangePwdCode || changePwdCountdown > 0}
>
{sendingChangePwdCode ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.sendCodeButtonText}>
{changePwdCountdown > 0 ? `${changePwdCountdown}s` : '发送验证码'}
</Text>
)}
</TouchableOpacity>
</View>
{/* 更新密码按钮 */}
<TouchableOpacity
style={[styles.primaryButton, updatingPassword && styles.buttonDisabled]}
onPress={handleChangePassword}
disabled={updatingPassword}
>
{updatingPassword ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.primaryButtonText}></Text>
)}
</TouchableOpacity>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
@@ -356,37 +370,51 @@ function createAccountSecurityStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.paper,
backgroundColor: '#fff',
},
scrollContent: {
paddingVertical: spacing.lg,
},
section: {
marginBottom: spacing['2xl'],
content: {
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
sectionHeader: {
// 分组标题
groupHeader: {
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,
},
// 卡片样式 - 统一
card: {
backgroundColor: '#F5F5F7',
borderRadius: 16,
padding: 6,
marginBottom: spacing['2xl'],
marginHorizontal: spacing['2xl'],
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
// 状态显示
statusRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: spacing.md,
paddingVertical: spacing.md,
marginBottom: spacing.md,
backgroundColor: '#fff',
borderRadius: 12,
},
statusLabel: {
fontSize: 15,
color: colors.text.secondary,
},
statusBadge: {
paddingHorizontal: spacing.sm,
@@ -394,24 +422,29 @@ function createAccountSecurityStyles(colors: AppColors) {
borderRadius: borderRadius.sm,
},
statusVerified: {
backgroundColor: colors.success.light + '40',
backgroundColor: '#E8F5E9',
},
statusUnverified: {
backgroundColor: colors.warning.light + '40',
backgroundColor: '#FFF3E0',
},
statusText: {
fontSize: 13,
fontWeight: '600',
},
statusVerifiedText: {
color: colors.success.dark,
color: '#2E7D32',
},
statusUnverifiedText: {
color: colors.warning.dark,
color: '#E65100',
},
// 输入框样式 - 统一
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.default,
backgroundColor: '#fff',
borderRadius: 14,
paddingHorizontal: spacing.md,
height: 50,
height: 56,
marginBottom: spacing.sm,
},
inputIcon: {
@@ -421,8 +454,9 @@ function createAccountSecurityStyles(colors: AppColors) {
flex: 1,
color: colors.text.primary,
fontSize: fontSizes.md,
height: 50,
height: 56,
},
// 验证码行
codeRow: {
flexDirection: 'row',
alignItems: 'center',
@@ -433,30 +467,32 @@ function createAccountSecurityStyles(colors: AppColors) {
flex: 1,
marginBottom: 0,
},
// 发送验证码按钮
sendCodeButton: {
height: 50,
height: 56,
minWidth: 110,
borderRadius: 14,
backgroundColor: colors.primary.main,
backgroundColor: THEME_COLORS.primary,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: spacing.sm,
},
sendCodeButtonText: {
color: colors.text.inverse,
color: '#fff',
fontSize: fontSizes.sm,
fontWeight: '600',
},
// 主按钮样式 - 统一
primaryButton: {
height: 50,
height: 56,
borderRadius: 14,
backgroundColor: colors.primary.main,
backgroundColor: THEME_COLORS.primary,
alignItems: 'center',
justifyContent: 'center',
marginTop: spacing.xs,
},
primaryButtonText: {
color: colors.text.inverse,
color: '#fff',
fontSize: fontSizes.md,
fontWeight: '600',
},