feat(service): add foreground service for background message sync
Implement Android foreground service to keep app alive in background for real-time message sync: - Add withForegroundService expo config plugin for notification channel setup - Add BackgroundSyncManager with REALTIME, BATTERY_SAVER, and DISABLED modes - Add ForegroundServiceModule for native Android foreground service binding - Refactor backgroundService to integrate foreground service and expo-background-task Also refactor auth and profile screens to use dynamic theme colors: - Replace hardcoded THEME_COLORS with colors.primary.main, colors.background.paper, etc. - Add useResolvedColorScheme for dynamic StatusBar styling - Update NotificationSettingsScreen with sync mode selection UI BREAKING CHANGE: Switched from expo-background-fetch to expo-background-task, minimum background sync interval changed from 900s to 15min in config
This commit is contained in:
@@ -26,19 +26,13 @@ import {
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useAppColors, type AppColors } from '../../theme';
|
||||
import { useAppColors, useResolvedColorScheme, type AppColors } from '../../theme';
|
||||
import { authService, resolveAuthApiError } from '../../services/authService';
|
||||
import { showPrompt } from '../../services/promptService';
|
||||
|
||||
// 胡萝卜橙主题色
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
export const ForgotPasswordScreen: React.FC = () => {
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useMemo(() => createForgotPasswordStyles(colors), [colors]);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -156,7 +150,7 @@ export const ForgotPasswordScreen: React.FC = () => {
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.keyboardView}
|
||||
@@ -198,7 +192,7 @@ export const ForgotPasswordScreen: React.FC = () => {
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
{email.length > 0 && validateEmail(email) && (
|
||||
<MaterialCommunityIcons name="check-circle" size={20} color={THEME_COLORS.primary} style={styles.checkIcon} />
|
||||
<MaterialCommunityIcons name="check-circle" size={20} color={colors.primary.main} style={styles.checkIcon} />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -225,7 +219,7 @@ export const ForgotPasswordScreen: React.FC = () => {
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
{sendingCode ? (
|
||||
<ActivityIndicator size="small" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="small" color={colors.primary.main} />
|
||||
) : (
|
||||
<Text style={styles.sendCodeButtonText}>{countdown > 0 ? `${countdown}s` : '获取验证码'}</Text>
|
||||
)}
|
||||
@@ -295,7 +289,7 @@ export const ForgotPasswordScreen: React.FC = () => {
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.submitButtonText}>重置密码</Text>
|
||||
)}
|
||||
@@ -307,7 +301,7 @@ export const ForgotPasswordScreen: React.FC = () => {
|
||||
onPress={() => router.back()}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<MaterialCommunityIcons name="arrow-left" size={18} color={THEME_COLORS.primary} style={{ marginRight: 4 }} />
|
||||
<MaterialCommunityIcons name="arrow-left" size={18} color={colors.primary.main} style={{ marginRight: 4 }} />
|
||||
<Text style={styles.backButtonText}>返回登录</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -322,7 +316,7 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
keyboardView: {
|
||||
flex: 1,
|
||||
@@ -354,7 +348,7 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
underline: {
|
||||
width: 40,
|
||||
height: 4,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderRadius: 2,
|
||||
marginTop: 12,
|
||||
marginBottom: 16,
|
||||
@@ -382,7 +376,7 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 18,
|
||||
height: 56,
|
||||
@@ -412,7 +406,7 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
sendCodeButton: {
|
||||
height: 56,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.1)',
|
||||
backgroundColor: colors.primary.light + '20',
|
||||
borderRadius: 14,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -424,14 +418,14 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
sendCodeButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
},
|
||||
|
||||
// 提交按钮
|
||||
submitButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 8,
|
||||
@@ -440,7 +434,7 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
opacity: 0.7,
|
||||
},
|
||||
submitButtonText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -453,7 +447,7 @@ function createForgotPasswordStyles(colors: AppColors) {
|
||||
marginTop: 24,
|
||||
},
|
||||
backButtonText: {
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
||||
@@ -26,21 +26,17 @@ import {
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useAppColors, type AppColors } from '../../theme';
|
||||
import { useAppColors, useResolvedColorScheme, type AppColors } from '../../theme';
|
||||
import { useAuthStore } from '../../stores';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
import { useResponsive } from '../../hooks';
|
||||
import { showPrompt } from '../../services/promptService';
|
||||
|
||||
// 胡萝卜橙主题色
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
|
||||
export const LoginScreen: React.FC = () => {
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useMemo(() => createLoginStyles(colors), [colors]);
|
||||
const router = useRouter();
|
||||
const login = useAuthStore((state) => state.login);
|
||||
@@ -129,7 +125,7 @@ export const LoginScreen: React.FC = () => {
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.keyboardView}
|
||||
@@ -175,7 +171,7 @@ export const LoginScreen: React.FC = () => {
|
||||
returnKeyType="next"
|
||||
/>
|
||||
{username.length > 0 && (
|
||||
<MaterialCommunityIcons name="check-circle" size={20} color={THEME_COLORS.primary} style={styles.checkIcon} />
|
||||
<MaterialCommunityIcons name="check-circle" size={20} color={colors.primary.main} style={styles.checkIcon} />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -218,17 +214,17 @@ export const LoginScreen: React.FC = () => {
|
||||
|
||||
{/* 服务条款勾选 */}
|
||||
<View style={styles.termsContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setAgreedToTerms(!agreedToTerms)}
|
||||
activeOpacity={0.8}
|
||||
style={styles.checkboxWrapper}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={agreedToTerms ? 'checkbox-marked' : 'checkbox-blank-outline'}
|
||||
size={22}
|
||||
color={agreedToTerms ? THEME_COLORS.primary : colors.text.hint}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => setAgreedToTerms(!agreedToTerms)}
|
||||
activeOpacity={0.8}
|
||||
style={styles.checkboxWrapper}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={agreedToTerms ? 'checkbox-marked' : 'checkbox-blank-outline'}
|
||||
size={22}
|
||||
color={agreedToTerms ? colors.primary.main : colors.text.hint}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.termsText}>
|
||||
我已阅读并同意
|
||||
<Text
|
||||
@@ -255,7 +251,7 @@ export const LoginScreen: React.FC = () => {
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color="#FFF" />
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.loginButtonText}>登 录</Text>
|
||||
)}
|
||||
@@ -282,7 +278,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
keyboardView: {
|
||||
flex: 1,
|
||||
@@ -317,7 +313,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
underline: {
|
||||
width: 40,
|
||||
height: 4,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderRadius: 2,
|
||||
marginTop: 12,
|
||||
marginBottom: 16,
|
||||
@@ -345,7 +341,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 18,
|
||||
height: 56,
|
||||
@@ -372,7 +368,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
},
|
||||
forgotPasswordText: {
|
||||
fontSize: 14,
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
||||
@@ -380,7 +376,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
loginButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@@ -390,7 +386,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
loginButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
color: colors.text.inverse,
|
||||
},
|
||||
|
||||
// 底部
|
||||
@@ -406,7 +402,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
},
|
||||
registerLink: {
|
||||
fontSize: 15,
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
fontWeight: '600',
|
||||
marginLeft: 6,
|
||||
},
|
||||
@@ -419,7 +415,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
color: colors.text.hint,
|
||||
},
|
||||
policyLink: {
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
fontWeight: '500',
|
||||
},
|
||||
termsContainer: {
|
||||
@@ -439,7 +435,7 @@ function createLoginStyles(colors: AppColors) {
|
||||
lineHeight: 22,
|
||||
},
|
||||
termsLink: {
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useAppColors, type AppColors } from '../../theme';
|
||||
import { useAppColors, useResolvedColorScheme, type AppColors } from '../../theme';
|
||||
import { authService, resolveAuthApiError } from '../../services/authService';
|
||||
import { useAuthStore } from '../../stores';
|
||||
import {
|
||||
@@ -58,6 +58,7 @@ const STEP_COMPONENTS = [
|
||||
|
||||
export const RegisterScreen: React.FC = () => {
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useMemo(() => createRegisterStyles(colors), [colors]);
|
||||
const router = useRouter();
|
||||
const register = useAuthStore((state) => state.register);
|
||||
@@ -232,7 +233,7 @@ export const RegisterScreen: React.FC = () => {
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.keyboardView}
|
||||
@@ -295,7 +296,7 @@ function createRegisterStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
keyboardView: {
|
||||
flex: 1,
|
||||
@@ -316,7 +317,7 @@ function createRegisterStyles(colors: AppColors) {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@@ -346,7 +347,7 @@ function createRegisterStyles(colors: AppColors) {
|
||||
},
|
||||
loginLink: {
|
||||
fontSize: 15,
|
||||
color: '#FF6B35',
|
||||
color: colors.primary.main,
|
||||
fontWeight: '600',
|
||||
marginLeft: 6,
|
||||
},
|
||||
|
||||
@@ -15,12 +15,6 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useAppColors, type AppColors } from '../../theme';
|
||||
import { RegisterStepProps } from './types';
|
||||
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
export const RegisterStep1Email: React.FC<RegisterStepProps> = ({
|
||||
formData,
|
||||
updateFormData,
|
||||
@@ -91,7 +85,7 @@ export const RegisterStep1Email: React.FC<RegisterStepProps> = ({
|
||||
<MaterialCommunityIcons
|
||||
name="check-circle"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
style={styles.checkIcon}
|
||||
/>
|
||||
)}
|
||||
@@ -145,7 +139,7 @@ function createStyles(colors: AppColors) {
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 18,
|
||||
height: 56,
|
||||
@@ -173,7 +167,7 @@ function createStyles(colors: AppColors) {
|
||||
actionButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#FF6B35',
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 8,
|
||||
@@ -184,7 +178,7 @@ function createStyles(colors: AppColors) {
|
||||
actionButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
color: colors.text.inverse,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* 注册步骤2:填写验证码
|
||||
* 使用 VerificationCodeInput 组件
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
@@ -15,12 +10,6 @@ import { useAppColors, type AppColors } from '../../theme';
|
||||
import { VerificationCodeInput } from '../../components/common';
|
||||
import { RegisterStepProps } from './types';
|
||||
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
export const RegisterStep2Verification: React.FC<RegisterStepProps> = ({
|
||||
formData,
|
||||
updateFormData,
|
||||
@@ -175,13 +164,13 @@ function createStyles(colors: AppColors) {
|
||||
},
|
||||
resendText: {
|
||||
fontSize: 14,
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
fontWeight: '500',
|
||||
},
|
||||
actionButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#FF6B35',
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
@@ -193,7 +182,7 @@ function createStyles(colors: AppColors) {
|
||||
actionButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
color: colors.text.inverse,
|
||||
},
|
||||
backButton: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* 注册步骤3:设置用户信息
|
||||
* 包含用户名、昵称、密码等
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
@@ -18,12 +13,6 @@ import { useAppColors, type AppColors } from '../../theme';
|
||||
import { RegisterStepProps } from './types';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
export const RegisterStep3Profile: React.FC<RegisterStepProps> = ({
|
||||
formData,
|
||||
updateFormData,
|
||||
@@ -208,7 +197,7 @@ export const RegisterStep3Profile: React.FC<RegisterStepProps> = ({
|
||||
<MaterialCommunityIcons
|
||||
name={agreedToTerms ? 'checkbox-marked' : 'checkbox-blank-outline'}
|
||||
size={22}
|
||||
color={agreedToTerms ? THEME_COLORS.primary : colors.text?.hint || '#999'}
|
||||
color={agreedToTerms ? colors.primary.main : colors.text?.hint || '#999'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.termsText}>
|
||||
@@ -288,7 +277,7 @@ function createStyles(colors: AppColors) {
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 18,
|
||||
height: 52,
|
||||
@@ -331,13 +320,13 @@ function createStyles(colors: AppColors) {
|
||||
lineHeight: 22,
|
||||
},
|
||||
termsLink: {
|
||||
color: '#FF6B35',
|
||||
color: colors.primary.main,
|
||||
fontWeight: '500',
|
||||
},
|
||||
actionButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#FF6B35',
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@@ -347,7 +336,7 @@ function createStyles(colors: AppColors) {
|
||||
actionButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
color: colors.text.inverse,
|
||||
},
|
||||
backButton: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -26,12 +26,6 @@ import { showPrompt } from '../../services/promptService';
|
||||
import { uploadService } from '../../services/uploadService';
|
||||
import type { UserIdentity } from '../../types/dto';
|
||||
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
const IDENTITY_CONFIG: Record<string, { title: string; idField: string; idLabel: string }> = {
|
||||
student: { title: '在校学生', idField: 'student_id', idLabel: '学号' },
|
||||
teacher: { title: '教职工', idField: 'employee_id', idLabel: '工号' },
|
||||
@@ -230,7 +224,7 @@ export const VerificationFormScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="information-outline"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
<Text style={styles.infoText}>
|
||||
请填写真实信息,上传清晰的证明材料照片,审核通过后将获得认证标识
|
||||
@@ -303,7 +297,7 @@ export const VerificationFormScreen: React.FC = () => {
|
||||
disabled={isLoading || uploading}
|
||||
>
|
||||
{(isLoading || uploading) ? (
|
||||
<ActivityIndicator size="small" color="#FFF" />
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.submitButtonText}>提交认证申请</Text>
|
||||
)}
|
||||
@@ -317,7 +311,7 @@ function createStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
@@ -349,7 +343,7 @@ function createStyles(colors: AppColors) {
|
||||
infoCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: '#FFF5F0',
|
||||
backgroundColor: colors.primary.main + '15',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
marginBottom: 24,
|
||||
@@ -374,7 +368,7 @@ function createStyles(colors: AppColors) {
|
||||
marginBottom: 8,
|
||||
},
|
||||
required: {
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
},
|
||||
hintText: {
|
||||
fontSize: 13,
|
||||
@@ -382,7 +376,7 @@ function createStyles(colors: AppColors) {
|
||||
marginBottom: 8,
|
||||
},
|
||||
inputWrapper: {
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 16,
|
||||
height: 50,
|
||||
@@ -404,7 +398,7 @@ function createStyles(colors: AppColors) {
|
||||
marginTop: 6,
|
||||
},
|
||||
uploadButton: {
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
minHeight: 200,
|
||||
@@ -433,7 +427,7 @@ function createStyles(colors: AppColors) {
|
||||
submitButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -443,7 +437,7 @@ function createStyles(colors: AppColors) {
|
||||
submitButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
color: colors.text.inverse,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* 注册后身份认证引导页面
|
||||
* 可跳过的认证界面,引导用户完成身份认证
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
@@ -10,7 +5,6 @@ import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
Image,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
@@ -19,12 +13,6 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useAppColors, type AppColors } from '../../theme';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
// 身份类型选项
|
||||
const IDENTITY_OPTIONS = [
|
||||
{
|
||||
@@ -98,7 +86,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name={option.icon as any}
|
||||
size={32}
|
||||
color={isSelected ? THEME_COLORS.primary : colors.text?.secondary || '#666'}
|
||||
color={isSelected ? colors.primary.main : colors.text?.secondary || '#666'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.identityContent}>
|
||||
@@ -112,7 +100,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="check-circle"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -132,7 +120,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="shield-check-outline"
|
||||
size={48}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.title}>身份认证</Text>
|
||||
@@ -147,7 +135,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="check-decagram"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
<Text style={styles.benefitText}>获得认证标识</Text>
|
||||
</View>
|
||||
@@ -155,7 +143,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="lock-open-outline"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
<Text style={styles.benefitText}>解锁专属频道</Text>
|
||||
</View>
|
||||
@@ -163,7 +151,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="account-group-outline"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
<Text style={styles.benefitText}>加入社群圈子</Text>
|
||||
</View>
|
||||
@@ -198,7 +186,7 @@ function createStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
@@ -214,7 +202,7 @@ function createStyles(colors: AppColors) {
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: 50,
|
||||
backgroundColor: '#FFF5F0',
|
||||
backgroundColor: colors.primary.main + '15',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
@@ -258,15 +246,15 @@ function createStyles(colors: AppColors) {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
marginBottom: 12,
|
||||
borderWidth: 2,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
identityCardSelected: {
|
||||
backgroundColor: '#FFF5F0',
|
||||
borderColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main + '15',
|
||||
borderColor: colors.primary.main,
|
||||
},
|
||||
identityIconContainer: {
|
||||
width: 48,
|
||||
@@ -285,7 +273,7 @@ function createStyles(colors: AppColors) {
|
||||
marginBottom: 4,
|
||||
},
|
||||
identityTitleSelected: {
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
},
|
||||
identityDescription: {
|
||||
fontSize: 13,
|
||||
@@ -303,17 +291,17 @@ function createStyles(colors: AppColors) {
|
||||
continueButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
continueButtonDisabled: {
|
||||
backgroundColor: '#ccc',
|
||||
backgroundColor: colors.background.disabled,
|
||||
},
|
||||
continueButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
},
|
||||
skipButton: {
|
||||
height: 56,
|
||||
|
||||
@@ -33,7 +33,7 @@ import { useNavigation, useRouter } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Text, ImageGallery, ImageGridItem } from '../../components/common';
|
||||
import { useAppColors } from '../../theme';
|
||||
import { useAppColors, useResolvedColorScheme } from '../../theme';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
import { messageManager, callStore } from '../../stores';
|
||||
import { useBreakpointGTE } from '../../hooks/useResponsive';
|
||||
@@ -56,6 +56,7 @@ export const ChatScreen: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useChatScreenStyles();
|
||||
|
||||
// 响应式布局
|
||||
@@ -406,7 +407,7 @@ export const ChatScreen: React.FC = () => {
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
||||
>
|
||||
<StatusBar style="dark" backgroundColor="#FFFFFF" />
|
||||
<StatusBar style={resolved === 'dark' ? 'light' : 'dark'} backgroundColor={colors.background.paper} />
|
||||
|
||||
{/* 顶部栏 */}
|
||||
<ChatHeader
|
||||
@@ -499,7 +500,7 @@ export const ChatScreen: React.FC = () => {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
backgroundColor: colors.background.paper + 'E6',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { spacing, borderRadius, useAppColors, type AppColors } from '../../theme';
|
||||
import { spacing, borderRadius, useAppColors, useResolvedColorScheme, type AppColors } from '../../theme';
|
||||
import { SystemMessageResponse } from '../../types/dto';
|
||||
import { messageService } from '../../services/messageService';
|
||||
import { commentService } from '../../services/commentService';
|
||||
@@ -49,18 +49,12 @@ const MESSAGE_TYPES = [
|
||||
{ key: 'announcement', title: '公告' },
|
||||
];
|
||||
|
||||
// 胡萝卜橙主题色
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
const LIKE_SYSTEM_TYPES = new Set(['like_post', 'like_comment', 'like_reply', 'favorite_post']);
|
||||
const GROUP_SYSTEM_TYPES = new Set(['group_invite', 'group_join_apply', 'group_join_approved', 'group_join_rejected']);
|
||||
|
||||
export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack }) => {
|
||||
const colors = useAppColors();
|
||||
const resolved = useResolvedColorScheme();
|
||||
const styles = useMemo(() => createNotificationsStyles(colors), [colors]);
|
||||
const isFocused = useIsFocused();
|
||||
const { setSystemUnreadCount, decrementSystemUnreadCount } = useMessageManagerSystemUnreadCount();
|
||||
@@ -424,9 +418,9 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
if (!isHydrated) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -434,7 +428,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
|
||||
<StatusBar barStyle={resolved === 'dark' ? 'light-content' : 'dark-content'} backgroundColor={colors.background.paper} />
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.content,
|
||||
@@ -499,7 +493,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
{/* 消息列表 */}
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
@@ -519,8 +513,8 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[THEME_COLORS.primary]}
|
||||
tintColor={THEME_COLORS.primary}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -580,7 +574,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
{/* 消息列表 */}
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
@@ -600,8 +594,8 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[THEME_COLORS.primary]}
|
||||
tintColor={THEME_COLORS.primary}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -617,7 +611,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
@@ -629,7 +623,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
headerWide: {
|
||||
paddingHorizontal: 32,
|
||||
@@ -650,7 +644,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
fontSize: 20,
|
||||
},
|
||||
unreadBadge: {
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
@@ -660,7 +654,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
unreadBadgeText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -674,7 +668,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -686,7 +680,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider || '#E5E5EA',
|
||||
},
|
||||
@@ -702,7 +696,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
paddingVertical: 8,
|
||||
marginRight: 8,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderWidth: 1,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
@@ -712,15 +706,15 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
marginRight: 10,
|
||||
},
|
||||
filterTagActive: {
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
borderColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderColor: colors.primary.main,
|
||||
},
|
||||
filterTagTextActive: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
filterCount: {
|
||||
marginLeft: 6,
|
||||
backgroundColor: THEME_COLORS.primaryLight + '40',
|
||||
backgroundColor: colors.primary.light + '40',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 1,
|
||||
borderRadius: 8,
|
||||
@@ -770,7 +764,7 @@ function createNotificationsStyles(colors: AppColors) {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
|
||||
@@ -21,13 +21,6 @@ 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: {
|
||||
@@ -114,7 +107,7 @@ function createAccountDeletionStyles(colors: AppColors) {
|
||||
},
|
||||
// 输入框 - 扁平化风格
|
||||
input: {
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
padding: spacing.md,
|
||||
fontSize: 16,
|
||||
@@ -133,12 +126,12 @@ function createAccountDeletionStyles(colors: AppColors) {
|
||||
flex: 1,
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
primaryButtonText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: fontSizes.md,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -166,7 +159,7 @@ function createAccountDeletionStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
dangerButtonText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: fontSizes.md,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -392,9 +385,9 @@ export const AccountDeletionScreen: React.FC = () => {
|
||||
onPress={handleRequestDeletion}
|
||||
disabled={submitting || !password.trim()}
|
||||
>
|
||||
{submitting ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
{submitting ? (
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.dangerButtonText}>确认注销</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -16,13 +16,6 @@ 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';
|
||||
@@ -263,9 +256,9 @@ export const AccountSecurityScreen: React.FC = () => {
|
||||
onPress={handleSendCode}
|
||||
disabled={sendingCode || countdown > 0}
|
||||
>
|
||||
{sendingCode ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
{sendingCode ? (
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.sendCodeButtonText}>{countdown > 0 ? `${countdown}s` : '发送验证码'}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
@@ -278,7 +271,7 @@ export const AccountSecurityScreen: React.FC = () => {
|
||||
disabled={verifyingEmail}
|
||||
>
|
||||
{verifyingEmail ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.primaryButtonText}>验证邮箱</Text>
|
||||
)}
|
||||
@@ -352,7 +345,7 @@ export const AccountSecurityScreen: React.FC = () => {
|
||||
disabled={sendingChangePwdCode || changePwdCountdown > 0}
|
||||
>
|
||||
{sendingChangePwdCode ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.sendCodeButtonText}>
|
||||
{changePwdCountdown > 0 ? `${changePwdCountdown}s` : '发送验证码'}
|
||||
@@ -368,7 +361,7 @@ export const AccountSecurityScreen: React.FC = () => {
|
||||
disabled={updatingPassword}
|
||||
>
|
||||
{updatingPassword ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
<ActivityIndicator size="small" color={colors.text.inverse} />
|
||||
) : (
|
||||
<Text style={styles.primaryButtonText}>更新密码</Text>
|
||||
)}
|
||||
@@ -432,26 +425,26 @@ function createAccountSecurityStyles(colors: AppColors) {
|
||||
borderRadius: borderRadius.sm,
|
||||
},
|
||||
statusVerified: {
|
||||
backgroundColor: '#E8F5E9',
|
||||
backgroundColor: colors.success.light + '30',
|
||||
},
|
||||
statusUnverified: {
|
||||
backgroundColor: '#FFF3E0',
|
||||
backgroundColor: colors.warning.light + '30',
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
},
|
||||
statusVerifiedText: {
|
||||
color: '#2E7D32',
|
||||
color: colors.success.dark,
|
||||
},
|
||||
statusUnverifiedText: {
|
||||
color: '#E65100',
|
||||
color: colors.warning.dark,
|
||||
},
|
||||
// 输入框样式 - 扁平化风格,与登录页一致
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: spacing.lg,
|
||||
height: 56,
|
||||
@@ -485,13 +478,13 @@ function createAccountSecurityStyles(colors: AppColors) {
|
||||
height: 56,
|
||||
minWidth: 110,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: spacing.sm,
|
||||
},
|
||||
sendCodeButtonText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: fontSizes.sm,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -499,14 +492,14 @@ function createAccountSecurityStyles(colors: AppColors) {
|
||||
primaryButton: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: spacing.xs,
|
||||
marginHorizontal: spacing['2xl'],
|
||||
},
|
||||
primaryButtonText: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontSize: fontSizes.md,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
StyleSheet,
|
||||
Switch,
|
||||
ScrollView,
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
setVibrationPreference,
|
||||
} from '../../services/notificationPreferences';
|
||||
import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
||||
import { backgroundSyncManager, BackgroundSyncMode } from '../../services/BackgroundSyncManager';
|
||||
|
||||
// 内容最大宽度
|
||||
const CONTENT_MAX_WIDTH = 720;
|
||||
@@ -41,6 +44,7 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
const [vibrationEnabled, setVibrationEnabledState] = useState(true);
|
||||
const [pushEnabled, setPushEnabled] = useState(true);
|
||||
const [soundEnabled, setSoundEnabled] = useState(true);
|
||||
const [syncMode, setSyncMode] = useState<BackgroundSyncMode>(BackgroundSyncMode.BATTERY_SAVER);
|
||||
const { isWideScreen, isMobile } = useResponsive();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
@@ -55,6 +59,7 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
setVibrationEnabledState(prefs.vibrationEnabled);
|
||||
setPushEnabled(prefs.pushEnabled);
|
||||
setSoundEnabled(prefs.soundEnabled);
|
||||
setSyncMode(backgroundSyncManager.getMode());
|
||||
} catch (error) {
|
||||
console.error('加载通知设置失败:', error);
|
||||
}
|
||||
@@ -89,6 +94,51 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSyncModeChange = async (mode: BackgroundSyncMode) => {
|
||||
if (mode === BackgroundSyncMode.REALTIME) {
|
||||
Alert.alert(
|
||||
'实时模式',
|
||||
'实时模式会在通知栏显示常驻通知以保持应用后台运行,确保消息即时同步。\n\n' +
|
||||
'建议在系统设置中将应用加入电池优化白名单以获得最佳效果。\n\n' +
|
||||
'注意:此模式可能增加电池消耗。',
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '开启',
|
||||
onPress: async () => {
|
||||
await backgroundSyncManager.setMode(mode);
|
||||
setSyncMode(mode);
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
} else {
|
||||
await backgroundSyncManager.setMode(mode);
|
||||
setSyncMode(mode);
|
||||
}
|
||||
};
|
||||
|
||||
const syncModeOptions: { mode: BackgroundSyncMode; title: string; subtitle: string; icon: string }[] = [
|
||||
{
|
||||
mode: BackgroundSyncMode.BATTERY_SAVER,
|
||||
title: '省电模式',
|
||||
subtitle: '系统后台任务,每 15 分钟检查一次',
|
||||
icon: 'leaf',
|
||||
},
|
||||
{
|
||||
mode: BackgroundSyncMode.REALTIME,
|
||||
title: '实时模式',
|
||||
subtitle: '通知栏常驻保活,即时同步消息',
|
||||
icon: 'lightning-bolt',
|
||||
},
|
||||
{
|
||||
mode: BackgroundSyncMode.DISABLED,
|
||||
title: '禁用',
|
||||
subtitle: '仅在应用打开时接收消息',
|
||||
icon: 'close-circle-outline',
|
||||
},
|
||||
];
|
||||
|
||||
const settings: NotificationSettingItem[] = [
|
||||
{
|
||||
key: 'push',
|
||||
@@ -164,6 +214,62 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
关闭推送通知后,您将不再收到任何消息提醒,但消息仍会在应用内显示。
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 后台同步模式 */}
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text variant="caption" style={styles.sectionTitle}>
|
||||
后台同步模式
|
||||
</Text>
|
||||
</View>
|
||||
{syncModeOptions.map((option) => (
|
||||
<TouchableOpacity
|
||||
key={option.mode}
|
||||
style={[
|
||||
styles.settingItem,
|
||||
syncMode === option.mode && styles.settingItemActive,
|
||||
]}
|
||||
onPress={() => handleSyncModeChange(option.mode)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.iconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name={option.icon as any}
|
||||
size={22}
|
||||
color={syncMode === option.mode ? colors.primary.main : colors.text.secondary}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.settingContent}>
|
||||
<Text
|
||||
variant="body"
|
||||
color={syncMode === option.mode ? colors.primary.main : colors.text.primary}
|
||||
>
|
||||
{option.title}
|
||||
</Text>
|
||||
<Text variant="caption" color={colors.text.secondary} style={styles.subtitle}>
|
||||
{option.subtitle}
|
||||
</Text>
|
||||
</View>
|
||||
{syncMode === option.mode && (
|
||||
<MaterialCommunityIcons
|
||||
name="check"
|
||||
size={20}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 实时模式说明 */}
|
||||
{syncMode === BackgroundSyncMode.REALTIME && (
|
||||
<View style={styles.tipContainer}>
|
||||
<MaterialCommunityIcons name="shield-check-outline" size={16} color={colors.text.hint} />
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
|
||||
实时模式已开启,应用将在通知栏显示常驻通知以保持后台运行。如需更稳定的后台运行,请在系统设置中将本应用加入电池优化白名单。
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -210,6 +316,9 @@ function createNotificationSettingsStyles(colors: AppColors) {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
},
|
||||
settingItemActive: {
|
||||
backgroundColor: colors.primary.main + '0D',
|
||||
},
|
||||
iconContainer: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
|
||||
@@ -18,13 +18,6 @@ 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: '只有你关注的人可查看' },
|
||||
@@ -74,7 +67,7 @@ function createPrivacySettingsStyles(colors: AppColors) {
|
||||
},
|
||||
// 隐私设置项 - 扁平化卡片风格
|
||||
item: {
|
||||
backgroundColor: '#F5F5F7',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 14,
|
||||
padding: spacing.lg,
|
||||
marginHorizontal: spacing['2xl'],
|
||||
@@ -111,15 +104,15 @@ function createPrivacySettingsStyles(colors: AppColors) {
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
optionButtonActive: {
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
borderColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderColor: colors.primary.main,
|
||||
},
|
||||
optionText: {
|
||||
fontSize: 13,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
optionTextActive: {
|
||||
color: '#fff',
|
||||
color: colors.text.inverse,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,12 +26,6 @@ import {
|
||||
import { showPrompt } from '../../services/promptService';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
not_submitted: {
|
||||
title: '未认证',
|
||||
@@ -99,7 +93,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={THEME_COLORS.primary} />
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -110,10 +104,11 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
const identityText = IDENTITY_TEXT[status?.identity || 'general'];
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 状态卡片 */}
|
||||
<View style={styles.statusCard}>
|
||||
<View style={[styles.statusIconContainer, { backgroundColor: statusConfig.color + '20' }]}>
|
||||
@@ -128,7 +123,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
|
||||
{status?.verification_status === 'approved' && (
|
||||
<View style={styles.identityBadge}>
|
||||
<MaterialCommunityIcons name="check-circle" size={16} color={THEME_COLORS.primary} />
|
||||
<MaterialCommunityIcons name="check-circle" size={16} color={colors.primary.main} />
|
||||
<Text style={styles.identityText}>{identityText}</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -153,7 +148,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="check-decagram"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.benefitContent}>
|
||||
@@ -169,7 +164,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="lock-open-outline"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.benefitContent}>
|
||||
@@ -185,7 +180,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="account-group-outline"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
color={colors.primary.main}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.benefitContent}>
|
||||
@@ -208,7 +203,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
onPress={handleStartVerification}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<MaterialCommunityIcons name="shield-check-outline" size={20} color="#FFF" />
|
||||
<MaterialCommunityIcons name="shield-check-outline" size={20} color={colors.primary.contrast} />
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{status?.verification_status === 'rejected' ? '重新认证' : '开始认证'}
|
||||
</Text>
|
||||
@@ -221,7 +216,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
onPress={handleViewRecords}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<MaterialCommunityIcons name="file-document-outline" size={20} color={THEME_COLORS.primary} />
|
||||
<MaterialCommunityIcons name="file-document-outline" size={20} color={colors.primary.main} />
|
||||
<Text style={styles.secondaryButtonText}>查看认证记录</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
@@ -262,6 +257,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -269,7 +265,7 @@ function createStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
@@ -307,7 +303,7 @@ function createStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 32,
|
||||
paddingHorizontal: 24,
|
||||
backgroundColor: '#F9F9F9',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 16,
|
||||
marginBottom: 24,
|
||||
},
|
||||
@@ -334,7 +330,7 @@ function createStyles(colors: AppColors) {
|
||||
identityBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#FFF5F0',
|
||||
backgroundColor: colors.primary.main + '15',
|
||||
borderRadius: 20,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
@@ -343,25 +339,25 @@ function createStyles(colors: AppColors) {
|
||||
identityText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
marginLeft: 6,
|
||||
},
|
||||
rejectReasonContainer: {
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
backgroundColor: '#FFF3F3',
|
||||
backgroundColor: colors.error.light + '20',
|
||||
borderRadius: 8,
|
||||
width: '100%',
|
||||
},
|
||||
rejectReasonLabel: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: '#F44336',
|
||||
color: colors.error.main,
|
||||
marginBottom: 4,
|
||||
},
|
||||
rejectReasonText: {
|
||||
fontSize: 13,
|
||||
color: '#D32F2F',
|
||||
color: colors.error.dark,
|
||||
lineHeight: 18,
|
||||
},
|
||||
benefitsSection: {
|
||||
@@ -379,7 +375,7 @@ function createStyles(colors: AppColors) {
|
||||
benefitItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: '#F9F9F9',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
},
|
||||
@@ -387,7 +383,7 @@ function createStyles(colors: AppColors) {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#FFF5F0',
|
||||
backgroundColor: colors.primary.main + '15',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 12,
|
||||
@@ -415,12 +411,12 @@ function createStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: THEME_COLORS.primary,
|
||||
backgroundColor: colors.primary.main,
|
||||
},
|
||||
primaryButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: '#FFF',
|
||||
color: colors.text.inverse,
|
||||
marginLeft: 8,
|
||||
},
|
||||
secondaryButton: {
|
||||
@@ -429,18 +425,18 @@ function createStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#FFF5F0',
|
||||
backgroundColor: colors.primary.main + '15',
|
||||
borderWidth: 1,
|
||||
borderColor: THEME_COLORS.primary,
|
||||
borderColor: colors.primary.main,
|
||||
},
|
||||
secondaryButtonText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
color: THEME_COLORS.primary,
|
||||
color: colors.primary.main,
|
||||
marginLeft: 8,
|
||||
},
|
||||
verifiedInfo: {
|
||||
backgroundColor: '#F9F9F9',
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user