diff --git a/wake/ContentView.swift b/wake/ContentView.swift index d53f4ef..9adf215 100644 --- a/wake/ContentView.swift +++ b/wake/ContentView.swift @@ -6,8 +6,8 @@ struct ContentView: View { var body: some View { NavigationStack(path: $router.path) { - // Home entry: show the BlindBox home (all) - BlindBoxView(mediaType: .all) + // Home entry: gate decides destination by querying blind boxes first + GateView() .navigationDestination(for: AppRoute.self) { route in route.view } diff --git a/wake/Utils/Router.swift b/wake/Utils/Router.swift index 6f6829e..d4220d0 100644 --- a/wake/Utils/Router.swift +++ b/wake/Utils/Router.swift @@ -3,7 +3,6 @@ import SwiftUI @MainActor enum AppRoute: Hashable { case login - case avatarBox case feedbackView case feedbackDetail(type: FeedbackView.FeedbackType) case mediaUpload @@ -16,14 +15,13 @@ enum AppRoute: Hashable { case about case permissionManagement case feedback + case secondOnboarding @ViewBuilder var view: some View { switch self { case .login: LoginView() - case .avatarBox: - AvatarBoxView() case .feedbackView: FeedbackView() case .feedbackDetail(let type): @@ -48,6 +46,8 @@ enum AppRoute: Hashable { PermissionManagementView() case .feedback: FeedbackView() + case .secondOnboarding: + SecondOnboardingView() } } } diff --git a/wake/View/Blind/JoinModal.swift b/wake/View/Blind/JoinModal.swift deleted file mode 100644 index 2a21e5c..0000000 --- a/wake/View/Blind/JoinModal.swift +++ /dev/null @@ -1,229 +0,0 @@ -import SwiftUI - -struct JoinModal: View { - @Binding var isPresented: Bool - - var body: some View { - ZStack(alignment: .bottom) { - // Semi-transparent background - if isPresented { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - .onTapGesture { - withAnimation { - isPresented = false - } - } - } - - // Modal content - if isPresented { - VStack(spacing: 0) { - // IP Image peeking from top - HStack { - // Make sure you have an image named "IP" in your assets - SVGImageHtml(svgName: "IP1") - .frame(width: 116, height: 65) - .offset(x: 30) - Spacer() - } - .frame(height: 65) - - VStack(spacing: 0) { - // Close button on the right - HStack { - Spacer() - Button(action: { - withAnimation { - Router.shared.navigate(to: .blindBox(mediaType: .all)) - } - }) { - Image(systemName: "xmark") - .font(.system(size: 20, weight: .medium)) - .foregroundColor(.themeTextMessageMain) - .padding(12) - } - .padding(.trailing, 16) - } - - // 文本 - VStack(spacing: 8) { - Text("Join us!") - .font(Typography.font(for: .headline1, family: .quicksandBold)) - .foregroundColor(.themeTextMessageMain) - Text("Join us to get more exclusive benefits.") - .font(.system(size: 14, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - .padding(.vertical, 12) - // List content - VStack (alignment: .leading) { - HStack { - SVGImage(svgName: "JoinList") - .frame(width: 32, height: 32) - HStack (alignment: .top){ - Text("Unlimited") - .font(.system(size: 16, weight: .bold)) - .foregroundColor(.themeTextMessageMain) - Text(" blind box purchases.") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - } - .padding(.vertical, 12) - .padding(.leading,12) - HStack (alignment: .center) { - SVGImage(svgName: "JoinList") - .frame(width: 32, height: 32) - VStack (alignment: .leading,spacing: 4) { - HStack { - Text("Freely") - .font(.system(size: 16, weight: .bold)) - .foregroundColor(.themeTextMessageMain) - Text(" upload image and video") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - Text(" materials.") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - } - .padding(.vertical, 12) - .padding(.leading,12) - - HStack(alignment: .top) { - SVGImage(svgName: "JoinList") - .frame(width: 32, height: 32) - VStack (alignment: .leading,spacing: 4) { - HStack { - Text("500") - .font(.system(size: 16, weight: .bold)) - .foregroundColor(.themeTextMessageMain) - Text(" credits daily,") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - HStack(alignment: .top) { - VStack (alignment: .leading, spacing: 4) { - HStack { - Text("5000") - .font(.system(size: 16, weight: .bold)) - .foregroundColor(.themeTextMessageMain) - Text(" permanent credits on your first") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - Text(" purchase!") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.themeTextMessageMain) - } - } - } - } - .padding(.top, 12) - .padding(.leading,12) - HStack { - Spacer() // This will push the button to the right - Button(action: { - // 点击跳转到会员页面 - Router.shared.navigate(to: .subscribe) - }) { - HStack { - Text("See More") - .font(.system(size: 16)) - Image(systemName: "chevron.right") - .font(.system(size: 14)) - } - .foregroundColor(.themeTextMessageMain) - .padding(.vertical, 12) - .padding(.horizontal, 24) - .cornerRadius(20) - } - } - .padding(.trailing, 16) // Add some right padding to match the design - Button(action: { - // 点击跳转到会员页面 - Router.shared.navigate(to: .subscribe) - }) { - HStack { - Text("Subscribe") - .font(Typography.font(for: .body, family: .quicksandBold)) - Spacer() - Text("$1.00/Mon") - .font(Typography.font(for: .body, family: .quicksandBold)) - } - .foregroundColor(.themeTextMessageMain) - .padding(.vertical, 12) - .padding(.horizontal, 30) - .background(Color.themePrimary) - .cornerRadius(20) - } - .padding(.top, 16) - // 协议条款 - HStack(alignment: .center) { - Button(action: { - // Action for Terms of Service - if let url = URL(string: "https://memorywake.com/privacy-policy") { - UIApplication.shared.open(url) - } - }) { - Text("Terms of Service") - .font(.system(size: 12, weight: .regular)) - .foregroundColor(.themeTextMessage) - .underline() // Add underline - } - Rectangle() - .fill(Color.gray.opacity(0.5)) - .frame(width: 1, height: 16) - .padding(.vertical, 4) - Button(action: { - // 打开网页 - if let url = URL(string: "https://memorywake.com/privacy-policy") { - UIApplication.shared.open(url) - } - }) { - Text("Privacy Policy") - .font(.system(size: 12, weight: .regular)) - .foregroundColor(.themeTextMessage) - .underline() // Add underline - } - Rectangle() - .fill(Color.gray.opacity(0.5)) - .frame(width: 1, height: 16) - .padding(.vertical, 4) - Button(action: { - // Action for Restore Purchase - if let url = URL(string: "https://memorywake.com/privacy-policy") { - UIApplication.shared.open(url) - } - }) { - Text("AI Usage Guidelines") - .font(.system(size: 12, weight: .regular)) - .foregroundColor(.themeTextMessage) - .underline() // Add underline - } - } - .padding(.bottom, 24) - .frame(maxWidth: .infinity, alignment: .center) - } - .padding(.horizontal, 16) - } - .background(Color.white) - .cornerRadius(20, corners: [.topLeft, .topRight]) - } - .frame(height: nil) - .transition(.move(edge: .bottom)) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) - .edgesIgnoringSafeArea(.all) - .animation(.easeInOut, value: isPresented) - } -} - -struct JoinModal_Previews: PreviewProvider { - static var previews: some View { - JoinModal(isPresented: .constant(true)) - } -} diff --git a/wake/View/BlindBox/BlindBoxView.swift b/wake/View/BlindBox/BlindBoxView.swift index 0b74bff..d82175f 100644 --- a/wake/View/BlindBox/BlindBoxView.swift +++ b/wake/View/BlindBox/BlindBoxView.swift @@ -50,6 +50,46 @@ struct BlindBoxView: View { self.mediaType = mediaType } + // MARK: - 第三种盲盒(RetrievalGeneration)列表轮询 + private func startListPolling() { + // 复用 pollingTimer,每5秒更新一次列表状态 + pollingTimer?.invalidate() + pollingTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in + self.checkListStatus() + } + // 立即检查一次 + checkListStatus() + } + + private func checkListStatus() { + NetworkService.shared.get( + path: "/blind_boxs/query", + parameters: nil + ) { (result: Result, NetworkError>) in + DispatchQueue.main.async { + switch result { + case .success(let response): + let list = response.data ?? [] + self.blindList = list + let lowered = list.map { ($0.boxType).lowercased() } + let statuses = list.map { $0.status } + let hasRetrievalUnopened = zip(lowered, statuses).contains { (t, s) in + t == "retrievalgeneration" && s == "Unopened" + } + if hasRetrievalUnopened { + withAnimation { self.animationPhase = .ready } + } else if statuses.contains("Preparing") { + withAnimation { self.animationPhase = .loading } + } else { + withAnimation { self.animationPhase = .none } + } + case .failure(let error): + print("❌ 列表轮询失败: \(error.localizedDescription)") + } + } + } + } + // 倒计时 private func startCountdown() { // 重置为36:50:20 @@ -144,6 +184,8 @@ struct BlindBoxView: View { self.animationPhase = .none } print("✅ 成功获取 \(self.blindList.count) 个盲盒") + // 启动第三种盲盒的列表轮询 + self.startListPolling() case .failure(let error): self.blindList = [] self.animationPhase = .none @@ -151,8 +193,9 @@ struct BlindBoxView: View { } } } - } + } } + // 轮询接口 private func startPolling() { stopPolling() diff --git a/wake/View/BlindBox/SecondOnboardingView.swift b/wake/View/BlindBox/SecondOnboardingView.swift new file mode 100644 index 0000000..51d51ed --- /dev/null +++ b/wake/View/BlindBox/SecondOnboardingView.swift @@ -0,0 +1,104 @@ +import SwiftUI + +/// A dedicated page to explain the "second" blind box requirement +/// and guide the user to upload 20 images and 5 videos without mixing +/// with the generic MediaUploadView responsibilities. +struct SecondOnboardingView: View { + @EnvironmentObject private var router: Router + + var body: some View { + ZStack { + Color.themeTextWhiteSecondary.ignoresSafeArea() + + VStack(spacing: 0) { + // Simple header + SimpleNaviHeader(title: "Create Your Second Blind Box") { + router.pop() + } + .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0) + + VStack(spacing: 20) { + // Hero / Illustration + SVGImageHtml(svgName: "IP1") + .frame(width: 140, height: 80) + .padding(.top, 24) + + VStack(spacing: 8) { + Text("Upload Requirements") + .font(Typography.font(for: .title2, family: .quicksandBold)) + .foregroundColor(.themeTextMessageMain) + Text("To generate your next blind box, please upload:") + .font(Typography.font(for: .body)) + .foregroundColor(.themeTextMessage) + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 8) { + SVGImage(svgName: "Upload").frame(width: 20, height: 20) + Text("20 Images") + .font(Typography.font(for: .body)) + .foregroundColor(.themeTextMessageMain) + } + HStack(spacing: 8) { + SVGImage(svgName: "Upload").frame(width: 20, height: 20) + Text("5 Videos") + .font(Typography.font(for: .body)) + .foregroundColor(.themeTextMessageMain) + } + } + .padding(.top, 4) + } + .padding(.horizontal) + + // Tips + HStack(spacing: 6) { + SVGImage(svgName: "Tips").frame(width: 16, height: 16) + Text("Higher quality media helps create better memories.") + .font(.caption) + .foregroundColor(.black) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(3) + } + .background(Color.themeTextWhite.cornerRadius(6)) + .padding(.horizontal) + + Spacer() + + // Primary CTAs + VStack(spacing: 12) { + Button(action: { + router.navigate(to: .mediaUpload) + }) { + Text("Start Uploading") + .font(.headline) + .foregroundColor(.themeTextMessageMain) + .frame(maxWidth: .infinity) + .frame(height: 56) + .background(Color.themePrimary) + .cornerRadius(28) + .padding(.horizontal, 24) + } + + Button(action: { + router.navigate(to: .userInfo) + }) { + Text("Upload Avatar First") + .font(.subheadline) + .foregroundColor(.themeTextMessage) + } + .padding(.bottom, 24) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.white) + .cornerRadius(16) + .padding() + + Spacer() + } + } + .navigationBarBackButtonHidden(true) + } +} + +#Preview { + SecondOnboardingView().environmentObject(Router.shared) +} diff --git a/wake/View/Gate/GateView.swift b/wake/View/Gate/GateView.swift new file mode 100644 index 0000000..1d37dae --- /dev/null +++ b/wake/View/Gate/GateView.swift @@ -0,0 +1,108 @@ +import SwiftUI + +struct GateView: View { + @EnvironmentObject private var router: Router + @State private var isNavigated = false + @State private var isLoading = true + @State private var errorMessage: String? = nil + + var body: some View { + ZStack { + Color.themeTextWhiteSecondary.ignoresSafeArea() + + if isLoading { + VStack(spacing: 16) { + ProgressView() + Text("Loading...") + .font(Typography.font(for: .body)) + .foregroundColor(.themeTextMessage) + } + } else if let error = errorMessage { + // Briefly inform the user, then auto-navigate to avatar page + VStack(spacing: 12) { + Text("Network error, going to Avatar setup...") + .font(Typography.font(for: .subtitle, family: .quicksandBold)) + .foregroundColor(.themeTextMessageMain) + Text(error) + .font(.caption) + .foregroundColor(.themeTextMessage) + .multilineTextAlignment(.center) + .padding(.horizontal) + } + } else { + // Should not be seen because we auto-route when done + EmptyView() + } + } + .onAppear(perform: queryAndRoute) + .navigationBarBackButtonHidden(true) + } + + private func queryAndRoute() { + guard !isNavigated else { return } + isLoading = true + errorMessage = nil + print("[Gate] Start queryAndRoute") + + NetworkService.shared.get( + path: "/blind_boxs/query", + parameters: nil + ) { (result: Result, NetworkError>) in + print("Query result: \(result)") + DispatchQueue.main.async { + isLoading = false + switch result { + case .success(let response): + let list = response.data ?? [] + print("[Gate] Success, list count: \(list.count)") + let lowered = list.map { ($0.boxType).lowercased() } + let hasFirst = lowered.contains(where: { $0 == "first" }) + let hasSecond = lowered.contains(where: { $0 == "second" }) + let hasRetrieval = lowered.contains(where: { $0 == "retrievalgeneration" }) + + print("hasFirst: \(hasFirst), hasSecond: \(hasSecond), hasRetrieval: \(hasRetrieval)") + // Routing rules (updated): + // - If has 'second' OR 'retrievalgeneration' -> normal home + // - Else if only 'first' exists -> second onboarding page + // - Else -> avatar upload page + if hasSecond || hasRetrieval { + print("[Gate] Route -> BlindBox .all") + navigateOnce(to: .blindBox(mediaType: .all)) + } else if hasFirst { + print("[Gate] Route -> SecondOnboardingView") + navigateOnce(to: .secondOnboarding) + } else { + print("[Gate] Route -> UserInfo (no first/second/retrieval)") + navigateOnce(to: .userInfo) + } + + // Defensive fallback: if for any reason not navigated, push avatar after small delay + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + if !isNavigated { + print("[Gate] Fallback routing -> UserInfo") + navigateOnce(to: .userInfo) + } + } + + case .failure(let error): + // Show an error briefly, then route to avatar page automatically + errorMessage = error.localizedDescription + print("Error: \(error.localizedDescription)") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { + navigateOnce(to: .userInfo) + } + } + } + } + } + + private func navigateOnce(to destination: AppRoute) { + guard !isNavigated else { return } + isNavigated = true + router.navigate(to: destination) + } +} + +#Preview { + GateView().environmentObject(Router.shared) +} diff --git a/wake/View/Owner/UserInfo/UserInfo.swift b/wake/View/Owner/UserInfo/UserInfo.swift index b2b8ab4..a013d60 100644 --- a/wake/View/Owner/UserInfo/UserInfo.swift +++ b/wake/View/Owner/UserInfo/UserInfo.swift @@ -15,6 +15,7 @@ struct UserInfo: View { @State private var uploadedFileId: String? // Add state for file ID @State private var errorMessage: String = "" @State private var showError: Bool = false + @State private var hasTriggeredFirstBox: Bool = false // 防止重复触发首个盲盒 // 添加一个状态来跟踪键盘是否显示 @State private var isKeyboardVisible = false @@ -260,6 +261,13 @@ struct UserInfo: View { // Clean up observers NotificationCenter.default.removeObserver(self) } + // 当头像文件上传完成后,自动触发首个盲盒创建 + .onChange(of: uploadedFileId) { newValue in + guard let fileId = newValue, !fileId.isEmpty else { return } + guard !hasTriggeredFirstBox else { return } + hasTriggeredFirstBox = true + generateFirstBlindBox(with: fileId) + } .onReceive(keyboardPublisher) { isVisible in withAnimation(.easeInOut(duration: 0.2)) { isKeyboardVisible = isVisible @@ -273,6 +281,85 @@ struct UserInfo: View { } } +// MARK: - 首个盲盒自动创建 +extension UserInfo { + private struct GenerateFileInfo: Codable { + let id: String + let fileName: String? + let url: String? + let metadata: [String: String]? + enum CodingKeys: String, CodingKey { + case id + case fileName = "file_name" + case url + case metadata + } + } + + private struct GenerateData: Codable { + let id: Int64? + let boxType: String? + let status: String? + let resultFile: GenerateFileInfo? + enum CodingKeys: String, CodingKey { + case id + case boxType = "box_type" + case status + case resultFile = "result_file" + } + } + + private struct GenerateResponse: Codable { + let code: Int + let data: GenerateData? + } + + private func generateFirstBlindBox(with fileId: String) { + let params: [String: Any] = [ + "box_type": "First", + "material_ids": [fileId] + ] + NetworkService.shared.postWithToken( + path: "/blind_box/generate", + parameters: params + ) { (result: Result) in + DispatchQueue.main.async { + switch result { + case .success(let response): + guard response.code == 0, let urlStr = response.data?.resultFile?.url, let url = URL(string: urlStr) else { + self.errorMessage = "Create first blind box failed: invalid response" + self.showError = true + return + } + // 下载图片并跳转到盲盒结果页 + URLSession.shared.dataTask(with: url) { data, _, error in + if let error = error { + DispatchQueue.main.async { + self.errorMessage = "Load result image failed: \(error.localizedDescription)" + self.showError = true + } + return + } + guard let data = data, let image = UIImage(data: data) else { + DispatchQueue.main.async { + self.errorMessage = "Invalid image data" + self.showError = true + } + return + } + DispatchQueue.main.async { + Router.shared.navigate(to: .blindOutcome(media: .image(image), time: nil, description: nil)) + } + }.resume() + case .failure(let error): + self.errorMessage = "Create first blind box failed: \(error.localizedDescription)" + self.showError = true + } + } + } + } +} + // MARK: - Settings Row View struct SettingsRow: View { let icon: String diff --git a/wake/View/Upload/MediaUploadView.swift b/wake/View/Upload/MediaUploadView.swift index ab0250c..ec21648 100644 --- a/wake/View/Upload/MediaUploadView.swift +++ b/wake/View/Upload/MediaUploadView.swift @@ -7,6 +7,82 @@ import CoreImage.CIFilterBuiltins extension Notification.Name { static let didAddFirstMedia = Notification.Name("didAddFirstMedia") } + +// MARK: - Second Box Generate + Polling Models & Logic +private struct GenerateFileInfo: Codable { + let id: String + let fileName: String? + let url: String? + let metadata: [String: String]? + enum CodingKeys: String, CodingKey { + case id + case fileName = "file_name" + case url + case metadata + } +} + +private struct GenerateData: Codable { + let id: Int64? + let boxType: String? + let status: String? + let resultFile: GenerateFileInfo? + let videoGenerateTime: String? + let description: String? + enum CodingKeys: String, CodingKey { + case id + case boxType = "box_type" + case status + case resultFile = "result_file" + case videoGenerateTime = "video_generate_time" + case description + } +} + +private struct GenerateResponse: Codable { + let code: Int + let data: GenerateData? +} + +private struct QueryResponse: Codable { + let code: Int + let data: GenerateData? +} + +extension MediaUploadView { + private func startPollingSecondBox(id: Int64) { + pollingTimer?.invalidate() + // 每2秒轮询一次查询接口 + pollingTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in + querySecondBox(id: id) + } + // 立即查询一次 + querySecondBox(id: id) + } + + private func querySecondBox(id: Int64) { + NetworkService.shared.get( + path: "/blind_box/query/\(id)", + parameters: nil + ) { (result: Result) in + DispatchQueue.main.async { + switch result { + case .success(let response): + guard response.code == 0 else { return } + if let urlStr = response.data?.resultFile?.url, let url = URL(string: urlStr) { + // 检测到结果,停止轮询并跳转结果页(视频) + pollingTimer?.invalidate() + pollingTimer = nil + isGeneratingSecond = false + Router.shared.navigate(to: .blindOutcome(media: .video(url, nil), time: response.data?.videoGenerateTime, description: response.data?.description)) + } + case .failure(let error): + print("❌ 查询第二个盲盒失败: \(error.localizedDescription)") + } + } + } + } +} /// 主上传视图 /// 提供媒体选择、预览和上传功能 @MainActor @@ -26,6 +102,10 @@ struct MediaUploadView: View { @State private var uploadComplete = false /// 上传完成的文件ID列表 @State private var uploadedFileIds: [[String: String]] = [] + /// 生成第二个盲盒时的加载与轮询 + @State private var isGeneratingSecond = false + @State private var generatedSecondBoxId: Int64? = nil + @State private var pollingTimer: Timer? = nil // MARK: - 视图主体 @@ -88,6 +168,28 @@ struct MediaUploadView: View { .onChange(of: uploadManager.uploadResults) { newResults in handleUploadCompletion(results: newResults) } + .overlay( + Group { + if isGeneratingSecond { + ZStack { + Color.black.opacity(0.2).ignoresSafeArea() + VStack(spacing: 12) { + ProgressView() + Text("Generating your blind box...") + .font(.caption) + .foregroundColor(.white) + } + .padding(16) + .background(Color.black.opacity(0.6)) + .cornerRadius(12) + } + } + } + ) + .onDisappear { + pollingTimer?.invalidate() + pollingTimer = nil + } } // MARK: - 子视图 @@ -320,30 +422,33 @@ struct MediaUploadView: View { print("⚠️ 没有可用的文件ID") return } - - // 准备请求参数 - let files = uploadResults.map { (_, result) -> [String: String] in - return [ - "file_id": result.fileId, - "preview_file_id": result.thumbnailId ?? result.fileId - ] - } - - // 发送POST请求到/material接口 + // 收集素材 file_id 列表 + let materialIds: [String] = uploadResults.map { $0.value.fileId } + // 调用创建第二个盲盒接口 + let params: [String: Any] = [ + "box_type": "Second", + "material_ids": materialIds + ] + isGeneratingSecond = true NetworkService.shared.postWithToken( - path: "/material", - parameters: files - ) { (result: Result) in - switch result { - case .success: - print("✅ 素材提交成功") - // 跳转到盲盒页面 - DispatchQueue.main.async { - Router.shared.navigate(to: .blindBox(mediaType: .video)) + path: "/blind_box/generate", + parameters: params + ) { (result: Result) in + DispatchQueue.main.async { + switch result { + case .success(let response): + guard response.code == 0, let id = response.data?.id else { + print("❌ 创建第二个盲盒失败:响应无效") + isGeneratingSecond = false + return + } + generatedSecondBoxId = id + // 开始轮询结果 + startPollingSecondBox(id: id) + case .failure(let error): + print("❌ 创建第二个盲盒失败: \(error.localizedDescription)") + isGeneratingSecond = false } - case .failure(let error): - print("❌ 素材提交失败: \(error.localizedDescription)") - // 这里可以添加错误处理逻辑,比如显示错误提示 } } } diff --git a/wake/WakeApp.swift b/wake/WakeApp.swift index e66f67b..150aec5 100644 --- a/wake/WakeApp.swift +++ b/wake/WakeApp.swift @@ -44,13 +44,8 @@ struct WakeApp: App { } else { // 根据登录状态显示不同视图 if authState.isAuthenticated { - // 已登录:显示主页面 - NavigationStack(path: $router.path) { - BlindBoxView(mediaType: .all) - .navigationDestination(for: AppRoute.self) { route in - route.view - } - } + // 已登录:通过 ContentView 进入(其内部会展示 GateView 并打印 [Gate] 日志) + ContentView() } else { // 未登录:显示登录界面 NavigationStack(path: $router.path) {