import SwiftUI import AuthenticationServices import Alamofire import CryptoKit /// 主登录视图 - 处理苹果登录 struct LoginView: View { // MARK: - Properties @State private var isLoading = false @State private var showError = false @State private var errorMessage = "" @State private var currentNonce: String? @State private var isLoggedIn = false // MARK: - Body var body: some View { NavigationStack { ZStack { // Background Color(red: 1.0, green: 0.67, blue: 0.15) .edgesIgnoringSafeArea(.all) VStack(alignment: .leading, spacing: 4) { Text("Hi, I'm MeMo!") .font(.largeTitle) .fontWeight(.semibold) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) .padding(.leading, 24) .padding(.top, 44) Text("Welcome~") .font(.largeTitle) .fontWeight(.semibold) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) .padding(.leading, 24) .padding(.bottom, 20) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) VStack(spacing: 16) { Spacer() signInButton() termsAndPrivacyView() } .padding() .alert(isPresented: $showError) { Alert( title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("OK")) ) } if isLoading { loadingView() } } .navigationBarHidden(true) .fullScreenCover(isPresented: $isLoggedIn) { NavigationStack { UserInfo() } } } } // MARK: - Views private func signInButton() -> some View { SignInWithAppleButton( onRequest: { request in let nonce = String.randomURLSafeString(length: 32) self.currentNonce = nonce request.requestedScopes = [.fullName, .email] request.nonce = self.sha256(nonce) }, onCompletion: handleAppleSignIn ) .signInWithAppleButtonStyle(.white) .frame(height: 50) .cornerRadius(25) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.black, lineWidth: 1) ) } private func termsAndPrivacyView() -> some View { VStack(spacing: 4) { HStack { Text("By continuing, you agree to our") .font(.caption) .foregroundColor(.secondary) Button("Terms of") { openURL("https://yourwebsite.com/terms") } .font(.caption2) .foregroundColor(.blue) } .multilineTextAlignment(.center) .padding(.horizontal, 24) HStack(spacing: 8) { Button("Service") { openURL("https://yourwebsite.com/terms") } .font(.caption2) .foregroundColor(.blue) Text("and") .foregroundColor(.secondary) .font(.caption) Button("Privacy Policy") { openURL("https://yourwebsite.com/privacy") } .font(.caption2) .foregroundColor(.blue) } .padding(.top, 4) } .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity) .padding(.horizontal, 24) .padding(.bottom, 24) } private func loadingView() -> some View { ZStack { Color.black.opacity(0.4) .edgesIgnoringSafeArea(.all) ProgressView() .scaleEffect(1.5) .progressViewStyle(CircularProgressViewStyle(tint: .white)) } } // MARK: - Authentication private func handleAppleSignIn(result: Result) { print("🔵 [Apple Sign In] 开始处理登录结果...") DispatchQueue.main.async { self.isLoggedIn = true } switch result { case .success(let authResults): print("✅ [Apple Sign In] 登录授权成功") processAppleIDCredential(authResults.credential) case .failure(let error): print("❌ [Apple Sign In] 登录失败: \(error.localizedDescription)") handleSignInError(error) } } private func processAppleIDCredential(_ credential: ASAuthorizationCredential) { print("🔵 [Apple ID] 开始处理凭证...") guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else { print("❌ [Apple ID] 凭证类型不匹配") showError(message: "无法处理Apple ID凭证") return } let userId = appleIDCredential.user let email = appleIDCredential.email ?? "" let fullName = [ appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName ] .compactMap { $0 } .joined(separator: " ") print("ℹ️ [Apple ID] 用户数据 - ID: \(userId), 邮箱: \(email.isEmpty ? "未提供" : email), 姓名: \(fullName.isEmpty ? "未提供" : fullName)") guard let identityTokenData = appleIDCredential.identityToken, let identityToken = String(data: identityTokenData, encoding: .utf8) else { print("❌ [Apple ID] 无法获取身份令牌") showError(message: "无法获取身份令牌") return } var authCode: String? = nil if let authCodeData = appleIDCredential.authorizationCode { authCode = String(data: authCodeData, encoding: .utf8) print("ℹ️ [Apple ID] 获取到授权码") } else { print("ℹ️ [Apple ID] 未获取到授权码(可选)") } print("🔵 [Apple ID] 准备调用后端认证...") authenticateWithBackend( userId: userId, email: email, name: fullName, identityToken: identityToken, authCode: authCode ) } // MARK: - Network private func authenticateWithBackend( userId: String, email: String, name: String, identityToken: String, authCode: String? ) { isLoading = true print("🔵 [Backend] 开始后端认证...") let url = "https://your-api-endpoint.com/api/auth/apple" var parameters: [String: Any] = [ "appleUserId": userId, "email": email, "name": name, "identityToken": identityToken ] if let authCode = authCode { parameters["authorizationCode"] = authCode } print("📤 [Backend] 请求参数: \(parameters)") AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default) .validate() .responseJSON { response in self.isLoading = false switch response.result { case .success(let value): print("✅ [Backend] 认证成功: \(value)") self.handleSuccessfulAuthentication() case .failure(let error): print("❌ [Backend] 认证失败: \(error.localizedDescription)") if let data = response.data, let json = String(data: data, encoding: .utf8) { print("❌ [Backend] 错误详情: \(json)") } self.handleAuthenticationError(error) } } } // MARK: - Helpers private func handleSuccessfulAuthentication() { print("✅ [Auth] 登录成功,准备跳转到用户信息页面...") DispatchQueue.main.async { self.isLoggedIn = true } } private func handleSignInError(_ error: Error) { let errorMessage = (error as NSError).localizedDescription print("❌ [Auth] 登录错误: \(errorMessage)") showError(message: "登录失败: \(error.localizedDescription)") } private func handleAuthenticationError(_ error: AFError) { let errorMessage = error.localizedDescription print("❌ [Auth] 认证错误: \(errorMessage)") DispatchQueue.main.async { self.isLoggedIn = false self.showError(message: "登录失败: \(errorMessage)") } } private func showError(message: String) { DispatchQueue.main.async { self.errorMessage = message self.showError = true } } private func openURL(_ string: String) { guard let url = URL(string: string) else { return } UIApplication.shared.open(url) } private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) return hashedData.compactMap { String(format: "%02x", $0) }.joined() } } // MARK: - Extensions extension String { static func randomURLSafeString(length: Int) -> String { let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" var randomString = "" for _ in 0..