|
|
import React, { useEffect, useState, useRef } from "react"; |
|
|
import { |
|
|
View, |
|
|
Text, |
|
|
StyleSheet, |
|
|
TouchableOpacity, |
|
|
StatusBar, |
|
|
Platform, |
|
|
BackHandler, |
|
|
TextInput, |
|
|
Animated, |
|
|
Easing, |
|
|
FlatList, |
|
|
Keyboard, |
|
|
Modal, |
|
|
InteractionManager, |
|
|
Image, |
|
|
Alert |
|
|
} from "react-native"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useNavigation } from "@react-navigation/native"; |
|
|
import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; |
|
|
import { Country, countries } from "../constants/countries"; |
|
|
import { useAuth } from "../contexts/AuthContext"; |
|
|
import { settingApi } from "../services/api/setting"; |
|
|
import { userApi } from "../services/api/userApi"; |
|
|
import AsyncStorage from "@react-native-async-storage/async-storage"; |
|
|
|
|
|
import useUserStore from "../store/user"; |
|
|
|
|
|
type RootStackParamList = { |
|
|
Login: undefined; |
|
|
EmailLogin: undefined; |
|
|
MainTabs: { screen: string }; |
|
|
Google: undefined; |
|
|
Home: { screen: string }; |
|
|
}; |
|
|
|
|
|
// 常见邮箱后缀列表 |
|
|
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 { setSettings, settings, user, setUser } = useUserStore(); |
|
|
|
|
|
// 邮箱登录状态 |
|
|
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 = async () => { |
|
|
navigation.navigate("Google"); |
|
|
}; |
|
|
|
|
|
const handleFacebookLogin = () => { |
|
|
// 处理Facebook登录 |
|
|
}; |
|
|
|
|
|
const handleAppleLogin = () => { |
|
|
// 处理Apple登录 |
|
|
}; |
|
|
|
|
|
const handleInstagramLogin = () => { |
|
|
// 处理Instagram登录 |
|
|
}; |
|
|
|
|
|
const handleEmailLogin = () => { |
|
|
// 处理邮箱登录 - 显示邮箱登录面板 |
|
|
openEmailLogin(); |
|
|
}; |
|
|
|
|
|
// |
|
|
const handleEmailContinue = async () => { |
|
|
const params = { |
|
|
grant_type: "password", |
|
|
username: "lifei", |
|
|
password: "123456", |
|
|
client_id: "2", |
|
|
client_secret: "", |
|
|
scope: "", |
|
|
}; |
|
|
try { |
|
|
const res = await userApi.login(params); |
|
|
if (res.access_token) { |
|
|
const token = res.token_type + " " + res.access_token; |
|
|
await AsyncStorage.setItem("token", token); |
|
|
// const data = await settingApi.postFirstLogin(221); |
|
|
// setSettings(data); |
|
|
const user = await userApi.getProfile() |
|
|
setUser(user); |
|
|
navigation.navigate("MainTabs", { screen: "Home" }); |
|
|
} |
|
|
} catch (error) { |
|
|
Alert.alert(t("loginFailed")); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handlePhoneLogin = () => { |
|
|
// 处理手机号登录 - 显示手机号登录面板 |
|
|
openPhoneLogin(); |
|
|
}; |
|
|
|
|
|
const handlePhoneVerificationSuccess = async () => { |
|
|
await login(); |
|
|
closePhoneLogin(); |
|
|
navigation.replace("MainTabs", { screen: "Home" }); |
|
|
}; |
|
|
|
|
|
const handlePhoneContinue = async () => { |
|
|
if (phoneNumber.trim() && password) { |
|
|
if (password === "123") { |
|
|
await login(); |
|
|
closePhoneLogin(); |
|
|
navigation.replace("MainTabs", { screen: "Home" }); |
|
|
} 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}> |
|
|
<Image |
|
|
source={require("../../assets/img/google.png")} |
|
|
style={{ width: 20, height: 20 }} |
|
|
/> |
|
|
</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, |
|
|
}, |
|
|
});
|
|
|
|