feat: 登录
This commit is contained in:
parent
cc305aa84a
commit
4f92f52bb7
Binary file not shown.
@ -1,23 +1,26 @@
|
||||
import SwiftUI
|
||||
import AuthenticationServices // 苹果登录功能
|
||||
import Alamofire // 网络请求
|
||||
import CryptoKit // 加密功能
|
||||
import AuthenticationServices
|
||||
import Alamofire
|
||||
import CryptoKit
|
||||
|
||||
/// 主登录视图 - 处理苹果登录
|
||||
struct LoginView: View {
|
||||
// MARK: - 属性
|
||||
@Environment(\.dismiss) private var dismiss // 用于关闭视图
|
||||
@State private var isLoading = false // 加载状态
|
||||
@State private var showError = false // 是否显示错误
|
||||
@State private var errorMessage = "" // 错误信息
|
||||
@State private var currentNonce: String? // 用于防止重放攻击的随机数
|
||||
// MARK: - Properties
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var isLoading = false
|
||||
@State private var showError = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var currentNonce: String?
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
// MARK: - 视图主体
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// 背景
|
||||
Color(red: 1.0, green: 0.67, blue: 0.15) // FFAA26 in RGB (1.0, 0.67, 0.15)
|
||||
// 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)
|
||||
@ -25,8 +28,8 @@ struct LoginView: View {
|
||||
.foregroundColor(.black)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 24)
|
||||
.padding(.top, 44) // Add some top padding for the status bar
|
||||
|
||||
.padding(.top, 44)
|
||||
|
||||
Text("Welcome~")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
@ -34,15 +37,15 @@ struct LoginView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 24)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
|
||||
VStack(spacing: 16) {
|
||||
// appHeaderView()
|
||||
Spacer() // 应用标题
|
||||
signInButton() // 登录按钮
|
||||
termsAndPrivacyView() // 服务条款和隐私政策
|
||||
Spacer()
|
||||
signInButton()
|
||||
termsAndPrivacyView()
|
||||
}
|
||||
.padding()
|
||||
.alert(isPresented: $showError) {
|
||||
@ -53,7 +56,6 @@ struct LoginView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// 加载指示器
|
||||
if isLoading {
|
||||
loadingView()
|
||||
}
|
||||
@ -61,80 +63,45 @@ struct LoginView: View {
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
// MARK: - 视图组件
|
||||
// MARK: - Views
|
||||
|
||||
/// 应用标题视图
|
||||
private func appHeaderView() -> some View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 80, height: 80)
|
||||
.foregroundColor(.blue)
|
||||
|
||||
Text("Welcome to Wake")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("Sign in to continue")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
/// 苹果登录按钮
|
||||
private func signInButton() -> some View {
|
||||
Button(action: {
|
||||
// This will be handled by the SignInWithAppleButton
|
||||
}) {
|
||||
ZStack {
|
||||
// Invisible SignInWithAppleButton for functionality
|
||||
SignInWithAppleButton(
|
||||
onRequest: { request in
|
||||
let nonce = String.randomURLSafeString(length: 32)
|
||||
self.currentNonce = nonce
|
||||
request.requestedScopes = [.fullName, .email]
|
||||
request.nonce = self.sha256(nonce)
|
||||
},
|
||||
onCompletion: handleAppleSignIn
|
||||
)
|
||||
.frame(width: 0, height: 0)
|
||||
.opacity(0)
|
||||
|
||||
// Custom button appearance
|
||||
HStack {
|
||||
Image(systemName: "applelogo")
|
||||
Text("Continue with Apple")
|
||||
// .font(.headline)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 24)
|
||||
.foregroundColor(.black)
|
||||
.background(Color.white)
|
||||
.cornerRadius(25)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.stroke(Color.black, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
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)
|
||||
.padding(.horizontal, 40)
|
||||
.cornerRadius(25)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.stroke(Color.black, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
/// 服务条款和隐私政策链接
|
||||
private func termsAndPrivacyView() -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Text("By continuing, you agree to our")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 24)
|
||||
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: 16) {
|
||||
Button("Terms of Service") {
|
||||
HStack(spacing: 8) {
|
||||
Button("Service") {
|
||||
openURL("https://yourwebsite.com/terms")
|
||||
}
|
||||
.font(.caption2)
|
||||
@ -143,7 +110,6 @@ struct LoginView: View {
|
||||
Text("and")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Button("Privacy Policy") {
|
||||
openURL("https://yourwebsite.com/privacy")
|
||||
@ -153,16 +119,14 @@ struct LoginView: View {
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.lineLimit(2)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
|
||||
/// 加载视图
|
||||
private func loadingView() -> some View {
|
||||
return ZStack {
|
||||
ZStack {
|
||||
Color.black.opacity(0.4)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
||||
@ -172,9 +136,8 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 苹果登录处理
|
||||
// MARK: - Authentication
|
||||
|
||||
/// 处理苹果登录结果
|
||||
private func handleAppleSignIn(result: Result<ASAuthorization, Error>) {
|
||||
print("🔵 [Apple Sign In] 开始处理登录结果...")
|
||||
switch result {
|
||||
@ -187,7 +150,6 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理苹果ID凭证
|
||||
private func processAppleIDCredential(_ credential: ASAuthorizationCredential) {
|
||||
print("🔵 [Apple ID] 开始处理凭证...")
|
||||
guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else {
|
||||
@ -196,7 +158,6 @@ struct LoginView: View {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户数据
|
||||
let userId = appleIDCredential.user
|
||||
let email = appleIDCredential.email ?? ""
|
||||
let fullName = [
|
||||
@ -208,7 +169,6 @@ struct LoginView: View {
|
||||
|
||||
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] 无法获取身份令牌")
|
||||
@ -216,7 +176,6 @@ struct LoginView: View {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取授权码(可选)
|
||||
var authCode: String? = nil
|
||||
if let authCodeData = appleIDCredential.authorizationCode {
|
||||
authCode = String(data: authCodeData, encoding: .utf8)
|
||||
@ -235,9 +194,8 @@ struct LoginView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - 网络操作
|
||||
// MARK: - Network
|
||||
|
||||
/// 与后端服务器进行认证
|
||||
private func authenticateWithBackend(
|
||||
userId: String,
|
||||
email: String,
|
||||
@ -256,7 +214,6 @@ struct LoginView: View {
|
||||
"identityToken": identityToken
|
||||
]
|
||||
|
||||
// 添加授权码(如果存在)
|
||||
if let authCode = authCode {
|
||||
parameters["authorizationCode"] = authCode
|
||||
}
|
||||
@ -282,9 +239,8 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 辅助方法
|
||||
// MARK: - Helpers
|
||||
|
||||
/// 处理认证成功
|
||||
private func handleSuccessfulAuthentication() {
|
||||
print("✅ [Auth] 登录成功,准备关闭登录页面...")
|
||||
DispatchQueue.main.async {
|
||||
@ -292,21 +248,18 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理登录错误
|
||||
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)")
|
||||
showError(message: "登录失败: \(errorMessage)")
|
||||
}
|
||||
|
||||
/// 显示错误信息
|
||||
private func showError(message: String) {
|
||||
DispatchQueue.main.async {
|
||||
self.errorMessage = message
|
||||
@ -314,27 +267,21 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 在Safari中打开URL
|
||||
private func openURL(_ string: String) {
|
||||
guard let url = URL(string: string) else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
/// SHA256哈希函数
|
||||
private func sha256(_ input: String) -> String {
|
||||
let inputData = Data(input.utf8)
|
||||
let hashedData = SHA256.hash(data: inputData)
|
||||
let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined()
|
||||
return hashString
|
||||
return hashedData.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 字符串扩展:生成随机字符串
|
||||
// MARK: - Extensions
|
||||
|
||||
extension String {
|
||||
/// 生成指定长度的随机URL安全字符串
|
||||
/// - Parameter length: 字符串长度
|
||||
/// - Returns: 随机字符串
|
||||
static func randomURLSafeString(length: Int) -> String {
|
||||
let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
|
||||
var randomString = ""
|
||||
@ -349,7 +296,8 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 预览
|
||||
// MARK: - Preview
|
||||
|
||||
struct LoginView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LoginView()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user