import Libbox import Library import NetworkExtension import SwiftUI @MainActor class DashboardViewModel: ObservableObject { #if os(macOS) @Published var isLoading = true @Published var systemExtensionInstalled = true #endif @Published var alert: Alert? @Published var notStarted = false private var openURL: ((URL) -> Void)? func setOpenURL(_ openURL: @escaping (URL) -> Void) { self.openURL = openURL } #if os(macOS) nonisolated func reload() async { let systemExtensionInstalled = await SystemExtension.isInstalled() await MainActor.run { self.systemExtensionInstalled = systemExtensionInstalled self.isLoading = false } } #endif func handleStatusChange(_ status: NEVPNStatus, profile: ExtensionProfile) { if status == .connected { notStarted = false Task { await checkDeprecatedNotes() } } else if status == .connecting { notStarted = true } else if status == .disconnected { if #available(iOS 16.0, macOS 13.0, tvOS 17.0, *) { if notStarted { Task { await checkLastDisconnectError(profile: profile) } } } } } nonisolated func checkDeprecatedNotes() async { if await SharedPreferences.disableDeprecatedWarnings.get() { return } do { let reports = try LibboxNewStandaloneCommandClient()!.getDeprecatedNotes() if reports.hasNext() { await MainActor.run { loopShowDeprecateNotes(reports) } } } catch { await MainActor.run { alert = Alert(error) } } } private func loopShowDeprecateNotes(_ reports: any LibboxDeprecatedNoteIteratorProtocol) { if reports.hasNext() { let report = reports.next()! if report.migrationLink.isEmpty { alert = Alert( title: Text("Deprecated Warning"), message: Text(report.message()), dismissButton: .cancel(Text("Ok")) { Task.detached { [weak self] in try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) await self?.loopShowDeprecateNotes(reports) } } ) } else { alert = Alert( title: Text("Deprecated Warning"), message: Text(report.message()), primaryButton: .default(Text("Documentation")) { self.openURL?(URL(string: report.migrationLink)!) Task.detached { [weak self] in try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) await self?.loopShowDeprecateNotes(reports) } }, secondaryButton: .cancel(Text("Ok")) { Task.detached { [weak self] in try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) await self?.loopShowDeprecateNotes(reports) } } ) } } } @available(iOS 16.0, macOS 13.0, tvOS 17.0, *) nonisolated func checkLastDisconnectError(profile: ExtensionProfile) async { var myError: NSError do { try await profile.fetchLastDisconnectError() return } catch { myError = error as NSError } #if os(macOS) if myError.domain == "Library.FullDiskAccessPermissionRequired" { await MainActor.run { alert = Alert( title: Text("Full Disk Access permission is required"), message: Text("Please grant the permission for **SFMExtension**, then we can continue."), primaryButton: .default(Text("Authorize"), action: openFDASettings), secondaryButton: .cancel() ) } return } #endif let message = myError.localizedDescription await MainActor.run { alert = Alert(title: Text("Service Error"), message: Text(message)) } } #if os(macOS) private func openFDASettings() { if NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!) { return } if #available(macOS 13, *) { NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/System Settings.app")) } else { NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/System Preferences.app")) } } #endif }