|
|
import React, { useEffect, useState, useRef } from 'react'; |
|
|
import { |
|
|
View, |
|
|
Text, |
|
|
StyleSheet, |
|
|
TouchableOpacity, |
|
|
StatusBar, |
|
|
Platform, |
|
|
BackHandler, |
|
|
TextInput, |
|
|
Animated, |
|
|
Easing, |
|
|
FlatList, |
|
|
Keyboard, |
|
|
Modal, |
|
|
InteractionManager, |
|
|
} from 'react-native'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
import { useNavigation } from '@react-navigation/native'; |
|
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; |
|
|
import { RootStackParamList } from '../navigation/types'; |
|
|
import { Country, countries } from '../constants/countries'; |
|
|
import { useAuth } from '../contexts/AuthContext'; |
|
|
|
|
|
// 常见邮箱后缀列表 |
|
|
const EMAIL_DOMAINS = [ |
|
|
'gmail.com', |
|
|
'yahoo.com', |
|
|
'hotmail.com', |
|
|
'outlook.com', |
|
|
'icloud.com', |
|
|
'mail.com', |
|
|
'protonmail.com', |
|
|
'qq.com', |
|
|
'163.com', |
|
|
'126.com', |
|
|
]; |
|
|
|
|
|
type LoginScreenProps = { |
|
|
onClose?: () => void; |
|
|
isModal?: boolean; |
|
|
}; |
|
|
|
|
|
export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => { |
|
|
const { t } = useTranslation(); |
|
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>(); |
|
|
const { login } = useAuth(); |
|
|
|
|
|
// 邮箱登录状态 |
|
|
const [showEmailLogin, setShowEmailLogin] = useState(false); |
|
|
const [email, setEmail] = useState(''); |
|
|
const [emailPassword, setEmailPassword] = useState(''); |
|
|
const [emailPasswordError, setEmailPasswordError] = useState(false); |
|
|
const [showSuggestions, setShowSuggestions] = useState(false); |
|
|
const [suggestions, setSuggestions] = useState<string[]>([]); |
|
|
|
|
|
// 手机号登录状态 |
|
|
const [showPhoneLogin, setShowPhoneLogin] = useState(false); |
|
|
const [phoneNumber, setPhoneNumber] = useState(''); |
|
|
const [password, setPassword] = useState(''); |
|
|
const [passwordError, setPasswordError] = useState(false); |
|
|
const [showCountryModal, setShowCountryModal] = useState(false); |
|
|
const [selectedCountry, setSelectedCountry] = useState<Country>({ |
|
|
name: 'Republic of Congo', |
|
|
code: 'CG', |
|
|
flag: '🇨🇬', |
|
|
userCount: 300000, |
|
|
phoneCode: '+242' |
|
|
}); |
|
|
|
|
|
// 动画值 |
|
|
const emailSlideAnim = useRef(new Animated.Value(400)).current; |
|
|
const phoneSlideAnim = useRef(new Animated.Value(400)).current; |
|
|
const [emailLoginMounted, setEmailLoginMounted] = useState(false); |
|
|
const [phoneLoginMounted, setPhoneLoginMounted] = useState(false); |
|
|
|
|
|
// 处理邮箱输入变化,生成邮箱建议 |
|
|
const handleEmailChange = (text: string) => { |
|
|
setEmail(text); |
|
|
|
|
|
// 检查是否包含@符号 |
|
|
if (text.includes('@')) { |
|
|
const [username, domain] = text.split('@'); |
|
|
|
|
|
if (domain) { |
|
|
// 如果已经输入了部分域名,过滤匹配的域名 |
|
|
const filteredDomains = EMAIL_DOMAINS.filter(item => |
|
|
item.toLowerCase().startsWith(domain.toLowerCase()) |
|
|
); |
|
|
|
|
|
// 生成完整的邮箱建议列表 |
|
|
const emailSuggestions = filteredDomains.map(d => `${username}@${d}`); |
|
|
setSuggestions(emailSuggestions); |
|
|
setShowSuggestions(emailSuggestions.length > 0); |
|
|
} else { |
|
|
// 如果只输入了@,显示所有域名建议 |
|
|
const emailSuggestions = EMAIL_DOMAINS.map(d => `${username}@${d}`); |
|
|
setSuggestions(emailSuggestions); |
|
|
setShowSuggestions(true); |
|
|
} |
|
|
} else if (text.length > 0) { |
|
|
// 没有@符号但有输入内容,显示常见邮箱后缀建议 |
|
|
const emailSuggestions = EMAIL_DOMAINS.map(d => `${text}@${d}`); |
|
|
setSuggestions(emailSuggestions); |
|
|
setShowSuggestions(true); |
|
|
} else { |
|
|
// 输入为空,不显示建议 |
|
|
setShowSuggestions(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
// 选择一个邮箱建议 |
|
|
const handleSelectSuggestion = (suggestion: string) => { |
|
|
setEmail(suggestion); |
|
|
setShowSuggestions(false); |
|
|
}; |
|
|
|
|
|
// 验证邮箱格式 |
|
|
const isValidEmail = (email: string): boolean => { |
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
|
|
return emailRegex.test(email); |
|
|
}; |
|
|
|
|
|
// 渲染单个邮箱建议项 |
|
|
const renderSuggestionItem = ({ item }: { item: string }) => ( |
|
|
<TouchableOpacity |
|
|
style={styles.suggestionItem} |
|
|
onPress={() => handleSelectSuggestion(item)} |
|
|
> |
|
|
<Text style={styles.suggestionText}>{item}</Text> |
|
|
</TouchableOpacity> |
|
|
); |
|
|
|
|
|
// 选择国家 |
|
|
const handleCountrySelect = (country: Country) => { |
|
|
setSelectedCountry(country); |
|
|
setShowCountryModal(false); |
|
|
}; |
|
|
|
|
|
// 渲染国家列表项 - 添加性能优化 |
|
|
const renderCountryItem = React.useCallback(({ item }: { item: Country }) => ( |
|
|
<TouchableOpacity |
|
|
style={styles.countryItem} |
|
|
onPress={() => handleCountrySelect(item)} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.countryItemFlag}>{item.flag}</Text> |
|
|
<View style={styles.countryItemContent}> |
|
|
<Text style={styles.countryItemName}>{item.name}</Text> |
|
|
<Text style={styles.countryItemCode}>{item.phoneCode}</Text> |
|
|
</View> |
|
|
</TouchableOpacity> |
|
|
), []); |
|
|
|
|
|
// 添加Android返回按钮处理 |
|
|
useEffect(() => { |
|
|
const backAction = () => { |
|
|
if (showEmailLogin) { |
|
|
closeEmailLogin(); |
|
|
return true; |
|
|
} |
|
|
if (showPhoneLogin) { |
|
|
closePhoneLogin(); |
|
|
return true; |
|
|
} |
|
|
if (showCountryModal) { |
|
|
setShowCountryModal(false); |
|
|
return true; |
|
|
} |
|
|
handleClose(); |
|
|
return true; |
|
|
}; |
|
|
|
|
|
const backHandler = BackHandler.addEventListener( |
|
|
'hardwareBackPress', |
|
|
backAction |
|
|
); |
|
|
|
|
|
return () => backHandler.remove(); |
|
|
}, [showEmailLogin, showPhoneLogin, showCountryModal]); |
|
|
|
|
|
// 初始化动画配置 |
|
|
useEffect(() => { |
|
|
// React Native Animated API不支持直接设置useNativeDriver |
|
|
// 这些配置会在动画函数中使用 |
|
|
}, []); |
|
|
|
|
|
// 打开邮箱登录面板 |
|
|
const openEmailLogin = () => { |
|
|
emailSlideAnim.setValue(400); |
|
|
setEmailLoginMounted(true); |
|
|
|
|
|
// 等待组件挂载后启动动画 |
|
|
requestAnimationFrame(() => { |
|
|
setShowEmailLogin(true); |
|
|
Animated.spring(emailSlideAnim, { |
|
|
toValue: 0, |
|
|
useNativeDriver: true, |
|
|
friction: 8, |
|
|
tension: 40, |
|
|
restDisplacementThreshold: 0.01, |
|
|
restSpeedThreshold: 0.01, |
|
|
}).start(); |
|
|
}); |
|
|
}; |
|
|
|
|
|
// 关闭邮箱登录面板 |
|
|
const closeEmailLogin = () => { |
|
|
Keyboard.dismiss(); |
|
|
|
|
|
// 使用更简单的配置减少计算复杂度 |
|
|
Animated.timing(emailSlideAnim, { |
|
|
toValue: 400, |
|
|
duration: 200, |
|
|
useNativeDriver: true, |
|
|
easing: Easing.cubic, |
|
|
}).start(); |
|
|
|
|
|
// 先隐藏建议列表,减少布局计算 |
|
|
setShowSuggestions(false); |
|
|
|
|
|
// 使用InteractionManager确保动画完成后再执行耗时操作 |
|
|
InteractionManager.runAfterInteractions(() => { |
|
|
setShowEmailLogin(false); |
|
|
setEmail(''); |
|
|
setEmailPassword(''); |
|
|
setEmailPasswordError(false); |
|
|
|
|
|
// 延迟卸载组件 |
|
|
setTimeout(() => { |
|
|
setEmailLoginMounted(false); |
|
|
}, 50); |
|
|
}); |
|
|
}; |
|
|
|
|
|
// 打开手机号登录面板 |
|
|
const openPhoneLogin = () => { |
|
|
phoneSlideAnim.setValue(400); |
|
|
setPhoneLoginMounted(true); |
|
|
|
|
|
// 等待组件挂载后启动动画 |
|
|
requestAnimationFrame(() => { |
|
|
setShowPhoneLogin(true); |
|
|
Animated.spring(phoneSlideAnim, { |
|
|
toValue: 0, |
|
|
useNativeDriver: true, |
|
|
friction: 8, |
|
|
tension: 40, |
|
|
restDisplacementThreshold: 0.01, |
|
|
restSpeedThreshold: 0.01, |
|
|
}).start(); |
|
|
}); |
|
|
}; |
|
|
|
|
|
// 关闭手机号登录面板 |
|
|
const closePhoneLogin = () => { |
|
|
Keyboard.dismiss(); |
|
|
|
|
|
// 使用更简单的配置减少计算复杂度 |
|
|
Animated.timing(phoneSlideAnim, { |
|
|
toValue: 400, |
|
|
duration: 200, |
|
|
useNativeDriver: true, |
|
|
easing: Easing.cubic, |
|
|
}).start(); |
|
|
|
|
|
// 使用InteractionManager确保动画完成后再执行耗时操作 |
|
|
InteractionManager.runAfterInteractions(() => { |
|
|
setShowPhoneLogin(false); |
|
|
setPhoneNumber(''); |
|
|
|
|
|
// 延迟卸载组件 |
|
|
setTimeout(() => { |
|
|
setPhoneLoginMounted(false); |
|
|
}, 50); |
|
|
}); |
|
|
}; |
|
|
|
|
|
// 修改关闭按钮处理函数 |
|
|
const handleClose = () => { |
|
|
if (isModal && onClose) { |
|
|
onClose(); |
|
|
} else { |
|
|
navigation.goBack(); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleGoogleLogin = () => { |
|
|
// 处理Google登录 |
|
|
}; |
|
|
|
|
|
const handleFacebookLogin = () => { |
|
|
// 处理Facebook登录 |
|
|
}; |
|
|
|
|
|
const handleAppleLogin = () => { |
|
|
// 处理Apple登录 |
|
|
}; |
|
|
|
|
|
const handleInstagramLogin = () => { |
|
|
// 处理Instagram登录 |
|
|
}; |
|
|
|
|
|
const handleEmailLogin = () => { |
|
|
// 处理邮箱登录 - 显示邮箱登录面板 |
|
|
openEmailLogin(); |
|
|
}; |
|
|
|
|
|
const handleEmailContinue = async () => { |
|
|
if (emailPassword === '123') { |
|
|
setEmailPasswordError(false); |
|
|
await login(); |
|
|
closeEmailLogin(); |
|
|
navigation.replace('MainTabs'); |
|
|
} else { |
|
|
setEmailPasswordError(true); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handlePhoneLogin = () => { |
|
|
// 处理手机号登录 - 显示手机号登录面板 |
|
|
openPhoneLogin(); |
|
|
}; |
|
|
|
|
|
const handlePhoneVerificationSuccess = async () => { |
|
|
await login(); |
|
|
closePhoneLogin(); |
|
|
navigation.replace('MainTabs'); |
|
|
}; |
|
|
|
|
|
const handlePhoneContinue = async () => { |
|
|
if (phoneNumber.trim() && password) { |
|
|
if (password === '123') { |
|
|
await login(); |
|
|
closePhoneLogin(); |
|
|
navigation.replace('MainTabs'); |
|
|
} else { |
|
|
setPasswordError(true); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleForgotPassword = () => { |
|
|
// 处理忘记密码 |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<View style={styles.container}> |
|
|
<StatusBar barStyle="light-content" backgroundColor="#0066FF" /> |
|
|
|
|
|
{/* 顶部蓝色背景区域 */} |
|
|
<View style={styles.blueHeader}> |
|
|
<Text style={styles.logo}>brainnel</Text> |
|
|
<View style={styles.features}> |
|
|
<View style={styles.featureItem}> |
|
|
<View style={styles.featureIconContainer}> |
|
|
<Text style={styles.featureIcon}>💰</Text> |
|
|
</View> |
|
|
<Text style={styles.featureText}>{t('wholesalePrice')}</Text> |
|
|
</View> |
|
|
<View style={styles.featureItem}> |
|
|
<View style={styles.featureIconContainer}> |
|
|
<Text style={styles.featureIcon}>🚚</Text> |
|
|
</View> |
|
|
<Text style={styles.featureText}>{t('fastShipping')}</Text> |
|
|
</View> |
|
|
</View> |
|
|
</View> |
|
|
|
|
|
{/* 登录区域 */} |
|
|
<View style={styles.loginContainer}> |
|
|
<TouchableOpacity |
|
|
style={styles.closeButton} |
|
|
onPress={handleClose} |
|
|
> |
|
|
<Text style={styles.closeButtonText}>×</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
<View style={styles.titleContainer}> |
|
|
{/* <Text style={styles.title}>Login For brainnel</Text> */} |
|
|
<Text style={styles.subtitle}>{t('loginSubtitle')}</Text> |
|
|
</View> |
|
|
|
|
|
{/* 登录按钮 */} |
|
|
<TouchableOpacity |
|
|
style={styles.loginButton} |
|
|
onPress={handleGoogleLogin} |
|
|
> |
|
|
<View style={styles.loginButtonIcon}> |
|
|
<Text>G</Text> |
|
|
</View> |
|
|
<Text style={styles.loginButtonText}>{t('continueWithGoogle')}</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
<TouchableOpacity |
|
|
style={styles.loginButton} |
|
|
onPress={handleFacebookLogin} |
|
|
> |
|
|
<View style={[styles.loginButtonIcon, styles.facebookIcon]}> |
|
|
<Text style={{color: '#fff'}}>f</Text> |
|
|
</View> |
|
|
<Text style={styles.loginButtonText}>{t('continueWithFacebook')}</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
{Platform.OS === 'ios' && ( |
|
|
<TouchableOpacity |
|
|
style={styles.loginButton} |
|
|
onPress={handleAppleLogin} |
|
|
> |
|
|
<View style={[styles.loginButtonIcon, styles.appleIconBg]}> |
|
|
<Text>🍎</Text> |
|
|
</View> |
|
|
<Text style={styles.loginButtonText}>{t('continueWithApple')}</Text> |
|
|
</TouchableOpacity> |
|
|
)} |
|
|
|
|
|
<TouchableOpacity |
|
|
style={styles.loginButton} |
|
|
onPress={handleInstagramLogin} |
|
|
> |
|
|
<View style={[styles.loginButtonIcon, styles.instagramIcon]}> |
|
|
<Text>📷</Text> |
|
|
</View> |
|
|
<Text style={styles.loginButtonText}>{t('continueWithInstagram')}</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
<TouchableOpacity |
|
|
style={styles.loginButton} |
|
|
onPress={handleEmailLogin} |
|
|
> |
|
|
<View style={styles.loginButtonIcon}> |
|
|
<Text>✉️</Text> |
|
|
</View> |
|
|
<Text style={styles.loginButtonText}>{t('continueWithEmail')}</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
<TouchableOpacity |
|
|
style={styles.loginButton} |
|
|
onPress={handlePhoneLogin} |
|
|
> |
|
|
<View style={styles.loginButtonIcon}> |
|
|
<Text>📱</Text> |
|
|
</View> |
|
|
<Text style={styles.loginButtonText}>{t('continueWithPhone')}</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
{/* 忘记密码 */} |
|
|
<TouchableOpacity |
|
|
style={styles.forgotPassword} |
|
|
onPress={handleForgotPassword} |
|
|
> |
|
|
<Text style={styles.forgotPasswordText}>{t('forgotPassword')}</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
{/* 服务条款 */} |
|
|
<View style={styles.termsContainer}> |
|
|
<Text style={styles.terms}> |
|
|
{t('termsText')} <Text style={styles.link}>{t('termsOfUse')}</Text> |
|
|
</Text> |
|
|
<Text style={styles.terms}> |
|
|
{t('and')} <Text style={styles.link}>{t('privacyPolicy')}</Text> |
|
|
</Text> |
|
|
</View> |
|
|
</View> |
|
|
|
|
|
{/* 邮箱登录面板 - 从底部滑出 */} |
|
|
{emailLoginMounted && ( |
|
|
<Animated.View |
|
|
style={[ |
|
|
styles.emailLoginContainer, |
|
|
{ |
|
|
transform: [{ translateY: emailSlideAnim }], |
|
|
opacity: emailSlideAnim.interpolate({ |
|
|
inputRange: [0, 400], |
|
|
outputRange: [1, 0.7] |
|
|
}) |
|
|
} |
|
|
]} |
|
|
removeClippedSubviews={true} |
|
|
collapsable={false} |
|
|
> |
|
|
<View style={styles.emailLoginHeader}> |
|
|
<TouchableOpacity |
|
|
style={styles.emailLoginCloseButton} |
|
|
onPress={closeEmailLogin} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.emailLoginCloseButtonText}>✕</Text> |
|
|
</TouchableOpacity> |
|
|
<Text style={styles.emailLoginTitle}>{t('logInOrSignUp')}</Text> |
|
|
</View> |
|
|
|
|
|
<View style={styles.emailLoginContent}> |
|
|
<View style={styles.emailInputContainer}> |
|
|
<TextInput |
|
|
style={styles.emailInput} |
|
|
placeholder={t('pleaseEnterEmail')} |
|
|
value={email} |
|
|
onChangeText={(text) => { |
|
|
handleEmailChange(text); |
|
|
setEmailPasswordError(false); |
|
|
}} |
|
|
keyboardType="email-address" |
|
|
autoCapitalize="none" |
|
|
autoCorrect={false} |
|
|
autoFocus |
|
|
maxLength={50} |
|
|
/> |
|
|
{email.length > 0 && ( |
|
|
<TouchableOpacity |
|
|
style={styles.emailClearButton} |
|
|
onPress={() => { |
|
|
setEmail(''); |
|
|
setShowSuggestions(false); |
|
|
setEmailPasswordError(false); |
|
|
}} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.emailClearButtonText}>✕</Text> |
|
|
</TouchableOpacity> |
|
|
)} |
|
|
</View> |
|
|
|
|
|
{/* 邮箱后缀建议列表 */} |
|
|
{showSuggestions && ( |
|
|
<View style={styles.suggestionsContainer}> |
|
|
<FlatList |
|
|
data={suggestions.slice(0, 5)} // 限制最多显示5个建议 |
|
|
renderItem={renderSuggestionItem} |
|
|
keyExtractor={(item) => item} |
|
|
style={styles.suggestionsList} |
|
|
keyboardShouldPersistTaps="handled" |
|
|
removeClippedSubviews={true} |
|
|
initialNumToRender={5} |
|
|
maxToRenderPerBatch={5} |
|
|
windowSize={5} |
|
|
getItemLayout={(data, index) => ( |
|
|
{length: 44, offset: 44 * index, index} |
|
|
)} |
|
|
/> |
|
|
</View> |
|
|
)} |
|
|
|
|
|
{/* 密码输入框 */} |
|
|
<View style={[ |
|
|
styles.phoneInputContainer, |
|
|
emailPasswordError && styles.passwordErrorContainer |
|
|
]}> |
|
|
<TextInput |
|
|
style={styles.passwordInput} |
|
|
placeholder={t('enterPassword')} |
|
|
value={emailPassword} |
|
|
onChangeText={(text) => { |
|
|
setEmailPassword(text); |
|
|
setEmailPasswordError(false); |
|
|
}} |
|
|
secureTextEntry={true} |
|
|
autoCapitalize="none" |
|
|
/> |
|
|
{emailPasswordError && ( |
|
|
<View style={styles.passwordErrorIcon}> |
|
|
<Text style={styles.passwordErrorIconText}>!</Text> |
|
|
</View> |
|
|
)} |
|
|
</View> |
|
|
|
|
|
{/* 密码错误提示 */} |
|
|
{emailPasswordError && ( |
|
|
<> |
|
|
<Text style={styles.passwordErrorText}> |
|
|
{t('passwordIncorrect')} |
|
|
</Text> |
|
|
|
|
|
<TouchableOpacity |
|
|
style={styles.forgotPasswordLink} |
|
|
onPress={handleForgotPassword} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.forgotPasswordLinkText}> |
|
|
{t('forgotPassword')} |
|
|
</Text> |
|
|
</TouchableOpacity> |
|
|
</> |
|
|
)} |
|
|
|
|
|
<TouchableOpacity |
|
|
style={[ |
|
|
styles.emailContinueButton, |
|
|
(!isValidEmail(email) || !emailPassword) && styles.emailDisabledButton |
|
|
]} |
|
|
onPress={handleEmailContinue} |
|
|
disabled={!isValidEmail(email) || !emailPassword} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.emailContinueButtonText}>{t('continue')}</Text> |
|
|
</TouchableOpacity> |
|
|
</View> |
|
|
</Animated.View> |
|
|
)} |
|
|
|
|
|
{/* 手机号登录面板 - 从底部滑出 */} |
|
|
{phoneLoginMounted && ( |
|
|
<Animated.View |
|
|
style={[ |
|
|
styles.phoneLoginContainer, |
|
|
{ |
|
|
transform: [{ translateY: phoneSlideAnim }], |
|
|
opacity: phoneSlideAnim.interpolate({ |
|
|
inputRange: [0, 400], |
|
|
outputRange: [1, 0.7] |
|
|
}) |
|
|
} |
|
|
]} |
|
|
removeClippedSubviews={true} |
|
|
collapsable={false} |
|
|
> |
|
|
<View style={styles.phoneLoginHeader}> |
|
|
<TouchableOpacity |
|
|
style={styles.phoneLoginCloseButton} |
|
|
onPress={closePhoneLogin} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.phoneLoginCloseButtonText}>✕</Text> |
|
|
</TouchableOpacity> |
|
|
<Text style={styles.phoneLoginTitle}>{t('logInOrSignUp')}</Text> |
|
|
</View> |
|
|
|
|
|
<View style={styles.phoneLoginContent}> |
|
|
<View style={styles.phoneInputContainer}> |
|
|
<TouchableOpacity |
|
|
style={styles.countrySelector} |
|
|
onPress={() => setShowCountryModal(true)} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.countryFlag}>{selectedCountry.flag}</Text> |
|
|
<Text style={styles.countryCode}>{selectedCountry.phoneCode}</Text> |
|
|
<Text style={styles.downArrow}>▼</Text> |
|
|
</TouchableOpacity> |
|
|
|
|
|
<TextInput |
|
|
style={styles.phoneInput} |
|
|
placeholder={t('phoneNumber')} |
|
|
value={phoneNumber} |
|
|
onChangeText={(text) => { |
|
|
setPhoneNumber(text); |
|
|
setPasswordError(false); |
|
|
}} |
|
|
keyboardType="phone-pad" |
|
|
autoFocus |
|
|
maxLength={15} |
|
|
/> |
|
|
{phoneNumber.length > 0 && ( |
|
|
<TouchableOpacity |
|
|
style={styles.phoneClearButton} |
|
|
onPress={() => { |
|
|
setPhoneNumber(''); |
|
|
setPasswordError(false); |
|
|
}} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.phoneClearButtonText}>✕</Text> |
|
|
</TouchableOpacity> |
|
|
)} |
|
|
</View> |
|
|
|
|
|
{/* 密码输入框 */} |
|
|
<View style={[ |
|
|
styles.phoneInputContainer, |
|
|
passwordError && styles.passwordErrorContainer |
|
|
]}> |
|
|
<TextInput |
|
|
style={styles.passwordInput} |
|
|
placeholder={t('enterPassword')} |
|
|
value={password} |
|
|
onChangeText={(text) => { |
|
|
setPassword(text); |
|
|
setPasswordError(false); |
|
|
}} |
|
|
secureTextEntry={true} |
|
|
autoCapitalize="none" |
|
|
/> |
|
|
{passwordError && ( |
|
|
<View style={styles.passwordErrorIcon}> |
|
|
<Text style={styles.passwordErrorIconText}>!</Text> |
|
|
</View> |
|
|
)} |
|
|
</View> |
|
|
|
|
|
{/* 密码错误提示 */} |
|
|
{passwordError && ( |
|
|
<> |
|
|
<Text style={styles.passwordErrorText}> |
|
|
{t('passwordIncorrect')} |
|
|
</Text> |
|
|
|
|
|
<TouchableOpacity |
|
|
style={styles.forgotPasswordLink} |
|
|
onPress={handleForgotPassword} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.forgotPasswordLinkText}> |
|
|
{t('forgotPassword')} |
|
|
</Text> |
|
|
</TouchableOpacity> |
|
|
</> |
|
|
)} |
|
|
|
|
|
<Text style={[ |
|
|
styles.phoneInfoText, |
|
|
passwordError && { marginTop: 5 } |
|
|
]}> |
|
|
|
|
|
</Text> |
|
|
|
|
|
<TouchableOpacity |
|
|
style={[ |
|
|
styles.phoneContinueButton, |
|
|
(!phoneNumber.trim() || !password) && styles.phoneDisabledButton |
|
|
]} |
|
|
onPress={handlePhoneContinue} |
|
|
disabled={!phoneNumber.trim() || !password} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.phoneContinueButtonText}>{t('continue')}</Text> |
|
|
</TouchableOpacity> |
|
|
</View> |
|
|
</Animated.View> |
|
|
)} |
|
|
|
|
|
{/* 国家选择模态框 */} |
|
|
<Modal |
|
|
visible={showCountryModal} |
|
|
animationType="slide" |
|
|
transparent={true} |
|
|
onRequestClose={() => setShowCountryModal(false)} |
|
|
hardwareAccelerated={true} |
|
|
statusBarTranslucent={true} |
|
|
presentationStyle="overFullScreen" |
|
|
> |
|
|
<View style={styles.countryModalContainer}> |
|
|
<View style={styles.countryModalContent}> |
|
|
<View style={styles.countryModalHeader}> |
|
|
<TouchableOpacity |
|
|
style={styles.countryModalCloseButton} |
|
|
onPress={() => setShowCountryModal(false)} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={styles.countryModalCloseButtonText}>✕</Text> |
|
|
</TouchableOpacity> |
|
|
<Text style={styles.countryModalTitle}>{t('selectCountry')}</Text> |
|
|
</View> |
|
|
<FlatList |
|
|
data={countries} |
|
|
renderItem={renderCountryItem} |
|
|
keyExtractor={(item) => item.code} |
|
|
style={styles.countryList} |
|
|
showsVerticalScrollIndicator={false} |
|
|
removeClippedSubviews={true} |
|
|
initialNumToRender={10} |
|
|
maxToRenderPerBatch={10} |
|
|
windowSize={10} |
|
|
getItemLayout={(data, index) => ( |
|
|
{length: 69, offset: 69 * index, index} |
|
|
)} |
|
|
/> |
|
|
</View> |
|
|
</View> |
|
|
</Modal> |
|
|
</View> |
|
|
); |
|
|
}; |
|
|
|
|
|
const styles = StyleSheet.create({ |
|
|
container: { |
|
|
flex: 1, |
|
|
backgroundColor: '#fff', |
|
|
}, |
|
|
closeButton: { |
|
|
position: 'absolute', |
|
|
top: 15, |
|
|
left: 10, |
|
|
width: 24, |
|
|
height: 24, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
zIndex: 1, |
|
|
}, |
|
|
closeButtonText: { |
|
|
color: '#000', |
|
|
fontSize: 24, |
|
|
fontWeight: '300', |
|
|
}, |
|
|
blueHeader: { |
|
|
backgroundColor: '#0066FF', |
|
|
paddingHorizontal: 20, |
|
|
paddingBottom: 20, |
|
|
paddingTop: Platform.OS === 'ios' ? 60 : 40, |
|
|
borderBottomLeftRadius: 24, |
|
|
borderBottomRightRadius: 24, |
|
|
}, |
|
|
logo: { |
|
|
fontSize: 28, |
|
|
fontWeight: 'bold', |
|
|
color: '#fff', |
|
|
marginBottom: 15, |
|
|
}, |
|
|
features: { |
|
|
flexDirection: 'row', |
|
|
gap: 16, |
|
|
}, |
|
|
featureItem: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
gap: 8, |
|
|
}, |
|
|
featureIconContainer: { |
|
|
backgroundColor: 'rgba(255, 255, 255, 0.2)', |
|
|
borderRadius: 8, |
|
|
width: 24, |
|
|
height: 24, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
featureIcon: { |
|
|
fontSize: 12, |
|
|
}, |
|
|
featureText: { |
|
|
fontSize: 14, |
|
|
color: '#fff', |
|
|
}, |
|
|
loginContainer: { |
|
|
flex: 1, |
|
|
paddingHorizontal: 20, |
|
|
paddingTop: Platform.OS === 'ios' ? 40 : 20, |
|
|
}, |
|
|
titleContainer: { |
|
|
alignItems: 'center', |
|
|
marginBottom: 30, |
|
|
paddingTop: 20, |
|
|
position: 'relative', |
|
|
}, |
|
|
title: { |
|
|
fontSize: 24, |
|
|
fontWeight: 'bold', |
|
|
color: '#000', |
|
|
marginBottom: 8, |
|
|
textAlign: 'center', |
|
|
}, |
|
|
subtitle: { |
|
|
fontSize: 14, |
|
|
color: '#666', |
|
|
textAlign: 'center', |
|
|
}, |
|
|
loginButton: { |
|
|
flexDirection: 'row', |
|
|
height: 50, |
|
|
borderRadius: 25, |
|
|
borderWidth: 1, |
|
|
borderColor: '#E1E1E1', |
|
|
alignItems: 'center', |
|
|
marginBottom: 12, |
|
|
paddingHorizontal: 16, |
|
|
backgroundColor: '#fff', |
|
|
}, |
|
|
loginButtonIcon: { |
|
|
width: 24, |
|
|
height: 24, |
|
|
borderRadius: 12, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
marginRight: 16, |
|
|
}, |
|
|
facebookIcon: { |
|
|
backgroundColor: '#3b5998', |
|
|
}, |
|
|
appleIconBg: { |
|
|
backgroundColor: '#000', |
|
|
}, |
|
|
instagramIcon: { |
|
|
backgroundColor: '#E1306C', |
|
|
}, |
|
|
loginButtonText: { |
|
|
flex: 1, |
|
|
fontSize: 16, |
|
|
color: '#000', |
|
|
textAlign: 'center', |
|
|
marginRight: 16, |
|
|
}, |
|
|
forgotPassword: { |
|
|
alignItems: 'center', |
|
|
marginVertical: 20, |
|
|
}, |
|
|
forgotPasswordText: { |
|
|
color: '#0066FF', |
|
|
fontSize: 14, |
|
|
}, |
|
|
termsContainer: { |
|
|
alignItems: 'center', |
|
|
marginTop: 10, |
|
|
}, |
|
|
terms: { |
|
|
fontSize: 12, |
|
|
color: '#666', |
|
|
textAlign: 'center', |
|
|
lineHeight: 18, |
|
|
}, |
|
|
link: { |
|
|
color: '#0066FF', |
|
|
}, |
|
|
|
|
|
// 邮箱登录样式 |
|
|
emailLoginContainer: { |
|
|
position: 'absolute', |
|
|
top: Platform.OS === 'ios' ? 60 : 40, |
|
|
height: Platform.OS === 'ios' ? 'auto' : '100%', |
|
|
bottom: 0, |
|
|
left: 0, |
|
|
right: 0, |
|
|
backgroundColor: '#fff', |
|
|
borderTopLeftRadius: 20, |
|
|
borderTopRightRadius: 20, |
|
|
shadowColor: '#000', |
|
|
shadowOffset: { width: 0, height: -2 }, |
|
|
shadowOpacity: 0.1, |
|
|
shadowRadius: 5, |
|
|
elevation: 5, |
|
|
zIndex: 10, |
|
|
}, |
|
|
emailLoginHeader: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
paddingTop: 20, |
|
|
paddingHorizontal: 16, |
|
|
paddingBottom: 15, |
|
|
borderBottomWidth: 1, |
|
|
borderBottomColor: '#f0f0f0', |
|
|
}, |
|
|
emailLoginCloseButton: { |
|
|
padding: 8, |
|
|
width: 36, |
|
|
height: 36, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
emailLoginCloseButtonText: { |
|
|
fontSize: 18, |
|
|
color: '#000', |
|
|
}, |
|
|
emailLoginTitle: { |
|
|
flex: 1, |
|
|
fontSize: 18, |
|
|
fontWeight: '600', |
|
|
color: '#000', |
|
|
textAlign: 'center', |
|
|
marginRight: 36, |
|
|
}, |
|
|
emailLoginContent: { |
|
|
padding: 20, |
|
|
paddingBottom: Platform.OS === 'ios' ? 50 : 30, |
|
|
flex: 1, |
|
|
}, |
|
|
emailInputContainer: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
borderWidth: 1, |
|
|
borderColor: '#E1E1E1', |
|
|
borderRadius: 25, |
|
|
height: 50, |
|
|
marginBottom: 20, |
|
|
}, |
|
|
emailInput: { |
|
|
flex: 1, |
|
|
height: '100%', |
|
|
paddingHorizontal: 16, |
|
|
fontSize: 16, |
|
|
paddingRight: 36, |
|
|
}, |
|
|
emailClearButton: { |
|
|
position: 'absolute', |
|
|
right: 12, |
|
|
top: '50%', |
|
|
transform: [{ translateY: -12 }], |
|
|
height: 24, |
|
|
width: 24, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
emailClearButtonText: { |
|
|
fontSize: 16, |
|
|
color: '#999', |
|
|
fontWeight: '500', |
|
|
textAlign: 'center', |
|
|
}, |
|
|
suggestionsContainer: { |
|
|
borderWidth: 1, |
|
|
borderColor: '#E1E1E1', |
|
|
borderRadius: 10, |
|
|
marginTop: -10, |
|
|
marginBottom: 20, |
|
|
maxHeight: 200, |
|
|
backgroundColor: '#fff', |
|
|
}, |
|
|
suggestionsList: { |
|
|
padding: 8, |
|
|
}, |
|
|
suggestionItem: { |
|
|
paddingVertical: 12, |
|
|
paddingHorizontal: 16, |
|
|
borderBottomWidth: 1, |
|
|
borderBottomColor: '#F0F0F0', |
|
|
}, |
|
|
suggestionText: { |
|
|
fontSize: 16, |
|
|
color: '#333', |
|
|
}, |
|
|
emailContinueButton: { |
|
|
height: 50, |
|
|
backgroundColor: '#0039CB', |
|
|
borderRadius: 25, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
marginTop: 20, |
|
|
}, |
|
|
emailDisabledButton: { |
|
|
backgroundColor: '#CCCCCC', |
|
|
}, |
|
|
emailContinueButtonText: { |
|
|
color: '#fff', |
|
|
fontSize: 16, |
|
|
fontWeight: '600', |
|
|
}, |
|
|
|
|
|
// 手机号登录样式 |
|
|
phoneLoginContainer: { |
|
|
position: 'absolute', |
|
|
top: Platform.OS === 'ios' ? 60 : 40, |
|
|
height: Platform.OS === 'ios' ? 'auto' : '100%', |
|
|
bottom: 0, |
|
|
left: 0, |
|
|
right: 0, |
|
|
backgroundColor: '#fff', |
|
|
borderTopLeftRadius: 20, |
|
|
borderTopRightRadius: 20, |
|
|
shadowColor: '#000', |
|
|
shadowOffset: { width: 0, height: -2 }, |
|
|
shadowOpacity: 0.1, |
|
|
shadowRadius: 5, |
|
|
elevation: 5, |
|
|
zIndex: 10, |
|
|
}, |
|
|
phoneLoginHeader: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
paddingTop: 20, |
|
|
paddingHorizontal: 16, |
|
|
paddingBottom: 15, |
|
|
borderBottomWidth: 1, |
|
|
borderBottomColor: '#f0f0f0', |
|
|
}, |
|
|
phoneLoginCloseButton: { |
|
|
padding: 8, |
|
|
width: 36, |
|
|
height: 36, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
phoneLoginCloseButtonText: { |
|
|
fontSize: 18, |
|
|
color: '#000', |
|
|
}, |
|
|
phoneLoginTitle: { |
|
|
flex: 1, |
|
|
fontSize: 18, |
|
|
fontWeight: '600', |
|
|
color: '#000', |
|
|
textAlign: 'center', |
|
|
marginRight: 36, |
|
|
}, |
|
|
phoneLoginContent: { |
|
|
padding: 20, |
|
|
paddingBottom: Platform.OS === 'ios' ? 50 : 30, |
|
|
flex: 1, |
|
|
}, |
|
|
phoneInputContainer: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
borderWidth: 1, |
|
|
borderColor: '#E1E1E1', |
|
|
borderRadius: 25, |
|
|
height: 50, |
|
|
marginBottom: 20, |
|
|
position: 'relative', |
|
|
}, |
|
|
countrySelector: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
paddingHorizontal: 12, |
|
|
borderRightWidth: 1, |
|
|
borderRightColor: '#E1E1E1', |
|
|
backgroundColor: '#F7F7F7', |
|
|
height: '100%', |
|
|
minWidth: 90, |
|
|
width: 90, |
|
|
justifyContent: 'center', |
|
|
}, |
|
|
countryFlag: { |
|
|
fontSize: 18, |
|
|
marginRight: 4, |
|
|
}, |
|
|
countryCode: { |
|
|
fontSize: 12, |
|
|
color: '#333', |
|
|
marginRight: 4, |
|
|
}, |
|
|
downArrow: { |
|
|
fontSize: 8, |
|
|
color: '#666', |
|
|
}, |
|
|
phoneInput: { |
|
|
flex: 1, |
|
|
height: '100%', |
|
|
paddingHorizontal: 16, |
|
|
fontSize: 16, |
|
|
paddingRight: 36, |
|
|
}, |
|
|
phoneClearButton: { |
|
|
position: 'absolute', |
|
|
right: 12, |
|
|
top: '50%', |
|
|
transform: [{ translateY: -12 }], |
|
|
height: 24, |
|
|
width: 24, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
phoneClearButtonText: { |
|
|
fontSize: 16, |
|
|
color: '#999', |
|
|
fontWeight: '500', |
|
|
textAlign: 'center', |
|
|
}, |
|
|
phoneInfoText: { |
|
|
fontSize: 14, |
|
|
color: '#666', |
|
|
marginBottom: 32, |
|
|
lineHeight: 20, |
|
|
}, |
|
|
phoneContinueButton: { |
|
|
height: 50, |
|
|
backgroundColor: '#0039CB', |
|
|
borderRadius: 25, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
phoneDisabledButton: { |
|
|
backgroundColor: '#CCCCCC', |
|
|
}, |
|
|
phoneContinueButtonText: { |
|
|
color: '#fff', |
|
|
fontSize: 16, |
|
|
fontWeight: '600', |
|
|
}, |
|
|
|
|
|
// 国家选择模态框样式 |
|
|
countryModalContainer: { |
|
|
flex: 1, |
|
|
backgroundColor: 'rgba(0,0,0,0.5)', |
|
|
justifyContent: 'flex-end', |
|
|
zIndex: 999, |
|
|
}, |
|
|
countryModalContent: { |
|
|
backgroundColor: '#fff', |
|
|
borderTopLeftRadius: 20, |
|
|
borderTopRightRadius: 20, |
|
|
maxHeight: '80%', |
|
|
zIndex: 1000, |
|
|
}, |
|
|
countryModalHeader: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
padding: 16, |
|
|
borderBottomWidth: 1, |
|
|
borderBottomColor: '#E5E5E5', |
|
|
}, |
|
|
countryModalCloseButton: { |
|
|
padding: 4, |
|
|
}, |
|
|
countryModalCloseButtonText: { |
|
|
fontSize: 18, |
|
|
color: '#999', |
|
|
}, |
|
|
countryModalTitle: { |
|
|
flex: 1, |
|
|
fontSize: 18, |
|
|
fontWeight: '600', |
|
|
textAlign: 'center', |
|
|
marginRight: 24, |
|
|
}, |
|
|
countryList: { |
|
|
padding: 8, |
|
|
}, |
|
|
countryItem: { |
|
|
flexDirection: 'row', |
|
|
alignItems: 'center', |
|
|
padding: 16, |
|
|
borderBottomWidth: 1, |
|
|
borderBottomColor: '#F0F0F0', |
|
|
}, |
|
|
countryItemFlag: { |
|
|
fontSize: 24, |
|
|
marginRight: 16, |
|
|
}, |
|
|
countryItemContent: { |
|
|
flex: 1, |
|
|
}, |
|
|
countryItemName: { |
|
|
fontSize: 16, |
|
|
color: '#333', |
|
|
}, |
|
|
countryItemCode: { |
|
|
fontSize: 14, |
|
|
color: '#666', |
|
|
marginTop: 4, |
|
|
}, |
|
|
// 密码输入框样式 |
|
|
passwordInput: { |
|
|
flex: 1, |
|
|
height: '100%', |
|
|
paddingHorizontal: 16, |
|
|
fontSize: 16, |
|
|
}, |
|
|
passwordErrorContainer: { |
|
|
borderColor: '#FF3B30', |
|
|
}, |
|
|
passwordErrorIcon: { |
|
|
position: 'absolute', |
|
|
right: 12, |
|
|
top: '50%', |
|
|
transform: [{ translateY: -12 }], |
|
|
width: 24, |
|
|
height: 24, |
|
|
backgroundColor: '#FF3B30', |
|
|
borderRadius: 12, |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
}, |
|
|
passwordErrorIconText: { |
|
|
color: 'white', |
|
|
fontWeight: 'bold', |
|
|
fontSize: 16, |
|
|
}, |
|
|
passwordErrorText: { |
|
|
color: '#FF3B30', |
|
|
fontSize: 14, |
|
|
marginTop: -12, |
|
|
marginBottom: 16, |
|
|
paddingHorizontal: 5, |
|
|
}, |
|
|
forgotPasswordLink: { |
|
|
alignItems: 'center', |
|
|
marginTop: 5, |
|
|
}, |
|
|
forgotPasswordLinkText: { |
|
|
color: '#0066FF', |
|
|
fontSize: 14, |
|
|
}, |
|
|
});
|