Files
frontend/src/screens/auth/RegisterStep1Email.tsx
lafay 6f84e17772 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
2026-04-11 04:27:22 +08:00

187 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 注册步骤1输入邮箱
* 包含邮箱输入框和获取验证码按钮
*/
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useAppColors, type AppColors } from '../../theme';
import { RegisterStepProps } from './types';
export const RegisterStep1Email: React.FC<RegisterStepProps> = ({
formData,
updateFormData,
onNext,
colors: propColors,
sendingCode,
countdown,
onSendCode,
}) => {
const colors = propColors || useAppColors();
const styles = createStyles(colors);
const [emailError, setEmailError] = useState('');
const validateEmail = (email: string): boolean => {
if (!email.trim()) {
setEmailError('请输入邮箱');
return false;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
setEmailError('请输入正确的邮箱地址');
return false;
}
setEmailError('');
return true;
};
const handleSendCode = async () => {
if (!validateEmail(formData.email)) {
return;
}
if (onSendCode) {
const success = await onSendCode(formData.email);
if (success) {
onNext();
}
}
};
return (
<View style={styles.container}>
<View style={styles.formSection}>
{/* 邮箱输入框 */}
<View style={styles.inputContainer}>
<Text style={styles.inputLabel}></Text>
<View
style={[
styles.inputWrapper,
emailError ? styles.inputWrapperError : null,
]}
>
<TextInput
style={styles.input}
placeholder="请输入邮箱地址"
placeholderTextColor={colors.text?.hint || '#999'}
value={formData.email}
onChangeText={(text) => {
updateFormData({ email: text });
if (emailError) setEmailError('');
}}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
editable={!sendingCode}
/>
{formData.email.length > 0 &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) && (
<MaterialCommunityIcons
name="check-circle"
size={20}
color={colors.primary.main}
style={styles.checkIcon}
/>
)}
</View>
{emailError ? (
<Text style={styles.errorText}>{emailError}</Text>
) : null}
</View>
{/* 获取验证码按钮 */}
<TouchableOpacity
style={[
styles.actionButton,
(sendingCode || (countdown ?? 0) > 0) && styles.actionButtonDisabled,
]}
onPress={handleSendCode}
disabled={sendingCode || (countdown ?? 0) > 0}
activeOpacity={0.9}
>
<Text style={styles.actionButtonText}>
{sendingCode
? '发送中...'
: (countdown ?? 0) > 0
? `${countdown}秒后重试`
: '获取验证码'}
</Text>
</TouchableOpacity>
</View>
</View>
);
}
function createStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
},
formSection: {
marginBottom: 24,
},
inputContainer: {
marginBottom: 24,
},
inputLabel: {
fontSize: 14,
fontWeight: '500',
color: colors.text?.secondary || '#666',
marginBottom: 10,
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: 14,
paddingHorizontal: 18,
height: 56,
borderWidth: 1,
borderColor: 'transparent',
},
inputWrapperError: {
borderColor: colors.error?.main || '#FF4444',
},
input: {
flex: 1,
fontSize: 16,
color: colors.text?.primary || '#333',
height: 56,
},
checkIcon: {
marginLeft: 8,
},
errorText: {
fontSize: 12,
color: colors.error?.main || '#FF4444',
marginTop: 6,
marginLeft: 4,
},
actionButton: {
height: 56,
borderRadius: 14,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
marginTop: 8,
},
actionButtonDisabled: {
opacity: 0.6,
},
actionButtonText: {
fontSize: 17,
fontWeight: '600',
color: colors.text.inverse,
},
});
}
export default RegisterStep1Email;