diff --git a/punchnet/Views/AppContext.swift b/punchnet/Views/AppContext.swift index 5cced56..527fc30 100644 --- a/punchnet/Views/AppContext.swift +++ b/punchnet/Views/AppContext.swift @@ -19,11 +19,12 @@ class AppContext { var vpnOptions: [String: NSObject]? = nil // 当前app所处的场景 - var loginScene: LoginScene = .login(username: nil) + var appScene: AppScene = .login(username: nil) // 当前的场景 - enum LoginScene: Equatable { + enum AppScene: Equatable { case login(username: String?) + case logined case register case resetPassword } diff --git a/punchnet/Views/Login/LoginView.swift b/punchnet/Views/Login/LoginView.swift index 7b3ce6f..715934d 100644 --- a/punchnet/Views/Login/LoginView.swift +++ b/punchnet/Views/Login/LoginView.swift @@ -19,63 +19,67 @@ struct LoginView: View { } var body: some View { - VStack(spacing: 0) { - // 顶部 Logo 区域 - VStack(spacing: 12) { - ZStack { - Circle() - .fill(Color.accentColor.opacity(0.1)) - .frame(width: 80, height: 80) + ZStack { + Color.clear + + VStack(spacing: 0) { + // 顶部 Logo 区域 + VStack(spacing: 12) { + ZStack { + Circle() + .fill(Color.accentColor.opacity(0.1)) + .frame(width: 80, height: 80) + + Image(systemName: "network") // 建议使用 SF Symbol 保持精致感 + .font(.system(size: 38, weight: .semibold)) + .foregroundColor(.accentColor) + } - Image(systemName: "network") // 建议使用 SF Symbol 保持精致感 - .font(.system(size: 38, weight: .semibold)) - .foregroundColor(.accentColor) + Text("PunchNet") + .font(.system(size: 24, weight: .bold, design: .rounded)) + .tracking(1) } + .padding(.top, 40) + .padding(.bottom, 30) - Text("PunchNet") - .font(.system(size: 24, weight: .bold, design: .rounded)) - .tracking(1) - } - .padding(.top, 40) - .padding(.bottom, 30) - - // 原生分段切换器 - Picker("", selection: $authMethod) { - ForEach(AuthMethod.allCases, id: \.self) { method in - Text(method.rawValue).tag(method) + // 原生分段切换器 + Picker("", selection: $authMethod) { + ForEach(AuthMethod.allCases, id: \.self) { method in + Text(method.rawValue).tag(method) + } } - } - .pickerStyle(.segmented) - .frame(width: 220) - .padding(.bottom, 30) - - // 动态内容区 - ZStack { - switch authMethod { - case .account: - LoginAccountView(username: self.username ?? "") - .transition(.move(edge: .leading).combined(with: .opacity)) - case .token: - LoginTokenView() - .transition(.move(edge: .trailing).combined(with: .opacity)) - } - } - .animation(.spring(response: 0.3, dampingFraction: 0.8), value: authMethod) - .frame(height: 180) - - Spacer() - - // 底部页脚 - HStack(spacing: 4) { - Circle() - .fill(Color.green) - .frame(width: 8, height: 8) + .pickerStyle(.segmented) + .frame(width: 220) + .padding(.bottom, 30) - Text("服务状态正常") - .font(.system(size: 11)) - .foregroundColor(.secondary) + // 动态内容区 + ZStack { + switch authMethod { + case .account: + LoginAccountView(username: self.username ?? "") + .transition(.move(edge: .leading).combined(with: .opacity)) + case .token: + LoginTokenView() + .transition(.move(edge: .trailing).combined(with: .opacity)) + } + } + .animation(.spring(response: 0.3, dampingFraction: 0.8), value: authMethod) + .frame(height: 180) + + Spacer() + + // 底部页脚 + HStack(spacing: 4) { + Circle() + .fill(Color.green) + .frame(width: 8, height: 8) + + Text("服务状态正常") + .font(.system(size: 11)) + .foregroundColor(.secondary) + } + .padding(.bottom, 20) } - .padding(.bottom, 20) } } @@ -89,10 +93,6 @@ struct LoginAccountView: View { @State private var password: String = "" @State private var isLoading = false - // 1. 引入环境操作 - @Environment(\.dismiss) private var dismiss - @Environment(\.openWindow) private var openWindow - // 错误提示 @State private var showAlert: Bool = false @State private var errorMessage: String = "" @@ -109,7 +109,7 @@ struct LoginAccountView: View { HStack { Button("注册") { withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.appContext.loginScene = .register + self.appContext.appScene = .register } } .buttonStyle(.link) @@ -118,7 +118,7 @@ struct LoginAccountView: View { Button("忘记密码?") { withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.appContext.loginScene = .resetPassword + self.appContext.appScene = .resetPassword } } .buttonStyle(.link) @@ -173,10 +173,7 @@ struct LoginAccountView: View { do { _ = try await appContext.loginWith(username: username, password: password) withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - // 2. 先打开新窗口(顺序通常不影响,但先打开更稳妥) - openWindow(id: "logined") - // 3. 销毁当前窗口 - dismiss() + self.appContext.appScene = .logined } } catch let err as SDLAPIError { @@ -192,10 +189,6 @@ struct LoginAccountView: View { // MARK: - 密钥登录组件 struct LoginTokenView: View { @Environment(AppContext.self) var appContext: AppContext - - // 1. 引入环境操作 - @Environment(\.dismiss) private var dismiss - @Environment(\.openWindow) private var openWindow @State private var token = "" @State private var isLoading = false @@ -242,10 +235,7 @@ struct LoginTokenView: View { do { _ = try await appContext.loginWith(token: token) withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - // 2. 先打开新窗口(顺序通常不影响,但先打开更稳妥) - openWindow(id: "logined") - // 3. 销毁当前窗口 - dismiss() + self.appContext.appScene = .logined } } catch let err as SDLAPIError { self.showAlert = true diff --git a/punchnet/Views/LoginRootView.swift b/punchnet/Views/LoginRootView.swift index d537d50..1b85866 100644 --- a/punchnet/Views/LoginRootView.swift +++ b/punchnet/Views/LoginRootView.swift @@ -16,10 +16,13 @@ struct LoginRootView: View { // 1. 主要界面容器 // 使用 ZStack 代替 Group,因为它在处理内容对齐和转场重叠时更稳定 ZStack(alignment: .center) { - switch appContext.loginScene { + switch appContext.appScene { case .login(username: let username): LoginView(username: username) .id("scene_login") // 显式 ID 确保转场触发 + case .logined: + NetworkView() + .id("scene_logined") case .register: RegisterRootView() .id("scene_register") @@ -28,10 +31,6 @@ struct LoginRootView: View { .id("scene_reset") } } - // 2. 关键:设置容器尺寸行为 - .frame(width: 380, height: 500) - // 3. 限制裁剪,防止位移动画超出 RootView 的边界 - .clipped() .transition(.asymmetric( insertion: .move(edge: .trailing).combined(with: .opacity), removal: .move(edge: .leading).combined(with: .opacity) // 修改为 leading 更有“流转”感 @@ -43,7 +42,7 @@ struct LoginRootView: View { } } // 4. 统一处理 Scene 切换的动画 - .animation(.spring(duration: 0.5), value: appContext.loginScene) + .animation(.spring(duration: 0.5), value: appContext.appScene) .animation(.spring(duration: 0.4), value: updateManager.showUpdateOverlay) // 关键:macOS 标准毛玻璃背景 .background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)) diff --git a/punchnet/Views/MenuBar/MainMenuBar.swift b/punchnet/Views/MenuBar/MainMenuBar.swift index 5af6fdd..5f21500 100644 --- a/punchnet/Views/MenuBar/MainMenuBar.swift +++ b/punchnet/Views/MenuBar/MainMenuBar.swift @@ -11,7 +11,6 @@ struct MainMenuBar: View { @State private var vpnManager = VPNManager.shared @Environment(AppContext.self) private var appContext: AppContext @Environment(\.openWindow) private var openWindow - @Environment(\.dismissWindow) private var dismissWindow var body: some View { VStack { @@ -36,9 +35,9 @@ struct MainMenuBar: View { Divider() -// Button("打开控制面板") { -// openWindow(id: appContext.isLoggedIn ? "logined" : "login") -// } + Button("打开控制面板") { + openWindow(id: "main") + } SettingsLink { Text("设置") @@ -58,10 +57,6 @@ struct MainMenuBar: View { private func startVPN() async { if let options = appContext.vpnOptions { try? await vpnManager.enableVpn(options: options) - dismissWindow(id: "login") - openWindow(id: "logined") - } else { - openWindow(id: "login") } } diff --git a/punchnet/Views/Network/NetworkView.swift b/punchnet/Views/Network/NetworkView.swift index 16acbfa..e59eccd 100644 --- a/punchnet/Views/Network/NetworkView.swift +++ b/punchnet/Views/Network/NetworkView.swift @@ -20,7 +20,6 @@ enum NetworkShowMode: String, CaseIterable { // MARK: - 主网络视图 struct NetworkView: View { @Environment(AppContext.self) var appContext: AppContext - @Environment(\.openWindow) private var openWindow @State private var showMode: NetworkShowMode = .resource @State private var connectState: ConnectState = .disconnected diff --git a/punchnet/Views/Register/RegisterView.swift b/punchnet/Views/Register/RegisterView.swift index 626b8f7..4d4e1a9 100644 --- a/punchnet/Views/Register/RegisterView.swift +++ b/punchnet/Views/Register/RegisterView.swift @@ -44,7 +44,7 @@ struct RegisterRootView: View { Button(action: { // 执行返回逻辑,例如重置到登录 withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.appContext.loginScene = .login(username: nil) + self.appContext.appScene = .login(username: nil) } }) { HStack { @@ -439,7 +439,7 @@ struct RegisterSuccessView: View { Button(action: { // 关闭当前注册窗口 withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.appContext.loginScene = .login(username: registerModel.username) + self.appContext.appScene = .login(username: registerModel.username) } }) { Text("立即开始使用") diff --git a/punchnet/Views/ResetPassword/ResetPasswordView.swift b/punchnet/Views/ResetPassword/ResetPasswordView.swift index 552e15f..37663e3 100644 --- a/punchnet/Views/ResetPassword/ResetPasswordView.swift +++ b/punchnet/Views/ResetPassword/ResetPasswordView.swift @@ -40,7 +40,7 @@ struct ResetPasswordRootView: View { Button(action: { // 执行返回逻辑,例如重置到登录 withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.appContext.loginScene = .login(username: nil) + self.appContext.appScene = .login(username: nil) } }) { HStack { @@ -362,7 +362,7 @@ struct ResetPasswordSuccessView: View { Button(action: { withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.appContext.loginScene = .login(username: self.resetPasswordModel.username) + self.appContext.appScene = .login(username: self.resetPasswordModel.username) } }) { Text("返回登录") diff --git a/punchnet/Views/Settings/SettingsAccountView.swift b/punchnet/Views/Settings/SettingsAccountView.swift index 3e41f28..027b6aa 100644 --- a/punchnet/Views/Settings/SettingsAccountView.swift +++ b/punchnet/Views/Settings/SettingsAccountView.swift @@ -28,7 +28,7 @@ struct SettingsAccountView: View { } else { // 蓝色主按钮 Button { - self.openMainWindow(id: "login") + self.openWindow(id: "main") } label: { Text("登录") .fontWeight(.medium) @@ -44,23 +44,6 @@ struct SettingsAccountView: View { } } - // 打开窗口 - private func openMainWindow(id: String) { - let window = NSApp.windows.first { win in - if let idStr = win.identifier?.rawValue { - return idStr.starts(with: id) - } - return false - } - - if let window { - window.makeKeyAndOrderFront(nil) - NSApp.activate(ignoringOtherApps: true) - } else { - openWindow(id: id) - } - } - // 辅助头部组件 private func sectionHeader(title: String, icon: String) -> some View { HStack { @@ -117,7 +100,6 @@ extension SettingsAccountView { struct AccountCreditView: View { @Environment(AppContext.self) var appContext: AppContext @Environment(\.openWindow) var openWindow - @Environment(\.dismissWindow) var dismissWindow let username: String @@ -133,12 +115,7 @@ extension SettingsAccountView { Task { @MainActor in try await appContext.logout() } - - self.dismissWindow(id: "logined") - self.dismissWindow(id: "settings") - - self.openWindow(id: "login") - + self.openWindow(id: "main") } .buttonStyle(.bordered) .foregroundColor(.red) @@ -150,7 +127,6 @@ extension SettingsAccountView { struct TokenCreditView: View { @Environment(AppContext.self) var appContext: AppContext @Environment(\.openWindow) var openWindow - @Environment(\.dismissWindow) var dismissWindow let token: String @@ -160,11 +136,7 @@ extension SettingsAccountView { Task { @MainActor in try await appContext.logout() } - - self.dismissWindow(id: "logined") - self.dismissWindow(id: "settings") - - self.openWindow(id: "login") + self.openWindow(id: "main") } .buttonStyle(.bordered) .foregroundColor(.red) diff --git a/punchnet/punchnetApp.swift b/punchnet/punchnetApp.swift index ddc6dc5..610594f 100644 --- a/punchnet/punchnetApp.swift +++ b/punchnet/punchnetApp.swift @@ -27,7 +27,6 @@ struct punchnetApp: App { }() */ - @Environment(\.openWindow) private var openWindow @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate private var noticeServer: UDPNoticeCenterServer @@ -45,10 +44,16 @@ struct punchnetApp: App { } var body: some Scene { - Window("登陆", id: "login") { + Window("Punchnet", id: "main") { LoginRootView() .navigationTitle("") .environment(self.appContext) + .frame( + width: self.appContext.isLogined ? 900 : 380, + height: self.appContext.isLogined ? 600 : 500 + ) + // 增加动画:当状态改变时,窗口会像微信那样丝滑地变大/变小 + .animation(.spring(response: 0.4, dampingFraction: 0.8), value: self.appContext.isLogined) .onAppear { self.showPrivacy = !hasAcceptedPrivacy } @@ -60,14 +65,7 @@ struct punchnetApp: App { .windowToolbarStyle(.unified) .windowResizability(.contentSize) .defaultPosition(.center) - - Window("网络", id: "logined") { - NetworkView() - .environment(self.appContext) - .frame(width: 750, height: 500) - } - .windowResizability(.contentSize) - .defaultPosition(.center) + .windowStyle(.hiddenTitleBar) Settings { SettingsView() @@ -77,7 +75,7 @@ struct punchnetApp: App { .windowResizability(.contentSize) .defaultPosition(.center) - MenuBarExtra("punchnet", image: "logo_32") { + MenuBarExtra("Punchnet", image: "logo_32") { MainMenuBar() .environment(appContext) }