This commit is contained in:
anlicheng 2026-03-24 23:43:27 +08:00
parent 7872604857
commit 2f2c5420e2
9 changed files with 86 additions and 132 deletions

View File

@ -19,11 +19,12 @@ class AppContext {
var vpnOptions: [String: NSObject]? = nil var vpnOptions: [String: NSObject]? = nil
// app // app
var loginScene: LoginScene = .login(username: nil) var appScene: AppScene = .login(username: nil)
// //
enum LoginScene: Equatable { enum AppScene: Equatable {
case login(username: String?) case login(username: String?)
case logined
case register case register
case resetPassword case resetPassword
} }

View File

@ -19,63 +19,67 @@ struct LoginView: View {
} }
var body: some View { var body: some View {
VStack(spacing: 0) { ZStack {
// Logo Color.clear
VStack(spacing: 12) {
ZStack { VStack(spacing: 0) {
Circle() // Logo
.fill(Color.accentColor.opacity(0.1)) VStack(spacing: 12) {
.frame(width: 80, height: 80) 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 Text("PunchNet")
.font(.system(size: 38, weight: .semibold)) .font(.system(size: 24, weight: .bold, design: .rounded))
.foregroundColor(.accentColor) .tracking(1)
} }
.padding(.top, 40)
.padding(.bottom, 30)
Text("PunchNet") //
.font(.system(size: 24, weight: .bold, design: .rounded)) Picker("", selection: $authMethod) {
.tracking(1) ForEach(AuthMethod.allCases, id: \.self) { method in
} Text(method.rawValue).tag(method)
.padding(.top, 40) }
.padding(.bottom, 30)
//
Picker("", selection: $authMethod) {
ForEach(AuthMethod.allCases, id: \.self) { method in
Text(method.rawValue).tag(method)
} }
} .pickerStyle(.segmented)
.pickerStyle(.segmented) .frame(width: 220)
.frame(width: 220) .padding(.bottom, 30)
.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)
Text("服务状态正常") //
.font(.system(size: 11)) ZStack {
.foregroundColor(.secondary) 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 password: String = ""
@State private var isLoading = false @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 showAlert: Bool = false
@State private var errorMessage: String = "" @State private var errorMessage: String = ""
@ -109,7 +109,7 @@ struct LoginAccountView: View {
HStack { HStack {
Button("注册") { Button("注册") {
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.loginScene = .register self.appContext.appScene = .register
} }
} }
.buttonStyle(.link) .buttonStyle(.link)
@ -118,7 +118,7 @@ struct LoginAccountView: View {
Button("忘记密码?") { Button("忘记密码?") {
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.loginScene = .resetPassword self.appContext.appScene = .resetPassword
} }
} }
.buttonStyle(.link) .buttonStyle(.link)
@ -173,10 +173,7 @@ struct LoginAccountView: View {
do { do {
_ = try await appContext.loginWith(username: username, password: password) _ = try await appContext.loginWith(username: username, password: password)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
// 2. self.appContext.appScene = .logined
openWindow(id: "logined")
// 3.
dismiss()
} }
} catch let err as SDLAPIError { } catch let err as SDLAPIError {
@ -192,10 +189,6 @@ struct LoginAccountView: View {
// MARK: - // MARK: -
struct LoginTokenView: View { struct LoginTokenView: View {
@Environment(AppContext.self) var appContext: AppContext @Environment(AppContext.self) var appContext: AppContext
// 1.
@Environment(\.dismiss) private var dismiss
@Environment(\.openWindow) private var openWindow
@State private var token = "" @State private var token = ""
@State private var isLoading = false @State private var isLoading = false
@ -242,10 +235,7 @@ struct LoginTokenView: View {
do { do {
_ = try await appContext.loginWith(token: token) _ = try await appContext.loginWith(token: token)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
// 2. self.appContext.appScene = .logined
openWindow(id: "logined")
// 3.
dismiss()
} }
} catch let err as SDLAPIError { } catch let err as SDLAPIError {
self.showAlert = true self.showAlert = true

View File

@ -16,10 +16,13 @@ struct LoginRootView: View {
// 1. // 1.
// 使 ZStack Group // 使 ZStack Group
ZStack(alignment: .center) { ZStack(alignment: .center) {
switch appContext.loginScene { switch appContext.appScene {
case .login(username: let username): case .login(username: let username):
LoginView(username: username) LoginView(username: username)
.id("scene_login") // ID .id("scene_login") // ID
case .logined:
NetworkView()
.id("scene_logined")
case .register: case .register:
RegisterRootView() RegisterRootView()
.id("scene_register") .id("scene_register")
@ -28,10 +31,6 @@ struct LoginRootView: View {
.id("scene_reset") .id("scene_reset")
} }
} }
// 2.
.frame(width: 380, height: 500)
// 3. RootView
.clipped()
.transition(.asymmetric( .transition(.asymmetric(
insertion: .move(edge: .trailing).combined(with: .opacity), insertion: .move(edge: .trailing).combined(with: .opacity),
removal: .move(edge: .leading).combined(with: .opacity) // leading removal: .move(edge: .leading).combined(with: .opacity) // leading
@ -43,7 +42,7 @@ struct LoginRootView: View {
} }
} }
// 4. Scene // 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) .animation(.spring(duration: 0.4), value: updateManager.showUpdateOverlay)
// macOS // macOS
.background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)) .background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow))

View File

@ -11,7 +11,6 @@ struct MainMenuBar: View {
@State private var vpnManager = VPNManager.shared @State private var vpnManager = VPNManager.shared
@Environment(AppContext.self) private var appContext: AppContext @Environment(AppContext.self) private var appContext: AppContext
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(\.dismissWindow) private var dismissWindow
var body: some View { var body: some View {
VStack { VStack {
@ -36,9 +35,9 @@ struct MainMenuBar: View {
Divider() Divider()
// Button("") { Button("打开控制面板") {
// openWindow(id: appContext.isLoggedIn ? "logined" : "login") openWindow(id: "main")
// } }
SettingsLink { SettingsLink {
Text("设置") Text("设置")
@ -58,10 +57,6 @@ struct MainMenuBar: View {
private func startVPN() async { private func startVPN() async {
if let options = appContext.vpnOptions { if let options = appContext.vpnOptions {
try? await vpnManager.enableVpn(options: options) try? await vpnManager.enableVpn(options: options)
dismissWindow(id: "login")
openWindow(id: "logined")
} else {
openWindow(id: "login")
} }
} }

View File

@ -20,7 +20,6 @@ enum NetworkShowMode: String, CaseIterable {
// MARK: - // MARK: -
struct NetworkView: View { struct NetworkView: View {
@Environment(AppContext.self) var appContext: AppContext @Environment(AppContext.self) var appContext: AppContext
@Environment(\.openWindow) private var openWindow
@State private var showMode: NetworkShowMode = .resource @State private var showMode: NetworkShowMode = .resource
@State private var connectState: ConnectState = .disconnected @State private var connectState: ConnectState = .disconnected

View File

@ -44,7 +44,7 @@ struct RegisterRootView: View {
Button(action: { Button(action: {
// //
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.loginScene = .login(username: nil) self.appContext.appScene = .login(username: nil)
} }
}) { }) {
HStack { HStack {
@ -439,7 +439,7 @@ struct RegisterSuccessView: View {
Button(action: { Button(action: {
// //
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.loginScene = .login(username: registerModel.username) self.appContext.appScene = .login(username: registerModel.username)
} }
}) { }) {
Text("立即开始使用") Text("立即开始使用")

View File

@ -40,7 +40,7 @@ struct ResetPasswordRootView: View {
Button(action: { Button(action: {
// //
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.loginScene = .login(username: nil) self.appContext.appScene = .login(username: nil)
} }
}) { }) {
HStack { HStack {
@ -362,7 +362,7 @@ struct ResetPasswordSuccessView: View {
Button(action: { Button(action: {
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { 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("返回登录") Text("返回登录")

View File

@ -28,7 +28,7 @@ struct SettingsAccountView: View {
} else { } else {
// //
Button { Button {
self.openMainWindow(id: "login") self.openWindow(id: "main")
} label: { } label: {
Text("登录") Text("登录")
.fontWeight(.medium) .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 { private func sectionHeader(title: String, icon: String) -> some View {
HStack { HStack {
@ -117,7 +100,6 @@ extension SettingsAccountView {
struct AccountCreditView: View { struct AccountCreditView: View {
@Environment(AppContext.self) var appContext: AppContext @Environment(AppContext.self) var appContext: AppContext
@Environment(\.openWindow) var openWindow @Environment(\.openWindow) var openWindow
@Environment(\.dismissWindow) var dismissWindow
let username: String let username: String
@ -133,12 +115,7 @@ extension SettingsAccountView {
Task { @MainActor in Task { @MainActor in
try await appContext.logout() try await appContext.logout()
} }
self.openWindow(id: "main")
self.dismissWindow(id: "logined")
self.dismissWindow(id: "settings")
self.openWindow(id: "login")
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.foregroundColor(.red) .foregroundColor(.red)
@ -150,7 +127,6 @@ extension SettingsAccountView {
struct TokenCreditView: View { struct TokenCreditView: View {
@Environment(AppContext.self) var appContext: AppContext @Environment(AppContext.self) var appContext: AppContext
@Environment(\.openWindow) var openWindow @Environment(\.openWindow) var openWindow
@Environment(\.dismissWindow) var dismissWindow
let token: String let token: String
@ -160,11 +136,7 @@ extension SettingsAccountView {
Task { @MainActor in Task { @MainActor in
try await appContext.logout() try await appContext.logout()
} }
self.openWindow(id: "main")
self.dismissWindow(id: "logined")
self.dismissWindow(id: "settings")
self.openWindow(id: "login")
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.foregroundColor(.red) .foregroundColor(.red)

View File

@ -27,7 +27,6 @@ struct punchnetApp: App {
}() }()
*/ */
@Environment(\.openWindow) private var openWindow
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
private var noticeServer: UDPNoticeCenterServer private var noticeServer: UDPNoticeCenterServer
@ -45,10 +44,16 @@ struct punchnetApp: App {
} }
var body: some Scene { var body: some Scene {
Window("登陆", id: "login") { Window("Punchnet", id: "main") {
LoginRootView() LoginRootView()
.navigationTitle("") .navigationTitle("")
.environment(self.appContext) .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 { .onAppear {
self.showPrivacy = !hasAcceptedPrivacy self.showPrivacy = !hasAcceptedPrivacy
} }
@ -60,14 +65,7 @@ struct punchnetApp: App {
.windowToolbarStyle(.unified) .windowToolbarStyle(.unified)
.windowResizability(.contentSize) .windowResizability(.contentSize)
.defaultPosition(.center) .defaultPosition(.center)
.windowStyle(.hiddenTitleBar)
Window("网络", id: "logined") {
NetworkView()
.environment(self.appContext)
.frame(width: 750, height: 500)
}
.windowResizability(.contentSize)
.defaultPosition(.center)
Settings { Settings {
SettingsView() SettingsView()
@ -77,7 +75,7 @@ struct punchnetApp: App {
.windowResizability(.contentSize) .windowResizability(.contentSize)
.defaultPosition(.center) .defaultPosition(.center)
MenuBarExtra("punchnet", image: "logo_32") { MenuBarExtra("Punchnet", image: "logo_32") {
MainMenuBar() MainMenuBar()
.environment(appContext) .environment(appContext)
} }