diff --git a/wake.xcodeproj/project.pbxproj b/wake.xcodeproj/project.pbxproj
index e45c27e..b8dfd9a 100644
--- a/wake.xcodeproj/project.pbxproj
+++ b/wake.xcodeproj/project.pbxproj
@@ -291,13 +291,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = wake/wake.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = GB3VPJ54BD;
+ DEVELOPMENT_TEAM = 392N3QB7XR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = wake/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "";
+ INFOPLIST_KEY_NSUserTrackingUsageDescription = "我们需要访问您的Apple ID信息来为您提供登录服务";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -307,7 +309,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.memowake.wake;
+ PRODUCT_BUNDLE_IDENTIFIER = com.memowake.fairclip2025;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
@@ -320,13 +322,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = wake/wake.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = GB3VPJ54BD;
+ DEVELOPMENT_TEAM = 392N3QB7XR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = wake/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "";
+ INFOPLIST_KEY_NSUserTrackingUsageDescription = "我们需要访问您的Apple ID信息来为您提供登录服务";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -336,7 +340,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.memowake.wake;
+ PRODUCT_BUNDLE_IDENTIFIER = com.memowake.fairclip2025;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate
index 39fe2f5..0aac839 100644
Binary files a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate and b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/wake/Info.plist b/wake/Info.plist
index 3a0d680..d4f0a89 100644
--- a/wake/Info.plist
+++ b/wake/Info.plist
@@ -2,13 +2,8 @@
- UIAppFonts
-
- Quicksand x.ttf
- SankeiCutePopanime.ttf
-
-
-
+ ASAuthorizationAppleIDProvider
+ $(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleURLTypes
@@ -18,14 +13,17 @@
-
- ASAuthorizationAppleIDProvider
- $(PRODUCT_BUNDLE_IDENTIFIER)
-
LSApplicationQueriesSchemes
appleid
appleauth
+ NSAppleIDUsageDescription
+ Sign in with Apple is used to authenticate your account
+ UIAppFonts
+
+ Quicksand x.ttf
+ SankeiCutePopanime.ttf
+
diff --git a/wake/View/Login/Login.swift b/wake/View/Login/Login.swift
index d9d4dde..895447e 100644
--- a/wake/View/Login/Login.swift
+++ b/wake/View/Login/Login.swift
@@ -1,165 +1,290 @@
import SwiftUI
import AuthenticationServices
import Alamofire
+import CryptoKit
-struct Post: Codable {
- let id: Int
- let title: String
- let body: String
- let userId: Int
-}
-
-struct Login: Encodable {
- let account: String
- let password: String
-}
-
+/// Main login view that handles Apple Sign In
struct LoginView: View {
+ // MARK: - Properties
@Environment(\.dismiss) private var dismiss
- @State private var showModal = false
@State private var isLoading = false
+ @State private var showError = false
+ @State private var errorMessage = ""
+ @State private var currentNonce: String?
+ // MARK: - Body
var body: some View {
- NavigationView {
- ZStack {
- VStack {
- // App logo or title
- VStack {
- Image(systemName: "person.circle.fill")
- .resizable()
- .frame(width: 80, height: 80)
- .foregroundColor(.blue)
- .padding(.bottom, 20)
-
- Text("Welcome")
- .font(.largeTitle)
- .fontWeight(.bold)
- .padding(.bottom, 40)
- }
-
- // Apple Sign In Button
- SignInWithAppleButton(
- onRequest: { request in
- request.requestedScopes = [.fullName, .email]
- },
- onCompletion: { result in
- switch result {
- case .success(let authResults):
- print("Authorization successful: \(authResults)")
- // Handle successful authentication
- // You can get user details from authResults.credential
- if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential {
- let userId = appleIDCredential.user
- let email = appleIDCredential.email
- let fullName = appleIDCredential.fullName
-
- print("User ID: \(userId)")
- print("Email: \(email ?? "No email")")
- print("Name: \(fullName?.givenName ?? "") \(fullName?.familyName ?? "")")
-
- // TODO: Send this information to your backend
- // For example:
- // loginWithApple(userId: userId, email: email, name: "\(fullName?.givenName ?? "") \(fullName?.familyName ?? "")")
-
- // Dismiss the login view after successful login
- DispatchQueue.main.async {
- self.dismiss()
- }
- }
-
- case .failure(let error):
- print("Authorization failed: \(error.localizedDescription)")
- // Handle error
- }
- }
- )
- .signInWithAppleButtonStyle(.black) // or .white, .whiteOutline
- .frame(height: 50)
- .padding(.horizontal, 40)
- .padding(.bottom, 20)
-
- // Terms and Privacy Policy
- VStack(spacing: 8) {
- Text("By continuing, you agree to our")
- .font(.caption)
- .foregroundColor(.gray)
-
- HStack(spacing: 16) {
- Button("Terms of Service") {
- // Open terms URL
- if let url = URL(string: "https://yourwebsite.com/terms") {
- UIApplication.shared.open(url)
- }
- }
- .font(.caption)
- .foregroundColor(.blue)
-
- Text("•")
- .foregroundColor(.gray)
-
- Button("Privacy Policy") {
- // Open privacy policy URL
- if let url = URL(string: "https://yourwebsite.com/privacy") {
- UIApplication.shared.open(url)
- }
- }
- .font(.caption)
- .foregroundColor(.blue)
- }
- }
- .padding(.bottom, 40)
-
- Spacer()
- }
- .padding()
- .navigationBarHidden(true)
- .navigationBarBackButtonHidden(true)
-
- // Loading indicator
- if isLoading {
- Color.black.opacity(0.4)
- .edgesIgnoringSafeArea(.all)
-
- ProgressView()
- .scaleEffect(1.5)
- .progressViewStyle(CircularProgressViewStyle(tint: .white))
- }
+ ZStack {
+ // Background
+ Color(.systemBackground)
+ .edgesIgnoringSafeArea(.all)
+
+ // Main content
+ VStack(spacing: 24) {
+ Spacer()
+ appHeaderView()
+ signInButton()
+ Spacer()
+ termsAndPrivacyView()
}
+ .padding()
+ .alert(isPresented: $showError) {
+ Alert(
+ title: Text("Error"),
+ message: Text(errorMessage),
+ dismissButton: .default(Text("OK"))
+ )
+ }
+
+ // Loading indicator
+ if isLoading {
+ loadingView()
+ }
+ }
+ .navigationBarHidden(true)
+ }
+
+ // MARK: - View Components
+
+ /// App header with icon and welcome text
+ 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)
+ }
+
+ /// Apple Sign In button
+ private func signInButton() -> some View {
+ SignInWithAppleButton(
+ onRequest: { request in
+ // Generate nonce for security
+ let nonce = String.randomURLSafeString(length: 32)
+ self.currentNonce = nonce
+
+ // Configure the request
+ request.requestedScopes = [.fullName, .email]
+ request.nonce = self.sha256(nonce)
+ },
+ onCompletion: handleAppleSignIn
+ )
+ .signInWithAppleButtonStyle(.black)
+ .frame(height: 50)
+ .padding(.horizontal, 40)
+ .cornerRadius(10)
+ }
+
+ /// Terms and Privacy policy links
+ private func termsAndPrivacyView() -> some View {
+ VStack(spacing: 8) {
+ Text("By continuing, you agree to our")
+ .font(.caption)
+ .foregroundColor(.secondary)
+
+ HStack(spacing: 16) {
+ Button("Terms of Service") {
+ openURL("https://yourwebsite.com/terms")
+ }
+ .font(.caption)
+ .foregroundColor(.blue)
+
+ Text("•")
+ .foregroundColor(.secondary)
+
+ Button("Privacy Policy") {
+ openURL("https://yourwebsite.com/privacy")
+ }
+ .font(.caption)
+ .foregroundColor(.blue)
+ }
+ }
+ .padding(.bottom, 24)
+ }
+
+ /// Loading overlay view
+ private func loadingView() -> some View {
+ return ZStack {
+ Color.black.opacity(0.4)
+ .edgesIgnoringSafeArea(.all)
+
+ ProgressView()
+ .scaleEffect(1.5)
+ .progressViewStyle(CircularProgressViewStyle(tint: .white))
}
}
- // Example function to handle login with your backend
- private func loginWithApple(userId: String, email: String?, name: String) {
+ // MARK: - Apple Sign In Handlers
+
+ /// Handles the result of Apple Sign In
+ private func handleAppleSignIn(result: Result) {
+ switch result {
+ case .success(let authResults):
+ processAppleIDCredential(authResults.credential)
+ case .failure(let error):
+ handleSignInError(error)
+ }
+ }
+
+ /// Processes the Apple ID credential
+ private func processAppleIDCredential(_ credential: ASAuthorizationCredential) {
+ guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else {
+ showError(message: "Unable to process Apple ID credentials")
+ return
+ }
+
+ // Get user data
+ let userId = appleIDCredential.user
+ let email = appleIDCredential.email ?? ""
+ let fullName = [
+ appleIDCredential.fullName?.givenName,
+ appleIDCredential.fullName?.familyName
+ ]
+ .compactMap { $0 }
+ .joined(separator: " ")
+
+ // Get the identity token
+ guard let identityTokenData = appleIDCredential.identityToken,
+ let identityToken = String(data: identityTokenData, encoding: .utf8) else {
+ showError(message: "Unable to fetch identity token")
+ return
+ }
+
+ // Get the authorization code (optional)
+ var authCode: String? = nil
+ if let authCodeData = appleIDCredential.authorizationCode {
+ authCode = String(data: authCodeData, encoding: .utf8)
+ }
+
+ // Proceed with backend authentication
+ authenticateWithBackend(
+ userId: userId,
+ email: email,
+ name: fullName,
+ identityToken: identityToken,
+ authCode: authCode
+ )
+ }
+
+ // MARK: - Network Operations
+
+ /// Authenticates the user with the backend server
+ private func authenticateWithBackend(
+ userId: String,
+ email: String,
+ name: String,
+ identityToken: String,
+ authCode: String?
+ ) {
isLoading = true
- // Replace with your actual API endpoint
- let url = "https://your-api-endpoint.com/auth/apple"
- let parameters: [String: Any] = [
+ let url = "https://your-api-endpoint.com/api/auth/apple"
+ var parameters: [String: Any] = [
"appleUserId": userId,
- "email": email ?? "",
+ "email": email,
"name": name,
- // Add any other required parameters
+ "identityToken": identityToken
]
+ // Add authorization code if available
+ if let authCode = authCode {
+ parameters["authorizationCode"] = authCode
+ }
+
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.validate()
.responseJSON { response in
- isLoading = false
+ self.isLoading = false
switch response.result {
case .success(let value):
- print("Login successful: \(value)")
- // Handle successful login (e.g., save auth token, navigate to home screen)
- dismiss()
-
+ print("Authentication successful: \(value)")
+ self.handleSuccessfulAuthentication()
case .failure(let error):
- print("Login failed: \(error.localizedDescription)")
- // Show error message to user
+ self.handleAuthenticationError(error)
}
}
}
+
+ // MARK: - Helper Methods
+
+ /// Handles successful authentication
+ private func handleSuccessfulAuthentication() {
+ DispatchQueue.main.async {
+ self.dismiss()
+ }
+ }
+
+ /// Handles sign in errors
+ private func handleSignInError(_ error: Error) {
+ let errorMessage = (error as NSError).localizedDescription
+ print("Apple Sign In failed: \(errorMessage)")
+ showError(message: "Sign in failed: \(error.localizedDescription)")
+ }
+
+ /// Handles authentication errors
+ private func handleAuthenticationError(_ error: AFError) {
+ let errorMessage = error.localizedDescription
+ print("API Error: \(errorMessage)")
+ showError(message: "Failed to sign in: \(errorMessage)")
+ }
+
+ /// Shows error message to the user
+ private func showError(message: String) {
+ DispatchQueue.main.async {
+ self.errorMessage = message
+ self.showError = true
+ }
+ }
+
+ /// Opens a URL in Safari
+ 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)
+ let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined()
+ return hashString
+ }
}
-#Preview {
- LoginView()
+// MARK: - String Extension for Random String Generation
+
+extension String {
+ /// Generates a random string of the specified length using URL-safe characters
+ /// - Parameter length: The length of the random string to generate
+ /// - Returns: A random string containing URL-safe characters
+ static func randomURLSafeString(length: Int) -> String {
+ let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
+ var randomString = ""
+
+ for _ in 0..
+
+
+
+ com.apple.developer.applesignin
+
+ Default
+
+
+