Files
sing-box-for-apple/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift
T
2023-07-29 09:44:31 +08:00

172 lines
6.3 KiB
Swift

import Foundation
import Libbox
import Library
import SwiftUI
public struct ActiveDashboardView: View {
public static let NotificationUpdateSelectedProfile = Notification.Name("update-selected-profile")
@Environment(\.scenePhase) var scenePhase
@Environment(\.selection) private var selection
@EnvironmentObject private var profile: ExtensionProfile
@State private var isLoading = true
@State private var profileList: [Profile] = []
@State private var selectedProfileID: Int64!
@State private var reasserting = false
@State private var observer: Any?
@State private var alert: Alert?
public init() {}
public var body: some View {
viewBuilder {
if isLoading {
ProgressView().onAppear {
Task.detached {
await doReload()
}
}
} else {
if profileList.isEmpty {
Text("Empty profiles")
} else {
VStack {
#if os(iOS) || os(tvOS)
if ApplicationLibrary.inPreview || profile.status.isConnected {
ExtensionStatusView()
.listStyle(.automatic)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
}
FormView {
StartStopButton()
Section("Profile") {
Picker(selection: $selectedProfileID) {
ForEach(profileList, id: \.id) { profile in
Text(profile.name).tag(profile.id)
}
} label: {}
.pickerStyle(.inline)
}
}
#elseif os(macOS)
if ApplicationLibrary.inPreview || profile.status.isConnected {
ExtensionStatusView()
}
FormView {
Section("Profile") {
ForEach(profileList, id: \.id) { profile in
Picker(profile.name, selection: $selectedProfileID) {
Text("").tag(profile.id)
}
}
.pickerStyle(.radioGroup)
}
}
#endif
}
.onChange(of: selectedProfileID) { _ in
reasserting = true
Task.detached {
await switchProfile(selectedProfileID!)
}
}
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting))
}
}
}
.alertBinding($alert)
.onChange(of: profile.status, perform: { newValue in
if newValue == .disconnecting || newValue == .connected {
Task.detached {
if let serviceError = try? String(contentsOf: ExtensionProvider.errorFile) {
DispatchQueue.main.async {
alert = Alert(errorMessage: serviceError)
}
try? FileManager.default.removeItem(at: ExtensionProvider.errorFile)
}
}
}
})
#if os(iOS) || os(tvOS)
.onChange(of: scenePhase, perform: { newValue in
if newValue == .active {
Task.detached {
await doReload()
}
}
})
.onChange(of: selection.wrappedValue, perform: { newValue in
if newValue == .dashboard {
Task.detached {
await doReload()
}
}
})
#elseif os(macOS)
.onAppear {
if observer == nil {
observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in
Task.detached {
await doReload()
}
})
}
}
.onDisappear {
if let observer {
NotificationCenter.default.removeObserver(observer)
}
}
#endif
}
private func doReload() {
defer {
isLoading = false
}
if ApplicationLibrary.inPreview {
profileList = [
Profile(id: 0, name: "profile local", type: .local, path: ""),
Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)),
]
selectedProfileID = 0
} else {
do {
profileList = try ProfileManager.list()
} catch {
alert = Alert(error)
return
}
if profileList.isEmpty {
return
}
selectedProfileID = SharedPreferences.selectedProfileID
if profileList.filter({ profile in
profile.id == selectedProfileID
})
.isEmpty {
selectedProfileID = profileList[0].id!
SharedPreferences.selectedProfileID = selectedProfileID
}
}
}
private func switchProfile(_ newProfileID: Int64) {
SharedPreferences.selectedProfileID = newProfileID
NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil)
if profile.status.isConnected {
do {
try LibboxNewStandaloneCommandClient()!.serviceReload()
} catch {
alert = Alert(error)
}
}
reasserting = false
}
}