|
|
import React, { useState, useRef } from "react"; |
|
|
import { |
|
|
View, |
|
|
Text, |
|
|
StyleSheet, |
|
|
TouchableOpacity, |
|
|
TextInput, |
|
|
FlatList, |
|
|
Keyboard, |
|
|
Alert, |
|
|
Platform, |
|
|
Modal |
|
|
} from "react-native"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useNavigation } from "@react-navigation/native"; |
|
|
import AsyncStorage from "@react-native-async-storage/async-storage"; |
|
|
import { userApi } from "../../services/api/userApi"; |
|
|
import { settingApi } from "../../services/api/setting"; |
|
|
import useUserStore from "../../store/user"; |
|
|
import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; |
|
|
|
|
|
// Common email domains list |
|
|
const EMAIL_DOMAINS = [ |
|
|
"gmail.com", |
|
|
"yahoo.com", |
|
|
"hotmail.com", |
|
|
"outlook.com", |
|
|
"icloud.com", |
|
|
"mail.com", |
|
|
"protonmail.com", |
|
|
"qq.com", |
|
|
"163.com", |
|
|
"126.com", |
|
|
]; |
|
|
|
|
|
type RootStackParamList = { |
|
|
Login: undefined; |
|
|
EmailLogin: undefined; |
|
|
MainTabs: { screen: string }; |
|
|
Google: undefined; |
|
|
Home: { screen: string }; |
|
|
}; |
|
|
|
|
|
type EmailLoginModalProps = { |
|
|
visible: boolean; |
|
|
onClose: () => void; |
|
|
}; |
|
|
|
|
|
const EmailLoginModal = ({ visible, onClose }: EmailLoginModalProps) => { |
|
|
const { t } = useTranslation(); |
|
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>(); |
|
|
const { setSettings, setUser } = useUserStore(); |
|
|
|
|
|
// 状态管理 |
|
|
const [email, setEmail] = useState(""); |
|
|
const [emailPassword, setEmailPassword] = useState(""); |
|
|
const [emailPasswordError, setEmailPasswordError] = useState(false); |
|
|
const [showSuggestions, setShowSuggestions] = useState(false); |
|
|
const [suggestions, setSuggestions] = useState<string[]>([]); |
|
|
|
|
|
// 防止重复关闭 |
|
|
const isClosing = useRef(false); |
|
|
|
|
|
// 引用输入框 |
|
|
const emailInputRef = useRef<TextInput>(null); |
|
|
const passwordInputRef = useRef<TextInput>(null); |
|
|
|
|
|
// 主动弹出键盘 |
|
|
const focusEmailInput = () => { |
|
|
if (emailInputRef.current) { |
|
|
emailInputRef.current.focus(); |
|
|
} |
|
|
}; |
|
|
|
|
|
const focusPasswordInput = () => { |
|
|
if (passwordInputRef.current) { |
|
|
passwordInputRef.current.focus(); |
|
|
} |
|
|
}; |
|
|
|
|
|
React.useEffect(() => { |
|
|
if (visible) { |
|
|
// 当模态框显示时,等待动画完成后主动弹出键盘 |
|
|
const timer = setTimeout(() => { |
|
|
focusEmailInput(); |
|
|
}, 300); |
|
|
|
|
|
return () => clearTimeout(timer); |
|
|
} |
|
|
}, [visible]); |
|
|
|
|
|
// 处理邮箱输入变化 |
|
|
const handleEmailChange = (text: string) => { |
|
|
setEmail(text); |
|
|
|
|
|
// Check if it includes @ symbol |
|
|
if (text.includes("@")) { |
|
|
const [username, domain] = text.split("@"); |
|
|
|
|
|
if (domain) { |
|
|
// If domain part is already entered, filter matching domains |
|
|
const filteredDomains = EMAIL_DOMAINS.filter((item) => |
|
|
item.toLowerCase().startsWith(domain.toLowerCase()) |
|
|
); |
|
|
|
|
|
// Generate complete email suggestion list |
|
|
const emailSuggestions = filteredDomains.map((d) => `${username}@${d}`); |
|
|
setSuggestions(emailSuggestions); |
|
|
setShowSuggestions(emailSuggestions.length > 0); |
|
|
} else { |
|
|
// If only @ is entered, show all domain suggestions |
|
|
const emailSuggestions = EMAIL_DOMAINS.map((d) => `${username}@${d}`); |
|
|
setSuggestions(emailSuggestions); |
|
|
setShowSuggestions(true); |
|
|
} |
|
|
} else if (text.length > 0) { |
|
|
// No @ symbol but has input content, show common email suffix suggestions |
|
|
const emailSuggestions = EMAIL_DOMAINS.map((d) => `${text}@${d}`); |
|
|
setSuggestions(emailSuggestions); |
|
|
setShowSuggestions(true); |
|
|
} else { |
|
|
// Empty input, don't show suggestions |
|
|
setShowSuggestions(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
// Select an email suggestion |
|
|
const handleSelectSuggestion = (suggestion: string) => { |
|
|
setEmail(suggestion); |
|
|
setShowSuggestions(false); |
|
|
}; |
|
|
|
|
|
// Validate email format |
|
|
const isValidEmail = (email: string): boolean => { |
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
|
|
return emailRegex.test(email); |
|
|
}; |
|
|
|
|
|
// Render a single email suggestion item |
|
|
const renderSuggestionItem = ({ item }: { item: string }) => ( |
|
|
<TouchableOpacity |
|
|
style={styles.suggestionItem} |
|
|
onPress={() => handleSelectSuggestion(item)} |
|
|
> |
|
|
<Text style={styles.suggestionText}>{item}</Text> |
|
|
</TouchableOpacity> |
|
|
); |
|
|
|
|
|
// Handle forgot password |
|
|
const handleForgotPassword = () => { |
|
|
// Handle forgot password logic |
|
|
}; |
|
|
|
|
|
// Handle email login |
|
|
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" }); |
|
|
onClose(); |
|
|
} |
|
|
} catch (error) { |
|
|
Alert.alert(t("loginFailed")); |
|
|
} |
|
|
}; |
|
|
|
|
|
// 安全地关闭模态框 |
|
|
const closeEmailLogin = () => { |
|
|
console.log("Closing email login modal"); |
|
|
// 确保键盘关闭 |
|
|
Keyboard.dismiss(); |
|
|
// 直接调用关闭回调 |
|
|
onClose(); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<Modal |
|
|
visible={visible} |
|
|
animationType="slide" |
|
|
transparent={true} |
|
|
onRequestClose={() => onClose()} |
|
|
statusBarTranslucent={true} |
|
|
> |
|
|
<View style={styles.modalContainer}> |
|
|
<View style={styles.emailLoginContainer}> |
|
|
<View style={styles.emailLoginHeader}> |
|
|
<TouchableOpacity |
|
|
style={styles.emailLoginCloseButton} |
|
|
onPress={() => onClose()} |
|
|
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 |
|
|
ref={emailInputRef} |
|
|
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> |
|
|
) : ( |
|
|
<TouchableOpacity |
|
|
style={styles.emailClearButton} |
|
|
onPress={focusEmailInput} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={{fontSize: 16, color: '#0066FF'}}>⌨️</Text> |
|
|
</TouchableOpacity> |
|
|
)} |
|
|
</View> |
|
|
|
|
|
{/* Email suffix suggestion list */} |
|
|
{showSuggestions && ( |
|
|
<View style={styles.suggestionsContainer}> |
|
|
<FlatList |
|
|
data={suggestions.slice(0, 5)} // Limit to showing 5 suggestions max |
|
|
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> |
|
|
)} |
|
|
|
|
|
{/* Password input */} |
|
|
<View |
|
|
style={[ |
|
|
styles.passwordContainer, |
|
|
emailPasswordError && styles.passwordErrorContainer, |
|
|
]} |
|
|
> |
|
|
<TextInput |
|
|
ref={passwordInputRef} |
|
|
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> |
|
|
) : ( |
|
|
<TouchableOpacity |
|
|
style={styles.emailClearButton} |
|
|
onPress={focusPasswordInput} |
|
|
activeOpacity={0.7} |
|
|
> |
|
|
<Text style={{fontSize: 16, color: '#0066FF'}}>⌨️</Text> |
|
|
</TouchableOpacity> |
|
|
)} |
|
|
</View> |
|
|
|
|
|
{/* Password error message */} |
|
|
{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> |
|
|
</View> |
|
|
</View> |
|
|
</Modal> |
|
|
); |
|
|
}; |
|
|
|
|
|
const styles = StyleSheet.create({ |
|
|
modalContainer: { |
|
|
flex: 1, |
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)", |
|
|
justifyContent: "flex-end", |
|
|
zIndex: 999, |
|
|
}, |
|
|
emailLoginContainer: { |
|
|
backgroundColor: "#fff", |
|
|
borderTopLeftRadius: 20, |
|
|
borderTopRightRadius: 20, |
|
|
height: "80%", |
|
|
shadowColor: "#000", |
|
|
shadowOffset: { width: 0, height: -2 }, |
|
|
shadowOpacity: 0.1, |
|
|
shadowRadius: 5, |
|
|
elevation: 5, |
|
|
}, |
|
|
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", |
|
|
}, |
|
|
passwordContainer: { |
|
|
flexDirection: "row", |
|
|
alignItems: "center", |
|
|
borderWidth: 1, |
|
|
borderColor: "#E1E1E1", |
|
|
borderRadius: 25, |
|
|
height: 50, |
|
|
marginBottom: 20, |
|
|
position: "relative", |
|
|
}, |
|
|
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, |
|
|
}, |
|
|
emailContinueButton: { |
|
|
height: 50, |
|
|
backgroundColor: "#0039CB", |
|
|
borderRadius: 25, |
|
|
justifyContent: "center", |
|
|
alignItems: "center", |
|
|
marginTop: 20, |
|
|
}, |
|
|
emailDisabledButton: { |
|
|
backgroundColor: "#CCCCCC", |
|
|
}, |
|
|
emailContinueButtonText: { |
|
|
color: "#fff", |
|
|
fontSize: 16, |
|
|
fontWeight: "600", |
|
|
}, |
|
|
}); |
|
|
|
|
|
export default EmailLoginModal;
|