添加Apple登录
This commit is contained in:
317
app/profile.tsx
317
app/profile.tsx
@@ -1,19 +1,34 @@
|
||||
import * as AppleAuthentication from "expo-apple-authentication";
|
||||
import Constants from "expo-constants";
|
||||
import { Image } from "expo-image";
|
||||
import { Stack } from "expo-router";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
import {
|
||||
Modal,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
import { ThemedText } from "@/components/themed-text";
|
||||
import { IconSymbol } from "@/components/ui/icon-symbol";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
import { changeLanguage } from "@/i18n";
|
||||
import { appleSignIn, fetchUserProfile, logout } from "@/lib/api";
|
||||
import { storage } from "@/lib/storage";
|
||||
import type { UserProfile } from "@/types/api";
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } =
|
||||
useTheme();
|
||||
const { t, i18n } = useTranslation();
|
||||
const isDark = theme === "dark";
|
||||
const [appleAvailable, setAppleAvailable] = React.useState(false);
|
||||
const [user, setUser] = React.useState<UserProfile | null>(null);
|
||||
const [loginModalVisible, setLoginModalVisible] = React.useState(false);
|
||||
|
||||
const currentLanguage = i18n.language;
|
||||
|
||||
@@ -22,6 +37,107 @@ export default function ProfileScreen() {
|
||||
changeLanguage(nextLang);
|
||||
};
|
||||
|
||||
const handleAppleSignIn = async () => {
|
||||
try {
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
],
|
||||
});
|
||||
|
||||
console.log("Apple credential:", {
|
||||
fullName: credential.fullName,
|
||||
email: credential.email,
|
||||
user: credential.user,
|
||||
});
|
||||
|
||||
const deviceId = Constants.deviceId || Constants.sessionId || "unknown";
|
||||
const platformVersion = Platform.Version.toString();
|
||||
|
||||
const options = {
|
||||
authorizationCode: credential.authorizationCode || "",
|
||||
identityToken: credential.identityToken || "",
|
||||
deviceInfo: {
|
||||
deviceId,
|
||||
platform: Platform.OS,
|
||||
platformVersion,
|
||||
},
|
||||
user:
|
||||
credential.fullName && (credential.fullName.givenName || credential.fullName.familyName)
|
||||
? {
|
||||
name: {
|
||||
firstName: credential.fullName.givenName || undefined,
|
||||
lastName: credential.fullName.familyName || undefined,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
console.log("登录参数", options);
|
||||
|
||||
const res = await appleSignIn(options);
|
||||
|
||||
console.log("登录响应", res);
|
||||
|
||||
await storage.setAccessToken(res.accessToken);
|
||||
await storage.setRefreshToken(res.refreshToken);
|
||||
|
||||
if (res.user) {
|
||||
await storage.setUser(res.user);
|
||||
setUser(res.user);
|
||||
} else {
|
||||
const profile = await fetchUserProfile();
|
||||
await storage.setUser(profile);
|
||||
setUser(profile);
|
||||
}
|
||||
setLoginModalVisible(false);
|
||||
} catch (error: any) {
|
||||
if (error?.code === "ERR_REQUEST_CANCELED") {
|
||||
return;
|
||||
}
|
||||
console.error("Apple sign in error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginPress = () => {
|
||||
setLoginModalVisible(true);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
AppleAuthentication.isAvailableAsync().then(setAppleAvailable);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const loadUser = async () => {
|
||||
const savedUser = await storage.getUser();
|
||||
if (savedUser) {
|
||||
setUser(savedUser);
|
||||
try {
|
||||
const profile = await fetchUserProfile();
|
||||
await storage.setUser(profile);
|
||||
setUser(profile);
|
||||
} catch {
|
||||
setUser(savedUser);
|
||||
}
|
||||
}
|
||||
};
|
||||
loadUser();
|
||||
}, []);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logout();
|
||||
} catch (error) {
|
||||
console.error("Logout error:", error);
|
||||
} finally {
|
||||
await storage.clear();
|
||||
setUser(null);
|
||||
}
|
||||
};
|
||||
|
||||
const isLoggedIn = !!user;
|
||||
|
||||
const iconColor = isDark ? "#FFFFFF" : "#000000";
|
||||
const textColor = isDark ? "#FFFFFF" : "#000000";
|
||||
const subTextColor = isDark ? "#AAAAAA" : "#666666";
|
||||
@@ -51,29 +167,59 @@ export default function ProfileScreen() {
|
||||
{ backgroundColor: isDark ? "#000" : "#f2f2f7" },
|
||||
]}
|
||||
>
|
||||
{/* User Info Section */}
|
||||
<View
|
||||
style={[
|
||||
styles.section,
|
||||
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||
]}
|
||||
>
|
||||
<View style={styles.profileHeader}>
|
||||
<Image
|
||||
source={{ uri: "https://via.placeholder.com/100" }}
|
||||
style={styles.avatar}
|
||||
contentFit="cover"
|
||||
/>
|
||||
<View style={styles.profileInfo}>
|
||||
<ThemedText type="title">{t("profile.name")}</ThemedText>
|
||||
<ThemedText style={{ color: subTextColor }}>
|
||||
user@example.com
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<View style={styles.profileHeader}>
|
||||
<Image
|
||||
source={{ uri: user?.avatar || "https://via.placeholder.com/100" }}
|
||||
style={styles.avatar}
|
||||
contentFit="cover"
|
||||
/>
|
||||
<View style={styles.profileInfo}>
|
||||
<ThemedText type="title">
|
||||
{user?.nickname || t("profile.name")}
|
||||
</ThemedText>
|
||||
<ThemedText style={{ color: subTextColor }}>
|
||||
{user?.appleId || ""}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.logoutButton,
|
||||
{
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: isDark ? "#38383a" : "#c6c6c8",
|
||||
},
|
||||
]}
|
||||
onPress={handleLogout}
|
||||
>
|
||||
<ThemedText style={{ color: "#FF3B30" }}>登出</ThemedText>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={styles.loginCard}
|
||||
onPress={handleLoginPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<View style={styles.loginIcon} />
|
||||
<View style={styles.loginInfo}>
|
||||
<ThemedText style={styles.loginTitle}>登录</ThemedText>
|
||||
<ThemedText style={{ color: subTextColor }}>
|
||||
点击登录
|
||||
</ThemedText>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Settings Section */}
|
||||
<ThemedText style={styles.sectionTitle}>
|
||||
{t("settings.title")}
|
||||
</ThemedText>
|
||||
@@ -84,7 +230,6 @@ export default function ProfileScreen() {
|
||||
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||
]}
|
||||
>
|
||||
{/* Theme Setting */}
|
||||
<View style={styles.settingItem}>
|
||||
<View style={styles.settingLabel}>
|
||||
<IconSymbol
|
||||
@@ -104,7 +249,6 @@ export default function ProfileScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Language Setting */}
|
||||
<View
|
||||
style={[
|
||||
styles.settingItem,
|
||||
@@ -134,7 +278,92 @@ export default function ProfileScreen() {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* <ThemedText style={styles.sectionTitle}>登录</ThemedText>
|
||||
|
||||
<View
|
||||
style={[
|
||||
styles.section,
|
||||
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||
]}
|
||||
>
|
||||
{appleAvailable && (
|
||||
<View style={styles.settingItem}>
|
||||
<AppleAuthentication.AppleAuthenticationButton
|
||||
buttonType={
|
||||
AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN
|
||||
}
|
||||
buttonStyle={
|
||||
isDark
|
||||
? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||
: AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||
}
|
||||
cornerRadius={8}
|
||||
style={{ width: "100%", height: 44 }}
|
||||
onPress={handleAppleSignIn}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
styles.settingItem,
|
||||
{
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: isDark ? "#38383a" : "#c6c6c8",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity style={styles.googleButton} onPress={() => {}}>
|
||||
<ThemedText>Google 登录</ThemedText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View> */}
|
||||
</ScrollView>
|
||||
<Modal
|
||||
visible={loginModalVisible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setLoginModalVisible(false)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.modalMask}
|
||||
activeOpacity={1}
|
||||
onPress={() => setLoginModalVisible(false)}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.modalCard,
|
||||
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.modalTitle}>选择登录方式</ThemedText>
|
||||
{appleAvailable && (
|
||||
<AppleAuthentication.AppleAuthenticationButton
|
||||
buttonType={
|
||||
AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN
|
||||
}
|
||||
buttonStyle={
|
||||
isDark
|
||||
? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||
: AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||
}
|
||||
cornerRadius={8}
|
||||
style={{ width: "100%", height: 44 }}
|
||||
onPress={handleAppleSignIn}
|
||||
/>
|
||||
)}
|
||||
<TouchableOpacity style={styles.googleButton} onPress={() => {}}>
|
||||
<ThemedText>Google 登录</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.modalCancel}
|
||||
onPress={() => setLoginModalVisible(false)}
|
||||
>
|
||||
<ThemedText>取消</ThemedText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -190,4 +419,56 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
},
|
||||
googleButton: {
|
||||
width: "100%",
|
||||
height: 44,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: "#c6c6c8",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
loginCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 12,
|
||||
},
|
||||
loginIcon: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
backgroundColor: "#c6c6c8",
|
||||
},
|
||||
loginInfo: {
|
||||
marginLeft: 12,
|
||||
},
|
||||
loginTitle: {
|
||||
fontSize: 18,
|
||||
},
|
||||
modalMask: {
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0,0,0,0.4)",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
modalCard: {
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
gap: 12,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 16,
|
||||
textAlign: "center",
|
||||
},
|
||||
modalCancel: {
|
||||
height: 40,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
logoutButton: {
|
||||
paddingVertical: 12,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginTop: 10,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user