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
// 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
}

View File

@ -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

View File

@ -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))

View File

@ -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")
}
}

View File

@ -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

View File

@ -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("立即开始使用")

View File

@ -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("返回登录")

View File

@ -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)

View File

@ -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)
}