简化环境逻辑
This commit is contained in:
parent
1ffea953bf
commit
c8c37954ce
@ -15,6 +15,17 @@ class AppContext {
|
|||||||
// 调用 "/connect" 之后的网络信息
|
// 调用 "/connect" 之后的网络信息
|
||||||
var networkContext: NetworkContext?
|
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 {
|
enum AppScene: Equatable {
|
||||||
case login(username: String?)
|
case login(username: String?)
|
||||||
@ -23,11 +34,51 @@ class AppContext {
|
|||||||
case resetPassword
|
case resetPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前app所处的场景
|
|
||||||
var appScene: AppScene = .login(username: nil)
|
|
||||||
|
|
||||||
init(noticePort: Int) {
|
init(noticePort: Int) {
|
||||||
self.noticePort = noticePort
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import Observation
|
|||||||
|
|
||||||
// MARK: - 主容器视图
|
// MARK: - 主容器视图
|
||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
|
||||||
@State private var authMethod: AuthMethod = .account
|
@State private var authMethod: AuthMethod = .account
|
||||||
|
|
||||||
var username: String?
|
var username: String?
|
||||||
@ -87,7 +86,6 @@ struct LoginView: View {
|
|||||||
|
|
||||||
// MARK: - 账户登录组件
|
// MARK: - 账户登录组件
|
||||||
struct LoginAccountView: View {
|
struct LoginAccountView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
|
||||||
@Environment(AppContext.self) var appContext: AppContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
|
|
||||||
@State var username: String = ""
|
@State var username: String = ""
|
||||||
@ -158,7 +156,7 @@ struct LoginAccountView: View {
|
|||||||
Alert(title: Text("提示"), message: Text(self.errorMessage))
|
Alert(title: Text("提示"), message: Text(self.errorMessage))
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let (cacheUsername, cachePassword) = self.userContext.loadCacheUsernameAndPassword() {
|
if let (cacheUsername, cachePassword) = self.appContext.loadCacheUsernameAndPassword() {
|
||||||
self.username = cacheUsername
|
self.username = cacheUsername
|
||||||
self.password = cachePassword
|
self.password = cachePassword
|
||||||
}
|
}
|
||||||
@ -172,7 +170,7 @@ struct LoginAccountView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
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)) {
|
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
|
||||||
self.appContext.appScene = .logined
|
self.appContext.appScene = .logined
|
||||||
}
|
}
|
||||||
@ -188,7 +186,6 @@ struct LoginAccountView: View {
|
|||||||
|
|
||||||
// MARK: - 密钥登录组件
|
// MARK: - 密钥登录组件
|
||||||
struct LoginTokenView: View {
|
struct LoginTokenView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
|
||||||
@Environment(AppContext.self) var appContext: AppContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
|
|
||||||
@State private var token = ""
|
@State private var token = ""
|
||||||
@ -221,7 +218,7 @@ struct LoginTokenView: View {
|
|||||||
Alert(title: Text("提示"), message: Text(self.errorMessage))
|
Alert(title: Text("提示"), message: Text(self.errorMessage))
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let cacheToken = self.userContext.loadCacheToken() {
|
if let cacheToken = self.appContext.loadCacheToken() {
|
||||||
self.token = cacheToken
|
self.token = cacheToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,7 +231,7 @@ struct LoginTokenView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
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)) {
|
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
|
||||||
self.appContext.appScene = .logined
|
self.appContext.appScene = .logined
|
||||||
}
|
}
|
||||||
@ -312,5 +309,5 @@ struct VisualEffectView: NSViewRepresentable {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
LoginView()
|
LoginView()
|
||||||
.environment(UserContext())
|
.environment(AppContext(noticePort: 0))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ enum ConnectState {
|
|||||||
|
|
||||||
// MARK: - 主网络视图
|
// MARK: - 主网络视图
|
||||||
struct NetworkView: View {
|
struct NetworkView: View {
|
||||||
@Environment(UserContext.self) var userContext
|
|
||||||
@Environment(AppContext.self) var appContext
|
@Environment(AppContext.self) var appContext
|
||||||
@Environment(\.openWindow) private var openWindow
|
@Environment(\.openWindow) private var openWindow
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ extension NetworkView {
|
|||||||
statusIndicator
|
statusIndicator
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(userContext.networkSession?.networkName ?? "未连接网络")
|
Text(appContext.networkSession?.networkName ?? "未连接网络")
|
||||||
.font(.system(size: 14, weight: .semibold))
|
.font(.system(size: 14, weight: .semibold))
|
||||||
|
|
||||||
if connectState == .connected {
|
if connectState == .connected {
|
||||||
@ -186,7 +185,7 @@ extension NetworkView {
|
|||||||
isConnecting = true
|
isConnecting = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
guard let session = userContext.networkSession else {
|
guard let session = appContext.networkSession else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +268,8 @@ struct NetworkNodeHeadView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct NetworkNodeDetailView: View {
|
struct NetworkNodeDetailView: View {
|
||||||
@Environment(UserContext.self) var userContext
|
@Environment(AppContext.self) private var appContext: AppContext
|
||||||
|
|
||||||
var node: Node
|
var node: Node
|
||||||
@State private var resources: [Resource] = []
|
@State private var resources: [Resource] = []
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@ -310,7 +310,7 @@ struct NetworkNodeDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadNodeResources(id: Int) async {
|
private func loadNodeResources(id: Int) async {
|
||||||
guard let session = userContext.networkSession else {
|
guard let session = appContext.networkSession else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RootView: View {
|
struct RootView: View {
|
||||||
@Environment(UserContext.self) var userContext
|
|
||||||
@Environment(AppContext.self) var appContext: AppContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
|
|
||||||
@State private var updateManager = AppUpdateManager.shared
|
@State private var updateManager = AppUpdateManager.shared
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsAccountView: View {
|
struct SettingsAccountView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
@Environment(\.openWindow) var openWindow
|
@Environment(\.openWindow) var openWindow
|
||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ struct SettingsAccountView: View {
|
|||||||
sectionHeader(title: "账户安全", icon: "shield.lefthalf.filled")
|
sectionHeader(title: "账户安全", icon: "shield.lefthalf.filled")
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if let loginCredit = userContext.loginCredit {
|
if let loginCredit = appContext.loginCredit {
|
||||||
switch loginCredit {
|
switch loginCredit {
|
||||||
case .token(let token):
|
case .token(let token):
|
||||||
TokenCreditView(token: token)
|
TokenCreditView(token: token)
|
||||||
@ -115,7 +115,7 @@ extension SettingsAccountView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct AccountCreditView: View {
|
struct AccountCreditView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
let username: String
|
let username: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -128,7 +128,7 @@ extension SettingsAccountView {
|
|||||||
|
|
||||||
Button("退出登录") {
|
Button("退出登录") {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
try await userContext.logout()
|
try await appContext.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
@ -139,14 +139,14 @@ extension SettingsAccountView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TokenCreditView: View {
|
struct TokenCreditView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
let token: String
|
let token: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
AccountRow(icon: "key.horizontal.fill", title: "Token 登录", subtitle: token, actions: AnyView(
|
AccountRow(icon: "key.horizontal.fill", title: "Token 登录", subtitle: token, actions: AnyView(
|
||||||
Button("退出登录") {
|
Button("退出登录") {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
try await userContext.logout()
|
try await appContext.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsNetworkView: View {
|
struct SettingsNetworkView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
|
|
||||||
@State private var selectedExitNode: SDLAPIClient.NetworkSession.ExitNode?
|
@State private var selectedExitNode: SDLAPIClient.NetworkSession.ExitNode?
|
||||||
@ -19,7 +19,7 @@ struct SettingsNetworkView: View {
|
|||||||
// MARK: - 网络部分
|
// MARK: - 网络部分
|
||||||
sectionHeader(title: "网络配置", icon: "network")
|
sectionHeader(title: "网络配置", icon: "network")
|
||||||
|
|
||||||
if let networkSession = userContext.networkSession {
|
if let networkSession = appContext.networkSession {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
@ -113,7 +113,7 @@ struct SettingsNetworkView: View {
|
|||||||
.frame(maxWidth: 600, alignment: .leading)
|
.frame(maxWidth: 600, alignment: .leading)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.selectedExitNode = self.userContext.networkSession?.exitNodes.first
|
self.selectedExitNode = self.appContext.networkSession?.exitNodes.first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import SwiftUI
|
|||||||
|
|
||||||
// MARK: - 用户反馈页面 (完整逻辑版)
|
// MARK: - 用户反馈页面 (完整逻辑版)
|
||||||
struct SettingsUserIssueView: View {
|
struct SettingsUserIssueView: View {
|
||||||
@Environment(UserContext.self) var userContext: UserContext
|
@Environment(AppContext.self) var appContext: AppContext
|
||||||
|
|
||||||
// 表单状态
|
// 表单状态
|
||||||
@State private var account: String = ""
|
@State private var account: String = ""
|
||||||
@ -129,7 +129,7 @@ struct SettingsUserIssueView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let params: [String: Any] = [
|
let params: [String: Any] = [
|
||||||
"access_token": self.userContext.networkSession?.accessToken ?? "",
|
"access_token": self.appContext.networkSession?.accessToken ?? "",
|
||||||
"contact": self.account,
|
"contact": self.account,
|
||||||
"platform": SystemConfig.systemInfo,
|
"platform": SystemConfig.systemInfo,
|
||||||
"content": self.text,
|
"content": self.text,
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -32,7 +32,6 @@ struct punchnetApp: App {
|
|||||||
|
|
||||||
private var noticeServer: UDPNoticeCenterServer
|
private var noticeServer: UDPNoticeCenterServer
|
||||||
@State private var appContext: AppContext
|
@State private var appContext: AppContext
|
||||||
@State private var userContext = UserContext()
|
|
||||||
@AppStorage("hasAcceptedPrivacy") var hasAcceptedPrivacy: Bool = false
|
@AppStorage("hasAcceptedPrivacy") var hasAcceptedPrivacy: Bool = false
|
||||||
// UI 控制状态:是否显示弹窗
|
// UI 控制状态:是否显示弹窗
|
||||||
@State private var showPrivacy: Bool = false
|
@State private var showPrivacy: Bool = false
|
||||||
@ -48,7 +47,6 @@ struct punchnetApp: App {
|
|||||||
RootView()
|
RootView()
|
||||||
.navigationTitle("")
|
.navigationTitle("")
|
||||||
.environment(self.appContext)
|
.environment(self.appContext)
|
||||||
.environment(self.userContext)
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.showPrivacy = !hasAcceptedPrivacy
|
self.showPrivacy = !hasAcceptedPrivacy
|
||||||
}
|
}
|
||||||
@ -72,7 +70,6 @@ struct punchnetApp: App {
|
|||||||
|
|
||||||
Window("设置", id: "settings") {
|
Window("设置", id: "settings") {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.environment(self.userContext)
|
|
||||||
.environment(self.appContext)
|
.environment(self.appContext)
|
||||||
.frame(width: 750, height: 500)
|
.frame(width: 750, height: 500)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user