feat: 暂提

This commit is contained in:
jinyaqiu 2025-08-27 13:31:03 +08:00
parent 963c87a9b9
commit 3165c1e7ea
4 changed files with 214 additions and 115 deletions

View File

@ -52,14 +52,8 @@ struct BlindBoxView: View {
if let data = data, let image = UIImage(data: data) { if let data = data, let image = UIImage(data: data) {
let media = MediaType.image(image) let media = MediaType.image(image)
DispatchQueue.main.async { DispatchQueue.main.async {
// Present the outcome view modally // Navigate to the outcome view using router
let outcomeView = BlindOutcomeView(media: media) Router.shared.navigate(to: .blindOutcome(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)
}
} }
} }
}.resume() }.resume()

View File

@ -10,76 +10,120 @@ struct BlindOutcomeView: View {
@State private var isPlaying = false @State private var isPlaying = false
var body: some View { var body: some View {
ZStack { NavigationView {
Color.black.edgesIgnoringSafeArea(.all) ZStack {
Color.themeTextWhiteSecondary.ignoresSafeArea()
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)
// Media content VStack(spacing: 0) {
ZStack { // Custom navigation header
switch media { HStack {
case .image(let uiImage): Button(action: {
Image(uiImage: uiImage) // Pop two view controllers to go back two levels
.resizable() if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
.scaledToFit() let window = windowScene.windows.first,
.onTapGesture { let rootVC = window.rootViewController as? UINavigationController {
withAnimation { let viewControllers = rootVC.viewControllers
isFullscreen.toggle() 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, _): Image(systemName: "chevron.left")
// Create an AVPlayer with the video URL .font(.headline)
let player = AVPlayer(url: url) .foregroundColor(Color.themeTextMessageMain)
VideoPlayer(player: player) }
.onAppear { Spacer()
player.play() Text("Blind Box")
isPlaying = true .font(.headline)
} .foregroundColor(Color.themeTextMessageMain)
.onDisappear { Spacer()
player.pause() // Invisible spacer to balance the layout
isPlaying = false 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)
} }
} .navigationBarBackButtonHidden(true)
.navigationBarHidden(true) .statusBar(hidden: isFullscreen)
.statusBar(hidden: isFullscreen) .fullScreenCover(isPresented: $isFullscreen) {
.fullScreenCover(isPresented: $isFullscreen) { if case .video(let url, _) = media {
if case .video(let url, _) = media { let player = AVPlayer(url: url)
let player = AVPlayer(url: url) FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: player)
FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: player) } else {
} else { FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: nil)
FullscreenMediaView(media: media, isPresented: $isFullscreen, isPlaying: $isPlaying, player: nil) }
} }
} }
} }

View File

@ -23,14 +23,14 @@ struct LoginView: View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("Hi, I'm MeMo!") Text("Hi, I'm MeMo!")
.font(Typography.font(for: .largeTitle)) .font(Typography.font(for: .largeTitle, family: .quicksandBold))
.foregroundColor(.black) .foregroundColor(.black)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.top, 44) .padding(.top, 44)
Text("Welcome~") Text("Welcome~")
.font(Typography.font(for: .largeTitle)) .font(Typography.font(for: .largeTitle, family: .quicksandBold))
.foregroundColor(.black) .foregroundColor(.black)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24) .padding(.horizontal, 24)
@ -71,28 +71,30 @@ struct LoginView: View {
// MARK: - Views // MARK: - Views
private func signInButton() -> some View { private func signInButton() -> some View {
print("🟢 [Debug] Sign in button tapped") print(" [1] 用户点击了登录按钮")
return AppleSignInButton { request in return AppleSignInButton { request in
print("🔵 [Debug] Creating sign in request") print(" 开始创建登录请求")
let nonce = String.randomURLSafeString(length: 32) let nonce = String.randomURLSafeString(length: 32)
self.currentNonce = nonce self.currentNonce = nonce
request.nonce = self.sha256(nonce) request.nonce = self.sha256(nonce)
request.requestedScopes = [.fullName, .email] request.requestedScopes = [.fullName, .email]
print("🔵 [Debug] Sign in request configured with nonce") print(" 登录请求配置完成nonce 已设置")
} onCompletion: { result in } onCompletion: { result in
print("🔵 [Debug] Sign in completion handler triggered") print(" 收到 Apple 登录回调")
switch result { switch result {
case .success(let authResults): case .success(let authResults):
print(" [Apple Sign In] 登录授权成功") print(" Apple 登录授权成功,开始处理凭证")
if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential { if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential {
print("🔵 [Debug] Processing Apple ID credential") print(" 成功获取 Apple ID 凭证")
self.processAppleIDCredential(appleIDCredential) self.processAppleIDCredential(appleIDCredential)
} else { } else {
print("❌ [Debug] Failed to cast credential to ASAuthorizationAppleIDCredential") print(" 凭证类型转换失败")
print(" 凭证类型: \(type(of: authResults.credential))")
self.showError(message: "无法处理登录凭证")
} }
case .failure(let error): case .failure(let error):
print(" [Apple Sign In] 登录失败: \(error.localizedDescription)") print(" Apple 登录失败: \(error.localizedDescription)")
print("❌ [Debug] Error details: \(error as NSError)") print(" 错误详情: \(error as NSError)")
self.handleSignInError(error) self.handleSignInError(error)
} }
} }
@ -153,21 +155,22 @@ struct LoginView: View {
// MARK: - Authentication // MARK: - Authentication
private func handleAppleSignIn(result: Result<ASAuthorization, Error>) { private func handleAppleSignIn(result: Result<ASAuthorization, Error>) {
print("🔵 [Apple Sign In] 开始处理登录结果...") print(" 开始处理登录结果...")
switch result { switch result {
case .success(let authResults): case .success(let authResults):
print("✅ [Apple Sign In] 登录授权成功") print(" 登录授权成功")
processAppleIDCredential(authResults.credential) processAppleIDCredential(authResults.credential)
case .failure(let error): case .failure(let error):
print("❌ [Apple Sign In] 登录失败: \(error.localizedDescription)") print(" 登录失败: \(error.localizedDescription)")
handleSignInError(error) handleSignInError(error)
} }
} }
private func processAppleIDCredential(_ credential: ASAuthorizationCredential) { private func processAppleIDCredential(_ credential: ASAuthorizationCredential) {
print("🔵 [Apple ID] 开始处理凭证...") print(" 开始处理 Apple ID 凭证")
guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else { guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else {
print("❌ [Apple ID] 凭证类型不匹配") print(" 凭证类型不匹配")
print(" 实际凭证类型: \(type(of: credential))")
showError(message: "无法处理Apple ID凭证") showError(message: "无法处理Apple ID凭证")
return return
} }
@ -181,24 +184,26 @@ struct LoginView: View {
.compactMap { $0 } .compactMap { $0 }
.joined(separator: " ") .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, guard let identityTokenData = appleIDCredential.identityToken,
let identityToken = String(data: identityTokenData, encoding: .utf8) else { let identityToken = String(data: identityTokenData, encoding: .utf8) else {
print("❌ [Apple ID] 无法获取身份令牌") print(" 无法获取身份令牌")
showError(message: "无法获取身份令牌") showError(message: "无法获取身份令牌")
return return
} }
print(" 成功获取 identityToken")
var authCode: String? = nil var authCode: String? = nil
if let authCodeData = appleIDCredential.authorizationCode { if let authCodeData = appleIDCredential.authorizationCode {
authCode = String(data: authCodeData, encoding: .utf8) authCode = String(data: authCodeData, encoding: .utf8)
print(" [Apple ID] 获取到授权码") print(" 获取到授权码")
} else { } else {
print(" [Apple ID] 未获取到授权码(可选)") print(" 未获取到授权码(可选)")
} }
print("🔵 [Apple ID] 准备调用后端认证...") print(" 准备调用后端认证接口...")
authenticateWithBackend( authenticateWithBackend(
identityToken: identityToken, identityToken: identityToken,
authCode: authCode authCode: authCode
@ -211,8 +216,8 @@ struct LoginView: View {
identityToken: String, identityToken: String,
authCode: String? authCode: String?
) { ) {
print(" 开始后端认证流程...")
isLoading = true isLoading = true
print("🔵 [Backend] 开始后端认证...")
var parameters: [String: Any] = [ var parameters: [String: Any] = [
"token": identityToken, "token": identityToken,
@ -221,59 +226,115 @@ struct LoginView: View {
if let authCode = authCode { if let authCode = authCode {
parameters["authorization_code"] = authCode parameters["authorization_code"] = authCode
print(" 添加授权码到请求参数")
} }
print(" 发送认证请求到服务器...")
print(" 接口: /iam/login/oauth")
print(" 请求参数: \(parameters.keys)")
NetworkService.shared.post( NetworkService.shared.post(
path: "/iam/login/oauth", path: "/iam/login/oauth",
parameters: parameters parameters: parameters
) { (result: Result<AuthResponse, NetworkError>) in ) { (result: Result<AuthResponse, NetworkError>) -> Void in
DispatchQueue.main.async { let handleResult: () -> Void = {
self.isLoading = false
switch result { switch result {
case .success(let authResponse): case .success(let authResponse):
print("✅ [Backend] 认证成功") print("✅ [15] 后端认证成功")
// token
if let loginInfo = authResponse.data?.userLoginInfo { if let loginInfo = authResponse.data?.userLoginInfo {
print("🔑 [16] 保存认证信息")
print(" - 用户ID: \(loginInfo.userId)")
print(" - 昵称: \(loginInfo.nickname)")
KeychainHelper.saveAccessToken(loginInfo.accessToken) KeychainHelper.saveAccessToken(loginInfo.accessToken)
KeychainHelper.saveRefreshToken(loginInfo.refreshToken) KeychainHelper.saveRefreshToken(loginInfo.refreshToken)
// userId, nickname
print("👤 用户ID: \(loginInfo.userId)") print("🔄 [17] 准备跳转到用户信息页面...")
print("👤 昵称: \(loginInfo.nickname)") 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): case .failure(let error):
print("❌ [Backend] 认证失败: \(error.localizedDescription)") print("❌ [15] 后端认证失败")
self.errorMessage = error.localizedDescription 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.showError = true
self.isLoading = false print("❌ 登录失败: \(errorMessage)")
} }
} }
// Execute on main thread
if Thread.isMainThread {
handleResult()
} else {
DispatchQueue.main.async(execute: handleResult)
}
} }
} }
// MARK: - Helpers // MARK: - Helpers
private func handleSuccessfulAuthentication() { private func handleSuccessfulAuthentication() {
print("✅ [Auth] 登录成功,准备跳转到用户信息页面...") print(" 登录成功,准备跳转到用户信息页面...")
print("🔵 [Debug] isLoggedIn before update: \(isLoggedIn)") print(" isLoggedIn before update: \(isLoggedIn)")
DispatchQueue.main.async { DispatchQueue.main.async {
print("🔵 [Debug] Setting isLoggedIn to true") print(" Setting isLoggedIn to true")
self.isLoggedIn = true self.isLoggedIn = true
print("🔵 [Debug] isLoggedIn after update: \(self.isLoggedIn)") print(" isLoggedIn after update: \(self.isLoggedIn)")
} }
} }
private func handleSignInError(_ error: Error) { private func handleSignInError(_ error: Error) {
let errorMessage = (error as NSError).localizedDescription let errorMessage = (error as NSError).localizedDescription
print("❌ [Auth] 登录错误: \(errorMessage)") print(" 登录错误: \(errorMessage)")
showError(message: "登录失败: \(error.localizedDescription)") showError(message: "登录失败: \(error.localizedDescription)")
} }
private func handleAuthenticationError(_ error: Error) { private func handleAuthenticationError(_ error: Error) {
let errorMessage = error.localizedDescription let errorMessage = error.localizedDescription
print("❌ [Auth] 认证错误: \(errorMessage)") print(" 认证错误: \(errorMessage)")
DispatchQueue.main.async { DispatchQueue.main.async {
self.isLoggedIn = false self.isLoggedIn = false
self.showError(message: "登录失败: \(errorMessage)") self.showError(message: "登录失败: \(errorMessage)")

View File

@ -52,10 +52,10 @@ struct WakeApp: App {
.environmentObject(authState) .environmentObject(authState)
} else { } else {
// //
// LoginView() LoginView()
// .environmentObject(authState)
UserInfo()
.environmentObject(authState) .environmentObject(authState)
// UserInfo()
// .environmentObject(authState)
} }
} }
} }