简化环境逻辑

This commit is contained in:
anlicheng 2026-03-24 00:05:17 +08:00
parent 1ffea953bf
commit c8c37954ce
9 changed files with 75 additions and 95 deletions

View File

@ -15,6 +15,17 @@ class AppContext {
// "/connect"
var networkContext: NetworkContext?
var loginCredit: Credit?
var networkSession: SDLAPIClient.NetworkSession?
// app
var appScene: AppScene = .login(username: nil)
enum Credit {
case token(token: String)
case accountAndPasword(account: String, password: String)
}
//
enum AppScene: Equatable {
case login(username: String?)
@ -23,11 +34,51 @@ class AppContext {
case resetPassword
}
// app
var appScene: AppScene = .login(username: nil)
init(noticePort: Int) {
self.noticePort = noticePort
}
func loginWith(credit: Credit) async throws -> Bool {
switch credit {
case .token(let token):
self.networkSession = try await SDLAPIClient.loginWithToken(token: token)
// keychain
if let data = token.data(using: .utf8) {
try KeychainStore.shared.save(data, account: "token")
}
case .accountAndPasword(let username, let password):
self.networkSession = try await SDLAPIClient.loginWithAccountAndPassword(username: username, password: password)
// keychain
if let data = "\(username):\(password)".data(using: .utf8) {
try KeychainStore.shared.save(data, account: "accountAndPasword")
}
}
self.loginCredit = credit
return true
}
func loadCacheToken() -> String? {
if let data = try? KeychainStore.shared.load(account: "token") {
return String(data: data, encoding: .utf8)
}
return nil
}
func loadCacheUsernameAndPassword() -> (String, String)? {
if let data = try? KeychainStore.shared.load(account: "accountAndPasword"),
let str = String(data: data, encoding: .utf8) {
let parts = str.split(separator: ":")
if parts.count == 2 {
return (String(parts[0]), String(parts[1]))
}
}
return nil
}
// 退
func logout() async throws {
try await VPNManager.shared.disableVpn()
}
}

View File

@ -9,7 +9,6 @@ import Observation
// MARK: -
struct LoginView: View {
@Environment(UserContext.self) var userContext: UserContext
@State private var authMethod: AuthMethod = .account
var username: String?
@ -87,7 +86,6 @@ struct LoginView: View {
// MARK: -
struct LoginAccountView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
@State var username: String = ""
@ -158,7 +156,7 @@ struct LoginAccountView: View {
Alert(title: Text("提示"), message: Text(self.errorMessage))
}
.onAppear {
if let (cacheUsername, cachePassword) = self.userContext.loadCacheUsernameAndPassword() {
if let (cacheUsername, cachePassword) = self.appContext.loadCacheUsernameAndPassword() {
self.username = cacheUsername
self.password = cachePassword
}
@ -172,7 +170,7 @@ struct LoginAccountView: View {
}
do {
_ = try await userContext.loginWith(credit: .accountAndPasword(account: username, password: password))
_ = try await appContext.loginWith(credit: .accountAndPasword(account: username, password: password))
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.appScene = .logined
}
@ -188,7 +186,6 @@ struct LoginAccountView: View {
// MARK: -
struct LoginTokenView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
@State private var token = ""
@ -221,7 +218,7 @@ struct LoginTokenView: View {
Alert(title: Text("提示"), message: Text(self.errorMessage))
}
.onAppear {
if let cacheToken = self.userContext.loadCacheToken() {
if let cacheToken = self.appContext.loadCacheToken() {
self.token = cacheToken
}
}
@ -234,7 +231,7 @@ struct LoginTokenView: View {
}
do {
_ = try await userContext.loginWith(credit: .token(token: token))
_ = try await appContext.loginWith(credit: .token(token: token))
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.appScene = .logined
}
@ -312,5 +309,5 @@ struct VisualEffectView: NSViewRepresentable {
#Preview {
LoginView()
.environment(UserContext())
.environment(AppContext(noticePort: 0))
}

View File

@ -11,7 +11,6 @@ enum ConnectState {
// MARK: -
struct NetworkView: View {
@Environment(UserContext.self) var userContext
@Environment(AppContext.self) var appContext
@Environment(\.openWindow) private var openWindow
@ -68,7 +67,7 @@ extension NetworkView {
statusIndicator
VStack(alignment: .leading, spacing: 2) {
Text(userContext.networkSession?.networkName ?? "未连接网络")
Text(appContext.networkSession?.networkName ?? "未连接网络")
.font(.system(size: 14, weight: .semibold))
if connectState == .connected {
@ -186,7 +185,7 @@ extension NetworkView {
isConnecting = true
Task {
do {
guard let session = userContext.networkSession else {
guard let session = appContext.networkSession else {
return
}
@ -269,7 +268,8 @@ struct NetworkNodeHeadView: View {
}
struct NetworkNodeDetailView: View {
@Environment(UserContext.self) var userContext
@Environment(AppContext.self) private var appContext: AppContext
var node: Node
@State private var resources: [Resource] = []
@State private var isLoading = false
@ -310,7 +310,7 @@ struct NetworkNodeDetailView: View {
}
private func loadNodeResources(id: Int) async {
guard let session = userContext.networkSession else {
guard let session = appContext.networkSession else {
return
}

View File

@ -8,7 +8,6 @@
import SwiftUI
struct RootView: View {
@Environment(UserContext.self) var userContext
@Environment(AppContext.self) var appContext: AppContext
@State private var updateManager = AppUpdateManager.shared

View File

@ -7,7 +7,7 @@
import SwiftUI
struct SettingsAccountView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
@Environment(\.openWindow) var openWindow
@Environment(\.openURL) var openURL
@ -18,7 +18,7 @@ struct SettingsAccountView: View {
sectionHeader(title: "账户安全", icon: "shield.lefthalf.filled")
VStack(spacing: 0) {
if let loginCredit = userContext.loginCredit {
if let loginCredit = appContext.loginCredit {
switch loginCredit {
case .token(let token):
TokenCreditView(token: token)
@ -115,7 +115,7 @@ extension SettingsAccountView {
}
struct AccountCreditView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
let username: String
var body: some View {
@ -128,7 +128,7 @@ extension SettingsAccountView {
Button("退出登录") {
Task { @MainActor in
try await userContext.logout()
try await appContext.logout()
}
}
.buttonStyle(.bordered)
@ -139,14 +139,14 @@ extension SettingsAccountView {
}
struct TokenCreditView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
let token: String
var body: some View {
AccountRow(icon: "key.horizontal.fill", title: "Token 登录", subtitle: token, actions: AnyView(
Button("退出登录") {
Task { @MainActor in
try await userContext.logout()
try await appContext.logout()
}
}
.buttonStyle(.bordered)

View File

@ -7,7 +7,7 @@
import SwiftUI
struct SettingsNetworkView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
@Environment(\.openURL) var openURL
@State private var selectedExitNode: SDLAPIClient.NetworkSession.ExitNode?
@ -19,7 +19,7 @@ struct SettingsNetworkView: View {
// MARK: -
sectionHeader(title: "网络配置", icon: "network")
if let networkSession = userContext.networkSession {
if let networkSession = appContext.networkSession {
VStack(spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 4) {
@ -113,7 +113,7 @@ struct SettingsNetworkView: View {
.frame(maxWidth: 600, alignment: .leading)
}
.onAppear {
self.selectedExitNode = self.userContext.networkSession?.exitNodes.first
self.selectedExitNode = self.appContext.networkSession?.exitNodes.first
}
}

View File

@ -8,7 +8,7 @@ import SwiftUI
// MARK: - ()
struct SettingsUserIssueView: View {
@Environment(UserContext.self) var userContext: UserContext
@Environment(AppContext.self) var appContext: AppContext
//
@State private var account: String = ""
@ -129,7 +129,7 @@ struct SettingsUserIssueView: View {
}
let params: [String: Any] = [
"access_token": self.userContext.networkSession?.accessToken ?? "",
"access_token": self.appContext.networkSession?.accessToken ?? "",
"contact": self.account,
"platform": SystemConfig.systemInfo,
"content": self.text,

View File

@ -1,64 +0,0 @@
//
// LoginState.swift
// punchnet
//
// Created by on 2026/1/16.
//
import Foundation
import Observation
@Observable
class UserContext {
var loginCredit: Credit?
var networkSession: SDLAPIClient.NetworkSession?
enum Credit {
case token(token: String)
case accountAndPasword(account: String, password: String)
}
func loginWith(credit: Credit) async throws -> Bool {
switch credit {
case .token(let token):
self.networkSession = try await SDLAPIClient.loginWithToken(token: token)
// keychain
if let data = token.data(using: .utf8) {
try KeychainStore.shared.save(data, account: "token")
}
case .accountAndPasword(let username, let password):
self.networkSession = try await SDLAPIClient.loginWithAccountAndPassword(username: username, password: password)
// keychain
if let data = "\(username):\(password)".data(using: .utf8) {
try KeychainStore.shared.save(data, account: "accountAndPasword")
}
}
self.loginCredit = credit
return true
}
func loadCacheToken() -> String? {
if let data = try? KeychainStore.shared.load(account: "token") {
return String(data: data, encoding: .utf8)
}
return nil
}
func loadCacheUsernameAndPassword() -> (String, String)? {
if let data = try? KeychainStore.shared.load(account: "accountAndPasword"),
let str = String(data: data, encoding: .utf8) {
let parts = str.split(separator: ":")
if parts.count == 2 {
return (String(parts[0]), String(parts[1]))
}
}
return nil
}
// 退
func logout() async throws {
try await VPNManager.shared.disableVpn()
}
}

View File

@ -32,7 +32,6 @@ struct punchnetApp: App {
private var noticeServer: UDPNoticeCenterServer
@State private var appContext: AppContext
@State private var userContext = UserContext()
@AppStorage("hasAcceptedPrivacy") var hasAcceptedPrivacy: Bool = false
// UI
@State private var showPrivacy: Bool = false
@ -48,7 +47,6 @@ struct punchnetApp: App {
RootView()
.navigationTitle("")
.environment(self.appContext)
.environment(self.userContext)
.onAppear {
self.showPrivacy = !hasAcceptedPrivacy
}
@ -72,7 +70,6 @@ struct punchnetApp: App {
Window("设置", id: "settings") {
SettingsView()
.environment(self.userContext)
.environment(self.appContext)
.frame(width: 750, height: 500)
}