From 3165c1e7eaa0b7ef7732f6f3f8e63043ca8ce4c8 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Wed, 27 Aug 2025 13:31:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9A=82=E6=8F=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wake/View/Blind/BlindBox.swift | 10 +- wake/View/Blind/BlindOutCome.swift | 172 ++++++++++++++++++----------- wake/View/Login/Login.swift | 141 ++++++++++++++++------- wake/WakeApp.swift | 6 +- 4 files changed, 214 insertions(+), 115 deletions(-) diff --git a/wake/View/Blind/BlindBox.swift b/wake/View/Blind/BlindBox.swift index 570af09..e13b761 100644 --- a/wake/View/Blind/BlindBox.swift +++ b/wake/View/Blind/BlindBox.swift @@ -52,14 +52,8 @@ struct BlindBoxView: View { if let data = data, let image = UIImage(data: data) { let media = MediaType.image(image) DispatchQueue.main.async { - // Present the outcome view modally - let outcomeView = BlindOutcomeView(media: media) - .ignoresSafeArea() - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first { - window.rootViewController?.present(UIHostingController(rootView: outcomeView), animated: true) - } + // Navigate to the outcome view using router + Router.shared.navigate(to: .blindOutcome(media: media)) } } }.resume() diff --git a/wake/View/Blind/BlindOutCome.swift b/wake/View/Blind/BlindOutCome.swift index f60aa5e..d7e5728 100644 --- a/wake/View/Blind/BlindOutCome.swift +++ b/wake/View/Blind/BlindOutCome.swift @@ -10,76 +10,120 @@ struct BlindOutcomeView: View { @State private var isPlaying = false var body: some View { - ZStack { - Color.black.edgesIgnoringSafeArea(.all) - - VStack(spacing: 0) { - // Custom navigation header - HStack { - Button(action: { - presentationMode.wrappedValue.dismiss() - }) { - Image(systemName: "chevron.left") - .font(.title2) - .foregroundColor(.white) - .padding() - } - - Spacer() - - Text("Blind Box") - .font(.headline) - .foregroundColor(.white) - - Spacer() - - // Invisible view to balance the layout - Image(systemName: "chevron.left") - .font(.title2) - .foregroundColor(.clear) - .padding() - } - .background(Color.black) + NavigationView { + ZStack { + Color.themeTextWhiteSecondary.ignoresSafeArea() - // Media content - ZStack { - switch media { - case .image(let uiImage): - Image(uiImage: uiImage) - .resizable() - .scaledToFit() - .onTapGesture { - withAnimation { - isFullscreen.toggle() + VStack(spacing: 0) { + // Custom navigation header + HStack { + Button(action: { + // Pop two view controllers to go back two levels + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first, + let rootVC = window.rootViewController as? UINavigationController { + let viewControllers = rootVC.viewControllers + if viewControllers.count > 2 { + let destinationVC = viewControllers[viewControllers.count - 3] + rootVC.popToViewController(destinationVC, animated: true) + } else { + presentationMode.wrappedValue.dismiss() } + } else { + presentationMode.wrappedValue.dismiss() } - - case .video(let url, _): - // Create an AVPlayer with the video URL - let player = AVPlayer(url: url) - VideoPlayer(player: player) - .onAppear { - player.play() - isPlaying = true - } - .onDisappear { - player.pause() - isPlaying = false - } + }) { + Image(systemName: "chevron.left") + .font(.headline) + .foregroundColor(Color.themeTextMessageMain) + } + Spacer() + Text("Blind Box") + .font(.headline) + .foregroundColor(Color.themeTextMessageMain) + Spacer() + // Invisible spacer to balance the layout + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .opacity(0) + Text("Back") + .opacity(0) + } + .padding(.trailing, 8) } + .padding(.vertical, 8) + .background(Color.themeTextWhiteSecondary) + .overlay( + Rectangle() + .frame(height: 1) + .foregroundColor(Color.gray.opacity(0.3)), + alignment: .bottom + ) + Spacer() + // Media content + ZStack { + switch media { + case .image(let uiImage): + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .onTapGesture { + withAnimation { + isFullscreen.toggle() + } + } + + case .video(let url, _): + // Create an AVPlayer with the video URL + let player = AVPlayer(url: url) + VideoPlayer(player: player) + .onAppear { + player.play() + isPlaying = true + } + .onDisappear { + player.pause() + isPlaying = false + } + } + } + .frame(maxWidth: .infinity) + .frame(height: UIScreen.main.bounds.height * 0.4) // Takes 40% of screen height + .background(Color.black) + .padding() // Add some space below navigation bar + Spacer() + // Button below media + VStack(spacing: 16) { + Button(action: { + // Navigate to the desired view + // Replace with your navigation logic + print("Button tapped!") + }) { + Text("Go to Next View") + .font(.headline) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding() + .background(Color.blue) + .cornerRadius(10) + } + .padding(.horizontal, 40) + .padding(.top, 30) + + Spacer() // Push everything to the top + } + .frame(maxWidth: .infinity, maxHeight: .infinity) } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black) } - } - .navigationBarHidden(true) - .statusBar(hidden: isFullscreen) - .fullScreenCover(isPresented: $isFullscreen) { - if case .video(let url, _) = media { - let player = AVPlayer(url: url) - FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: player) - } else { - FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: nil) + .navigationBarBackButtonHidden(true) + .statusBar(hidden: isFullscreen) + .fullScreenCover(isPresented: $isFullscreen) { + if case .video(let url, _) = media { + let player = AVPlayer(url: url) + FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: player) + } else { + FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: nil) + } } } } diff --git a/wake/View/Login/Login.swift b/wake/View/Login/Login.swift index f2e7e42..5b51152 100644 --- a/wake/View/Login/Login.swift +++ b/wake/View/Login/Login.swift @@ -23,14 +23,14 @@ struct LoginView: View { VStack(alignment: .leading, spacing: 4) { Text("Hi, I'm MeMo!") - .font(Typography.font(for: .largeTitle)) + .font(Typography.font(for: .largeTitle, family: .quicksandBold)) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 24) .padding(.top, 44) Text("Welcome~") - .font(Typography.font(for: .largeTitle)) + .font(Typography.font(for: .largeTitle, family: .quicksandBold)) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 24) @@ -71,28 +71,30 @@ struct LoginView: View { // MARK: - Views private func signInButton() -> some View { - print("🟢 [Debug] Sign in button tapped") + print(" [1] 用户点击了登录按钮") return AppleSignInButton { request in - print("🔵 [Debug] Creating sign in request") + print(" 开始创建登录请求") let nonce = String.randomURLSafeString(length: 32) self.currentNonce = nonce request.nonce = self.sha256(nonce) request.requestedScopes = [.fullName, .email] - print("🔵 [Debug] Sign in request configured with nonce") + print(" 登录请求配置完成,nonce 已设置") } onCompletion: { result in - print("🔵 [Debug] Sign in completion handler triggered") + print(" 收到 Apple 登录回调") switch result { case .success(let authResults): - print("✅ [Apple Sign In] 登录授权成功") + print(" Apple 登录授权成功,开始处理凭证") if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential { - print("🔵 [Debug] Processing Apple ID credential") + print(" 成功获取 Apple ID 凭证") self.processAppleIDCredential(appleIDCredential) } else { - print("❌ [Debug] Failed to cast credential to ASAuthorizationAppleIDCredential") + print(" 凭证类型转换失败") + print(" 凭证类型: \(type(of: authResults.credential))") + self.showError(message: "无法处理登录凭证") } case .failure(let error): - print("❌ [Apple Sign In] 登录失败: \(error.localizedDescription)") - print("❌ [Debug] Error details: \(error as NSError)") + print(" Apple 登录失败: \(error.localizedDescription)") + print(" 错误详情: \(error as NSError)") self.handleSignInError(error) } } @@ -153,21 +155,22 @@ struct LoginView: View { // MARK: - Authentication private func handleAppleSignIn(result: Result) { - print("🔵 [Apple Sign In] 开始处理登录结果...") + print(" 开始处理登录结果...") switch result { case .success(let authResults): - print("✅ [Apple Sign In] 登录授权成功") + print(" 登录授权成功") processAppleIDCredential(authResults.credential) case .failure(let error): - print("❌ [Apple Sign In] 登录失败: \(error.localizedDescription)") + print(" 登录失败: \(error.localizedDescription)") handleSignInError(error) } } private func processAppleIDCredential(_ credential: ASAuthorizationCredential) { - print("🔵 [Apple ID] 开始处理凭证...") + print(" 开始处理 Apple ID 凭证") guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else { - print("❌ [Apple ID] 凭证类型不匹配") + print(" 凭证类型不匹配") + print(" 实际凭证类型: \(type(of: credential))") showError(message: "无法处理Apple ID凭证") return } @@ -181,24 +184,26 @@ struct LoginView: View { .compactMap { $0 } .joined(separator: " ") - print("ℹ️ [Apple ID] 用户数据 - ID: \(userId), 邮箱: \(email.isEmpty ? "未提供" : email), 姓名: \(fullName.isEmpty ? "未提供" : fullName)") + print(" 用户数据 - ID: \(userId), 邮箱: \(email.isEmpty ? "未提供" : email), 姓名: \(fullName.isEmpty ? "未提供" : fullName)") guard let identityTokenData = appleIDCredential.identityToken, let identityToken = String(data: identityTokenData, encoding: .utf8) else { - print("❌ [Apple ID] 无法获取身份令牌") + print(" 无法获取身份令牌") showError(message: "无法获取身份令牌") return } + print(" 成功获取 identityToken") + var authCode: String? = nil if let authCodeData = appleIDCredential.authorizationCode { authCode = String(data: authCodeData, encoding: .utf8) - print("ℹ️ [Apple ID] 获取到授权码") + print(" 获取到授权码") } else { - print("ℹ️ [Apple ID] 未获取到授权码(可选)") + print(" 未获取到授权码(可选)") } - print("🔵 [Apple ID] 准备调用后端认证...") + print(" 准备调用后端认证接口...") authenticateWithBackend( identityToken: identityToken, authCode: authCode @@ -211,8 +216,8 @@ struct LoginView: View { identityToken: String, authCode: String? ) { + print(" 开始后端认证流程...") isLoading = true - print("🔵 [Backend] 开始后端认证...") var parameters: [String: Any] = [ "token": identityToken, @@ -221,59 +226,115 @@ struct LoginView: View { if let authCode = authCode { parameters["authorization_code"] = authCode + print(" 添加授权码到请求参数") } + print(" 发送认证请求到服务器...") + print(" 接口: /iam/login/oauth") + print(" 请求参数: \(parameters.keys)") + NetworkService.shared.post( path: "/iam/login/oauth", parameters: parameters - ) { (result: Result) in - DispatchQueue.main.async { + ) { (result: Result) -> Void in + let handleResult: () -> Void = { + self.isLoading = false + switch result { case .success(let authResponse): - print("✅ [Backend] 认证成功") + print("✅ [15] 后端认证成功") - // 保存token等认证信息 if let loginInfo = authResponse.data?.userLoginInfo { + print("🔑 [16] 保存认证信息") + print(" - 用户ID: \(loginInfo.userId)") + print(" - 昵称: \(loginInfo.nickname)") + KeychainHelper.saveAccessToken(loginInfo.accessToken) KeychainHelper.saveRefreshToken(loginInfo.refreshToken) - // 可以在这里保存其他用户信息,如userId, nickname等 - print("👤 用户ID: \(loginInfo.userId)") - print("👤 昵称: \(loginInfo.nickname)") + + print("🔄 [17] 准备跳转到用户信息页面...") + print("🔍 isLoggedIn 当前值: \(self.isLoggedIn)") + + self.isLoggedIn = true + + print("✅ [18] isLoggedIn 已设置为 true") + print("🎉 登录流程完成,即将跳转") + } else { + print("⚠️ [16] 认证成功但返回的用户信息不完整") + self.errorMessage = "登录信息不完整,请重试" + self.showError = true } - self.isLoggedIn = true - case .failure(let error): - print("❌ [Backend] 认证失败: \(error.localizedDescription)") - self.errorMessage = error.localizedDescription + print("❌ [15] 后端认证失败") + print("⚠️ 错误类型: \(type(of: error))") + print("📝 错误信息: \(error.localizedDescription)") + + var errorMessage = "登录失败,请重试" + + switch error { + case .invalidURL: + print(" → 无效的URL") + errorMessage = "服务器地址无效" + case .noData: + print(" → 服务器未返回数据") + errorMessage = "服务器未响应,请检查网络" + case .decodingError(let error): + print(" → 数据解析失败: \(error.localizedDescription)") + errorMessage = "服务器响应格式错误" + case .serverError(let message): + print(" → 服务器错误: \(message)") + errorMessage = "服务器错误: \(message)" + case .unauthorized: + print(" → 认证失败: 未授权") + errorMessage = "登录信息已过期,请重新登录" + case .networkError(let error): + print(" → 网络错误: \(error.localizedDescription)") + errorMessage = "网络连接失败,请检查网络" + case .unknownError(let error): + print(" → 未知错误: \(error.localizedDescription)") + errorMessage = "发生未知错误" + case .other(let error): + print(" → 其他错误: \(error.localizedDescription)") + errorMessage = "发生错误: \(error.localizedDescription)" + } + + self.errorMessage = errorMessage self.showError = true - self.isLoading = false + print("❌ 登录失败: \(errorMessage)") } } + + // Execute on main thread + if Thread.isMainThread { + handleResult() + } else { + DispatchQueue.main.async(execute: handleResult) + } } } // MARK: - Helpers private func handleSuccessfulAuthentication() { - print("✅ [Auth] 登录成功,准备跳转到用户信息页面...") - print("🔵 [Debug] isLoggedIn before update: \(isLoggedIn)") + print(" 登录成功,准备跳转到用户信息页面...") + print(" isLoggedIn before update: \(isLoggedIn)") DispatchQueue.main.async { - print("🔵 [Debug] Setting isLoggedIn to true") + print(" Setting isLoggedIn to true") self.isLoggedIn = true - print("🔵 [Debug] isLoggedIn after update: \(self.isLoggedIn)") + print(" isLoggedIn after update: \(self.isLoggedIn)") } } private func handleSignInError(_ error: Error) { let errorMessage = (error as NSError).localizedDescription - print("❌ [Auth] 登录错误: \(errorMessage)") + print(" 登录错误: \(errorMessage)") showError(message: "登录失败: \(error.localizedDescription)") } private func handleAuthenticationError(_ error: Error) { let errorMessage = error.localizedDescription - print("❌ [Auth] 认证错误: \(errorMessage)") + print(" 认证错误: \(errorMessage)") DispatchQueue.main.async { self.isLoggedIn = false self.showError(message: "登录失败: \(errorMessage)") diff --git a/wake/WakeApp.swift b/wake/WakeApp.swift index 13ff9ad..85fefae 100644 --- a/wake/WakeApp.swift +++ b/wake/WakeApp.swift @@ -52,10 +52,10 @@ struct WakeApp: App { .environmentObject(authState) } else { // 未登录:显示登录界面 - // LoginView() - // .environmentObject(authState) - UserInfo() + LoginView() .environmentObject(authState) + // UserInfo() + // .environmentObject(authState) } } }