From 1f6e99f576a3f44c405edb63ea9facfb7a446d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 7 Feb 2024 13:50:18 +0800 Subject: [PATCH] Improve UI --- .../Views/Abstract/FormItem.swift | 85 ++++ .../Views/Abstract/Formtem.swift | 39 -- .../Views/Abstract/RequestReviewButton.swift | 49 +++ .../Views/Abstract/ShareButton.swift | 4 +- .../Dashboard/InstallProfileButton.swift | 4 +- .../InstallSystemExtensionButton.swift | 4 +- ApplicationLibrary/Views/NavigationPage.swift | 9 +- .../Profile/EditProfileContentView.swift | 4 - .../Views/Profile/EditProfileView.swift | 90 ++--- .../Views/Profile/EditProfileWindowView.swift | 58 --- .../Views/Profile/NewProfileView.swift | 8 +- .../Views/Profile/ProfileView.swift | 193 +++++---- .../Views/Setting/CoreView.swift | 95 +++++ .../Views/Setting/MacAppView.swift | 155 +++++++ .../Views/Setting/PacketTunnelView.swift | 165 ++++++++ .../Views/Setting/ProfileOverrideView.swift | 65 +++ .../Views/Setting/ServiceLogView.swift | 74 ++-- .../Views/Setting/SettingView.swift | 377 ++++++------------ .../Views/Setting/SponsorView.swift | 86 ++++ Library/Database/Profile+Transferable.swift | 10 + .../ShadredPreferences+Database.swift | 2 +- Library/Database/SharedPreferences.swift | 55 ++- Library/Network/ExtensionEnvironments.swift | 2 + .../Network/ExtensionPlatformInterface.swift | 27 ++ Library/Network/ExtensionProvider.swift | 2 +- Library/Network/SystemExtension.swift | 4 +- Library/Shared/Color+Extension.swift | 25 ++ MacLibrary/ApplicationDelegate.swift | 4 +- MacLibrary/MacApplication.swift | 25 +- MacLibrary/MainView.swift | 9 +- MacLibrary/SidebarView.swift | 32 +- sing-box.xcodeproj/project.pbxproj | 86 ++-- 32 files changed, 1201 insertions(+), 646 deletions(-) create mode 100644 ApplicationLibrary/Views/Abstract/FormItem.swift delete mode 100644 ApplicationLibrary/Views/Abstract/Formtem.swift create mode 100644 ApplicationLibrary/Views/Abstract/RequestReviewButton.swift delete mode 100644 ApplicationLibrary/Views/Profile/EditProfileWindowView.swift create mode 100644 ApplicationLibrary/Views/Setting/CoreView.swift create mode 100644 ApplicationLibrary/Views/Setting/MacAppView.swift create mode 100644 ApplicationLibrary/Views/Setting/PacketTunnelView.swift create mode 100644 ApplicationLibrary/Views/Setting/ProfileOverrideView.swift create mode 100644 ApplicationLibrary/Views/Setting/SponsorView.swift create mode 100644 Library/Shared/Color+Extension.swift diff --git a/ApplicationLibrary/Views/Abstract/FormItem.swift b/ApplicationLibrary/Views/Abstract/FormItem.swift new file mode 100644 index 0000000..e6f76a0 --- /dev/null +++ b/ApplicationLibrary/Views/Abstract/FormItem.swift @@ -0,0 +1,85 @@ +import Foundation +import SwiftUI + +public func FormView(@ViewBuilder content: () -> some View) -> some View { + Form { + content() + } + #if os(macOS) + .formStyle(.grouped) + #endif +} + +public func FormTextItem(_ name: LocalizedStringKey, _ value: String) -> some View { + HStack { + Text(name) + Spacer() + Text(value) + .multilineTextAlignment(.trailing) + .font(Font.system(.caption, design: .monospaced)) + #if os(iOS) || os(macOS) + .textSelection(.enabled) + #endif + } +} + +public func FormTextItem(_ name: LocalizedStringKey, _ systemImage: String, @ViewBuilder _ value: () -> some View) -> some View { + HStack { + Label(name, systemImage: systemImage) + Spacer() + value() + .multilineTextAlignment(.trailing) + .font(Font.system(.caption, design: .monospaced)) + #if os(iOS) || os(macOS) + .textSelection(.enabled) + #endif + } +} + +public func FormItem(_ title: String, @ViewBuilder content: () -> some View) -> some View { + #if os(iOS) || os(tvOS) + HStack { + Text(title) + .lineLimit(1) + .layoutPriority(1) + Spacer() + Spacer() + content() + } + #elseif os(macOS) + content() + #endif +} + +public func FormSection(@ViewBuilder content: () -> some View, @ViewBuilder footer: () -> some View) -> some View { + Section { + content() + } footer: { + footer() + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +public func FormButton(action: @escaping () -> Void, @ViewBuilder label: () -> some View) -> some View { + Button(action: action, label: label) + #if os(macOS) + .buttonStyle(.plain) + .foregroundColor(.accentColor) + #endif +} + +public func FormButton(_ titleKey: some StringProtocol, action: @escaping () -> Void) -> some View { + Button(titleKey, action: action) + #if os(macOS) + .buttonStyle(.plain) + .foregroundColor(.accentColor) + #endif +} + +public func FormButton(role: ButtonRole?, action: @escaping () -> Void, @ViewBuilder label: () -> some View) -> some View { + Button(role: role, action: action, label: label) + #if os(macOS) + .buttonStyle(.plain) + .foregroundColor(.accentColor) + #endif +} diff --git a/ApplicationLibrary/Views/Abstract/Formtem.swift b/ApplicationLibrary/Views/Abstract/Formtem.swift deleted file mode 100644 index 00a34bb..0000000 --- a/ApplicationLibrary/Views/Abstract/Formtem.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation -import SwiftUI - -public func FormView(@ViewBuilder content: () -> some View) -> some View { - Form { - content() - } - #if os(macOS) - .formStyle(.grouped) - #endif -} - -public func FormTextItem(_ name: String, _ value: String) -> some View { - HStack { - Text(name) - Spacer() - Text(value) - .multilineTextAlignment(.trailing) - .font(Font.system(.caption, design: .monospaced)) - #if os(iOS) || os(macOS) - .textSelection(.enabled) - #endif - } -} - -public func FormItem(_ title: String, @ViewBuilder content: () -> some View) -> some View { - #if os(iOS) || os(tvOS) - HStack { - Text(title) - .lineLimit(1) - .layoutPriority(1) - Spacer() - Spacer() - content() - } - #elseif os(macOS) - content() - #endif -} diff --git a/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift b/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift new file mode 100644 index 0000000..a7459cd --- /dev/null +++ b/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift @@ -0,0 +1,49 @@ +#if !os(tvOS) + + import StoreKit + import SwiftUI + + public func RequestReviewButton(label: @escaping () -> some View) -> some View { + viewBuilder { + if #available(iOS 16.0, macOS 13.0, visionOS 1.0, *) { + RequestReviewButton0(label: label) + } else { + #if os(iOS) + RequestReviewButton1(label: label) + #else + EmptyView() + #endif + } + } + } + + @available(iOS 16.0, macOS 13.0, visionOS 1.0, *) + struct RequestReviewButton0: View { + @Environment(\.requestReview) private var requestReview + + private let label: () -> Label + init(label: @escaping () -> Label) { + self.label = label + } + + var body: some View { + FormButton(action: { + requestReview() + }, label: label) + } + } + + struct RequestReviewButton1: View { + private let label: () -> Label + init(label: @escaping () -> Label) { + self.label = label + } + + var body: some View { + Button(action: { + SKStoreReviewController.requestReview() + }, label: label) + } + } + +#endif diff --git a/ApplicationLibrary/Views/Abstract/ShareButton.swift b/ApplicationLibrary/Views/Abstract/ShareButton.swift index 6a2ce35..b78eef0 100644 --- a/ApplicationLibrary/Views/Abstract/ShareButton.swift +++ b/ApplicationLibrary/Views/Abstract/ShareButton.swift @@ -1,9 +1,9 @@ import Foundation import Library import SwiftUI -#if os(iOS) +#if canImport(UIKit) import UIKit -#elseif os(macOS) +#elseif canImport(AppKit) import AppKit #endif diff --git a/ApplicationLibrary/Views/Dashboard/InstallProfileButton.swift b/ApplicationLibrary/Views/Dashboard/InstallProfileButton.swift index d791e0e..3aa7398 100644 --- a/ApplicationLibrary/Views/Dashboard/InstallProfileButton.swift +++ b/ApplicationLibrary/Views/Dashboard/InstallProfileButton.swift @@ -11,10 +11,12 @@ public struct InstallProfileButton: View { } public var body: some View { - Button("Install NetworkExtension") { + FormButton { Task { await installProfile() } + } label: { + Label("Install Network Extension", systemImage: "lock.doc.fill") } .alertBinding($alert) } diff --git a/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift b/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift index 6b2eb26..7e6c0b7 100644 --- a/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift +++ b/ApplicationLibrary/Views/Dashboard/InstallSystemExtensionButton.swift @@ -12,10 +12,12 @@ } public var body: some View { - Button("Install SystemExtension") { + FormButton { Task { await installSystemExtension() } + } label: { + Label("Install System Extension", systemImage: "lock.doc.fill") } .alertBinding($alert) } diff --git a/ApplicationLibrary/Views/NavigationPage.swift b/ApplicationLibrary/Views/NavigationPage.swift index 85730af..a0cec65 100644 --- a/ApplicationLibrary/Views/NavigationPage.swift +++ b/ApplicationLibrary/Views/NavigationPage.swift @@ -17,12 +17,15 @@ public enum NavigationPage: Int, CaseIterable, Identifiable { } public extension NavigationPage { - static var macosDefaultPages: [NavigationPage] { - [.logs, .profiles, .settings] - } + #if os(macOS) + static var macosDefaultPages: [NavigationPage] { + [.logs, .profiles, .settings] + } + #endif var label: some View { Label(title, systemImage: iconImage) + .tint(.textColor) } var title: String { diff --git a/ApplicationLibrary/Views/Profile/EditProfileContentView.swift b/ApplicationLibrary/Views/Profile/EditProfileContentView.swift index 042c214..1ae3c0b 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileContentView.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileContentView.swift @@ -5,10 +5,6 @@ @MainActor public struct EditProfileContentView: View { - #if os(macOS) - public static let windowID = "edit-profile-content" - #endif - public struct Context: Codable, Hashable { public let profileID: Int64 public let readOnly: Bool diff --git a/ApplicationLibrary/Views/Profile/EditProfileView.swift b/ApplicationLibrary/Views/Profile/EditProfileView.swift index d9f9ae3..1ec7960 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileView.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileView.swift @@ -4,10 +4,6 @@ import SwiftUI @MainActor public struct EditProfileView: View { - #if os(macOS) - @Environment(\.openWindow) private var openWindow - #endif - @EnvironmentObject private var environments: ExtensionEnvironments @Environment(\.dismiss) private var dismiss @EnvironmentObject private var profile: Profile @@ -58,39 +54,45 @@ public struct EditProfileView: View { FormTextItem("Last Updated", profile.lastUpdatedString) } } - #if os(iOS) || os(tvOS) - Section("Action") { - if profile.type != .remote { - #if os(iOS) - NavigationLink { - EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false)) - } label: { - Text("Edit Content").foregroundColor(.accentColor) - } - #endif - } else { - #if os(iOS) - NavigationLink { - EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true)) - } label: { - Text("View Content").foregroundColor(.accentColor) - } - #endif - Button("Update") { - isLoading = true - Task { - await updateProfile() - } + Section("Action") { + if profile.type != .remote { + #if os(iOS) || os(macOS) + NavigationLink { + EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false)) + } label: { + Label("Edit Content", systemImage: "pencil") + .foregroundColor(.accentColor) } - .disabled(isLoading) - } - Button("Delete", role: .destructive) { + #endif + } else { + #if os(iOS) || os(macOS) + NavigationLink { + EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true)) + } label: { + Label("View Content", systemImage: "doc.fill") + .foregroundColor(.accentColor) + } + #endif + FormButton { + isLoading = true Task { - await deleteProfile() + await updateProfile() } + } label: { + Label("Update", systemImage: "arrow.clockwise") } + .foregroundColor(.accentColor) + .disabled(isLoading) } - #endif + FormButton(role: .destructive) { + Task { + await deleteProfile() + } + } label: { + Label("Delete", systemImage: "trash.fill") + } + .foregroundColor(.red) + } } .onChangeCompat(of: profile.name) { isChanged = true @@ -114,30 +116,6 @@ public struct EditProfileView: View { Image("save", bundle: ApplicationLibrary.bundle, label: Text("Save")) } .disabled(isLoading || !isChanged) - if profile.type != .remote { - Button { - openWindow(id: EditProfileContentView.windowID, value: EditProfileContentView.Context(profileID: profile.id!, readOnly: false)) - } label: { - Label("Edit Content", systemImage: "pencil") - } - .disabled(isLoading) - } else { - Button { - isLoading = true - Task { - await updateProfile() - } - } label: { - Label("Update", systemImage: "arrow.clockwise") - } - .disabled(isLoading) - Button { - openWindow(id: EditProfileContentView.windowID, value: EditProfileContentView.Context(profileID: profile.id!, readOnly: true)) - } label: { - Label("View Content", systemImage: "doc.text.fill") - } - .disabled(isLoading) - } } } #elseif os(iOS) diff --git a/ApplicationLibrary/Views/Profile/EditProfileWindowView.swift b/ApplicationLibrary/Views/Profile/EditProfileWindowView.swift deleted file mode 100644 index 3a23512..0000000 --- a/ApplicationLibrary/Views/Profile/EditProfileWindowView.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Library -import SwiftUI - -#if os(macOS) - @MainActor - public struct EditProfileWindowView: View { - public static let windowID = "edit-profile" - - private var profileID: Int64? - - public init(_ profileID: Int64?) { - self.profileID = profileID - } - - @Environment(\.dismiss) private var dismiss - - @State private var isLoading = true - @State private var profile: Profile! - @State private var alert: Alert? - - public var body: some View { - viewBuilder { - if isLoading { - ProgressView().onAppear { - Task { - await doReload() - } - } - } else { - EditProfileView().environmentObject(profile!) - } - } - .alertBinding($alert) - .onExitCommand { - dismiss() - } - } - - private func doReload() async { - guard let profileID else { - alert = Alert(errorMessage: "Context destroyed") - return - } - do { - profile = try await ProfileManager.get(profileID) - } catch { - alert = Alert(error) - return - } - if profile == nil { - alert = Alert(errorMessage: "Profile deleted") - return - } - isLoading = false - } - } - -#endif diff --git a/ApplicationLibrary/Views/Profile/NewProfileView.swift b/ApplicationLibrary/Views/Profile/NewProfileView.swift index f27051f..91cd5ea 100644 --- a/ApplicationLibrary/Views/Profile/NewProfileView.swift +++ b/ApplicationLibrary/Views/Profile/NewProfileView.swift @@ -5,10 +5,6 @@ import SwiftUI @MainActor public struct NewProfileView: View { - #if os(macOS) - public static let windowID = "new-profile" - #endif - @EnvironmentObject private var environments: ExtensionEnvironments @Environment(\.dismiss) private var dismiss @@ -100,11 +96,13 @@ public struct NewProfileView: View { } Section { if !isSaving { - Button("Create") { + FormButton { isSaving = true Task { await createProfile() } + } label: { + Label("Create", systemImage: "doc.fill.badge.plus") } } else { ProgressView() diff --git a/ApplicationLibrary/Views/Profile/ProfileView.swift b/ApplicationLibrary/Views/Profile/ProfileView.swift index ab70c44..ab9a38f 100644 --- a/ApplicationLibrary/Views/Profile/ProfileView.swift +++ b/ApplicationLibrary/Views/Profile/ProfileView.swift @@ -21,8 +21,6 @@ public struct ProfileView: View { #if os(iOS) || os(tvOS) @State private var editMode = EditMode.inactive - #elseif os(macOS) - @Environment(\.openWindow) private var openWindow #endif #if os(tvOS) @@ -39,73 +37,67 @@ public struct ProfileView: View { } } } else { - #if os(iOS) || os(tvOS) - ZStack { - if let importRemoteProfileRequest { - NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) { - NewProfileView(importRemoteProfileRequest) - } + ZStack { + if let importRemoteProfileRequest { + NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) { + NewProfileView(importRemoteProfileRequest) } - FormView { - #if os(iOS) + } + FormView { + #if os(iOS) + NavigationLink { + NewProfileView() + } label: { + Text("New Profile").foregroundColor(.accentColor) + } + .disabled(editMode.isEditing) + #elseif os(macOS) + NavigationLink { + NewProfileView() + } label: { + Text("New Profile") + } + #elseif os(tvOS) + Section { NavigationLink { NewProfileView() } label: { Text("New Profile").foregroundColor(.accentColor) } - .disabled(editMode.isEditing) - #elseif os(tvOS) - Section { + if ApplicationLibrary.inPreview || devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) { NavigationLink { - NewProfileView() - } label: { - Text("New Profile").foregroundColor(.accentColor) - } - if ApplicationLibrary.inPreview || devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) { - NavigationLink { - ImportProfileView { - await doReload() - } - } label: { - Text("Import Profile").foregroundColor(.accentColor) + ImportProfileView { + await doReload() } + } label: { + Text("Import Profile").foregroundColor(.accentColor) } } - #endif - if profileList.isEmpty { - Text("Empty profiles") - } else { - List { - ForEach(profileList, id: \.id) { profile in - viewBuilder { + } + #endif + if profileList.isEmpty { + Text("Empty profiles") + } else { + List { + ForEach(profileList, id: \.id) { profile in + viewBuilder { + #if os(iOS) || os(tvOS) if editMode.isEditing == true { Text(profile.name) } else { ProfileItem(self, profile) } - } + #else + ProfileItem(self, profile) + #endif } - .onMove(perform: moveProfile) - .onDelete(perform: deleteProfile) - } - } - } - } - #elseif os(macOS) - if profileList.isEmpty { - Text("Empty profiles") - } else { - FormView { - List { - ForEach(profileList, id: \.id) { profile in - ProfileItem(self, profile) } .onMove(perform: moveProfile) .onDelete(perform: deleteProfile) } } } - #endif + } } } .disabled(isUpdating) @@ -140,17 +132,7 @@ public struct ProfileView: View { // await doReload() // } } - #if os(macOS) - .toolbar { - ToolbarItem { - Button { - openWindow(id: NewProfileView.windowID) - } label: { - Label("New Profile", systemImage: "plus.square.fill") - } - } - } - #elseif os(iOS) + #if os(iOS) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton().disabled(profileList.isEmpty) @@ -202,11 +184,7 @@ public struct ProfileView: View { title: Text("Import Remote Profile"), message: Text("Are you sure to import remote profile \(newValue.name)? You will connect to \(newValue.host) to download the configuration."), primaryButton: .default(Text("Import")) { - #if os(iOS) || os(tvOS) - importRemoteProfilePresented = true - #elseif os(macOS) - openWindow(id: NewProfileView.windowID, value: importRemoteProfileRequest!) - #endif + importRemoteProfilePresented = true }, secondaryButton: .cancel() ) @@ -285,6 +263,7 @@ public struct ProfileView: View { } } + @MainActor public struct ProfileItem: View { private let parent: ProfileView @State private var profile: ProfilePreview @@ -307,7 +286,6 @@ public struct ProfileView: View { #endif } - @MainActor private var body0: some View { viewBuilder { #if !os(macOS) @@ -346,57 +324,63 @@ public struct ProfileView: View { } } label: { Label("Delete", systemImage: "trash.fill") + .foregroundColor(.red) } } #else - HStack { - VStack(alignment: .leading) { - Text(profile.name) - if profile.type == .remote { - Spacer(minLength: 4) - Text("Last Updated: \(profile.origin.lastUpdatedString)").font(.caption) - } - } + NavigationLink { + EditProfileView().environmentObject(profile.origin) + } label: { HStack { - if profile.type == .remote { + VStack(alignment: .leading) { + Text(profile.name) + if profile.type == .remote { + Spacer(minLength: 4) + Text("Last Updated: \(profile.origin.lastUpdatedString)").font(.caption) + } + } + HStack { + if profile.type == .remote { + Button { + parent.isUpdating = true + Task { + await parent.updateProfile(profile.origin) + profile = ProfilePreview(profile.origin) + } + } label: { + Image(systemName: "arrow.clockwise") + } + .padding(.leading, 4) + + Button { + shareLinkPresented = true + } label: { + Image(systemName: "qrcode") + } + .padding(.leading, 4) + .popover(isPresented: $shareLinkPresented, arrowEdge: .bottom) { + shareLinkView + } + } + ProfileShareButton(parent.$alert, profile.origin) { + Image(systemName: "square.and.arrow.up.fill") + } + .padding(.leading, 4) Button { - parent.isUpdating = true Task { - await parent.updateProfile(profile.origin) - profile = ProfilePreview(profile.origin) + await parent.deleteProfile(profile.origin) } } label: { - Image(systemName: "arrow.clockwise") - } - Button { - shareLinkPresented = true - } label: { - Image(systemName: "qrcode") - } - .popover(isPresented: $shareLinkPresented, arrowEdge: .bottom) { - shareLinkView + Image(systemName: "trash.fill") } + .padding([.leading, .trailing], 4) } - ProfileShareButton(parent.$alert, profile.origin) { - Image(systemName: "square.and.arrow.up.fill") - } - Button { - parent.openWindow(id: EditProfileWindowView.windowID, value: profile.id) - } label: { - Image(systemName: "pencil") - } - Button { - Task { - await parent.deleteProfile(profile.origin) - } - } label: { - Image(systemName: "trash.fill") - } + .buttonStyle(.plain) + .frame(maxWidth: .infinity, alignment: .trailing) } - .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) } - .padding(.vertical, 8) - .frame(maxWidth: .infinity, alignment: .leading) #endif } } @@ -412,6 +396,9 @@ public struct ProfileView: View { shareLinkView0 } } + #elseif os(macOS) + shareLinkView0 + .frame(minWidth: 300, minHeight: 300) #else shareLinkView0 #endif diff --git a/ApplicationLibrary/Views/Setting/CoreView.swift b/ApplicationLibrary/Views/Setting/CoreView.swift new file mode 100644 index 0000000..d355d71 --- /dev/null +++ b/ApplicationLibrary/Views/Setting/CoreView.swift @@ -0,0 +1,95 @@ + +import Libbox +import Library +import SwiftUI + +public struct CoreView: View { + @State private var isLoading = true + + @State private var version = "" + @State private var dataSize = "" + + public init() {} + public var body: some View { + viewBuilder { + if isLoading { + ProgressView().onAppear { + Task { + await loadSettings() + } + } + } else { + FormView { + FormTextItem("Version", version) + FormTextItem("Data Size", dataSize) + + Section("Working Directory") { + #if os(macOS) + FormButton { + NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: FilePath.workingDirectory.relativePath) + } label: { + Label("Open", systemImage: "macwindow.and.cursorarrow") + } + #endif + FormButton { + Task { + await destroyWorkingDirectory() + } + } label: { + Label("Destroy", systemImage: "trash.fill") + } + .foregroundColor(.red) + } + } + } + } + .navigationTitle("Core") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + + private nonisolated func loadSettings() async { + if ApplicationLibrary.inPreview { + version = "" + dataSize = LibboxFormatBytes(1000 * 1000 * 10) + isLoading = false + } else { + version = LibboxVersion() + dataSize = "Loading..." + isLoading = false + await loadSettingsBackground() + } + } + + private nonisolated func loadSettingsBackground() async { + let dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown" + await MainActor.run { + self.dataSize = dataSize + } + } + + private nonisolated func destroyWorkingDirectory() async { + try? FileManager.default.removeItem(at: FilePath.workingDirectory) + await MainActor.run { + isLoading = true + } + } +} + +private extension URL { + func formattedSize() throws -> String? { + guard let urls = FileManager.default.enumerator(at: self, includingPropertiesForKeys: nil)?.allObjects as? [URL] else { + return nil + } + let size = try urls.lazy.reduce(0) { + try ($1.resourceValues(forKeys: [.totalFileAllocatedSizeKey]).totalFileAllocatedSize ?? 0) + $0 + } + let formatter = ByteCountFormatter() + formatter.countStyle = .file + guard let byteCount = formatter.string(for: size) else { + return nil + } + return byteCount + } +} diff --git a/ApplicationLibrary/Views/Setting/MacAppView.swift b/ApplicationLibrary/Views/Setting/MacAppView.swift new file mode 100644 index 0000000..c756464 --- /dev/null +++ b/ApplicationLibrary/Views/Setting/MacAppView.swift @@ -0,0 +1,155 @@ +#if os(macOS) + + import AppKit + import Library + import ServiceManagement + import SwiftUI + + public struct MacAppView: View { + @State private var isLoading = true + + @State private var startAtLogin = false + @Environment(\.showMenuBarExtra) private var showMenuBarExtra + @State private var menuBarExtraInBackground = false + + @State private var alert: Alert? + + public init() {} + public var body: some View { + viewBuilder { + if isLoading { + ProgressView().onAppear { + Task { + await loadSettings() + } + } + } else { + FormView { + FormSection { + Toggle("Start At Login", isOn: $startAtLogin) + .onChangeCompat(of: startAtLogin) { newValue in + Task { + updateLoginItems(newValue) + } + } + } footer: { + Text("Launch the application when the system is logged in. If enabled at the same time as `Show in Menu Bar` and `Keep Menu Bar in Background`, the application interface will not be opened automatically.") + } + + Toggle("Show in Menu Bar", isOn: showMenuBarExtra) + .onChangeCompat(of: showMenuBarExtra.wrappedValue) { newValue in + Task { + await SharedPreferences.showMenuBarExtra.set(newValue) + if !newValue { + menuBarExtraInBackground = false + } + } + } + + if showMenuBarExtra.wrappedValue { + Toggle("Keep Menu Bar in Background", isOn: $menuBarExtraInBackground) + .onChangeCompat(of: menuBarExtraInBackground) { newValue in + Task { + await SharedPreferences.menuBarExtraInBackground.set(newValue) + } + } + } + + if Variant.useSystemExtension { + Section("System Extension") { + FormButton { + Task { + await updateSystemExtension() + } + } label: { + Label("Update", systemImage: "arrow.down.doc.fill") + } + FormButton { + Task { + await uninstallSystemExtension() + } + } label: { + Label("Uninstall", systemImage: "trash.fill").foregroundColor(.red) + } + } + } + } + } + } + .alertBinding($alert) + .navigationTitle("App") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + + private func loadSettings() async { + startAtLogin = SMAppService.mainApp.status == .enabled + menuBarExtraInBackground = await SharedPreferences.menuBarExtraInBackground.get() + isLoading = false + } + + private func updateLoginItems(_ startAtLogin: Bool) { + do { + if startAtLogin { + if SMAppService.mainApp.status == .enabled { + try? SMAppService.mainApp.unregister() + } + + try SMAppService.mainApp.register() + } else { + try SMAppService.mainApp.unregister() + } + } catch { + alert = Alert(error) + } + } + + private func updateSystemExtension() async { + do { + if let result = try await SystemExtension.install(forceUpdate: true) { + switch result { + case .completed: + alert = Alert( + title: Text("Update"), + message: Text("System Extension updated."), + dismissButton: .default(Text("Ok")) {} + ) + case .willCompleteAfterReboot: + alert = Alert( + title: Text("Update"), + message: Text("Reboot required."), + dismissButton: .default(Text("Ok")) {} + ) + } + } + } catch { + alert = Alert(error) + } + } + + private func uninstallSystemExtension() async { + do { + if let result = try await SystemExtension.uninstall() { + switch result { + case .completed: + alert = Alert( + title: Text("Uninstall"), + message: Text("System Extension removed."), + dismissButton: .default(Text("Ok")) {} + ) + case .willCompleteAfterReboot: + alert = Alert( + title: Text("Uninstall"), + message: Text("Reboot required."), + dismissButton: .default(Text("Ok")) {} + ) + } + } + } catch { + alert = Alert(error) + } + } + } + +#endif diff --git a/ApplicationLibrary/Views/Setting/PacketTunnelView.swift b/ApplicationLibrary/Views/Setting/PacketTunnelView.swift new file mode 100644 index 0000000..5ee52c3 --- /dev/null +++ b/ApplicationLibrary/Views/Setting/PacketTunnelView.swift @@ -0,0 +1,165 @@ +import Library +import SwiftUI + +struct PacketTunnelView: View { + #if os(macOS) + public static let windowID = "packet-tunnel" + #endif + + @State private var isLoading = true + + @State private var ignoreMemoryLimit = false + @State private var ignoreDeviceSleep = false + + @State private var includeAllNetworks = false + @State private var excludeAPNs = false + @State private var excludeCellularServices = false + @State private var excludeLocalNetworks = false + @State private var enforceRoutes = false + + public init() {} + public var body: some View { + viewBuilder { + if isLoading { + ProgressView().onAppear { + Task.detached { + await loadSettings() + } + } + } else { + FormView { + FormSection { + Toggle("Ignore Memory Limit", isOn: $ignoreMemoryLimit) + .onChangeCompat(of: ignoreMemoryLimit) { newValue in + Task { + await SharedPreferences.ignoreMemoryLimit.set(newValue) + } + } + } footer: { + Text("Do not enforce memory limits on sing-box. Will cause OOM on non-jailbroken iOS and tvOS devices.") + } + + FormSection { + Toggle("Ignore Device Sleep", isOn: $ignoreDeviceSleep) + .onChangeCompat(of: ignoreDeviceSleep) { newValue in + Task { + await SharedPreferences.ignoreDeviceSleep.set(newValue) + } + } + } footer: { + Text("Ignore system `sleep()` and `wake()` events. May cause increased power usage, only enable if you encounter unexpected `rejected ... while device paused` errors.") + } + + #if !os(tvOS) + + FormSection { + Toggle("includeAllNetworks", isOn: $includeAllNetworks) + .onChangeCompat(of: includeAllNetworks) { newValue in + Task { + await SharedPreferences.includeAllNetworks.set(newValue) + } + } + } footer: { + Text(""" + If this property is true, the system routes network traffic through the tunnel except traffic for designated system services necessary for maintaining expected device functionality. You can exclude some types of traffic using the **excludeAPNs**, **excludeLocalNetworks**, and **excludeCellularServices** properties in combination with this property. + + [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3131931-includeallnetworks) + """) + .multilineTextAlignment(.leading) + } + + FormSection { + Toggle("excludeAPNs", isOn: $excludeAPNs) + .onChangeCompat(of: excludeAPNs) { newValue in + Task { + await SharedPreferences.excludeAPNs.set(newValue) + } + } + } footer: { + Text(""" + If this property is true, the system excludes Apple Push Notification services (APNs) traffic, but only when the **includeAllNetworks** property is also true. + + [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140516-excludeapns) + """) + } + + FormSection { + Toggle("excludeCellularServices", isOn: $excludeCellularServices) + .onChangeCompat(of: excludeCellularServices) { newValue in + Task { + await SharedPreferences.excludeCellularServices.set(newValue) + } + } + } footer: { + Text(""" + If this property is true, the system excludes cellular services — such as Wi-Fi Calling, MMS, SMS, and Visual Voicemail — but only when the **includeAllNetworks** property is also true. This property doesn’t impact services that use the cellular network only — such as VoLTE — which the system automatically excludes. + + [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140517-excludecellularservices) + """) + } + + FormSection { + Toggle("excludeLocalNetworks", isOn: $excludeLocalNetworks) + .onChangeCompat(of: excludeLocalNetworks) { newValue in + Task { + await SharedPreferences.excludeLocalNetworks.set(newValue) + } + } + } footer: { + Text(""" + If this property is true, the system excludes network connections to hosts on the local network — such as AirPlay, AirDrop, and CarPlay — but only when the **includeAllNetworks** or **enforceRoutes** property is also true. + + [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3143658-excludelocalnetworks) + """) + } + + FormSection { + Toggle("enforceRoutes", isOn: $enforceRoutes) + .onChangeCompat(of: enforceRoutes) { newValue in + Task { + await SharedPreferences.enforceRoutes.set(newValue) + } + } + } footer: { + Text(""" + If this property is true when the **includeAllNetworks** property is false, the system scopes the included routes to the VPN and the excluded routes to the current primary network interface. This property supersedes the system routing table and scoping operations by apps. + + If you set both the **enforceRoutes** and **excludeLocalNetworks** properties to true, the system excludes network connections to hosts on the local network. + + [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3689459-enforceroutes) + """) + } + + #endif + + FormButton { + Task { + await SharedPreferences.resetPacketTunnel() + isLoading = true + } + } label: { + Label("Reset", systemImage: "eraser.fill") + } + .foregroundColor(.red) + } + } + } + .navigationTitle("Packet Tunnel") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + + private func loadSettings() async { + ignoreMemoryLimit = await SharedPreferences.ignoreMemoryLimit.get() + ignoreDeviceSleep = await SharedPreferences.ignoreDeviceSleep.get() + #if !os(tvOS) + includeAllNetworks = await SharedPreferences.includeAllNetworks.get() + excludeAPNs = await SharedPreferences.excludeAPNs.get() + excludeCellularServices = await SharedPreferences.excludeCellularServices.get() + excludeLocalNetworks = await SharedPreferences.excludeLocalNetworks.get() + enforceRoutes = await SharedPreferences.enforceRoutes.get() + #endif + isLoading = false + } +} diff --git a/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift b/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift new file mode 100644 index 0000000..a148d57 --- /dev/null +++ b/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift @@ -0,0 +1,65 @@ +import Library +import SwiftUI + +public struct ProfileOverrideView: View { + @State private var isLoading = true + @State private var excludeDefaultRoute = false + @State private var autoRouteUseSubRangesByDefault = false + + public init() {} + public var body: some View { + viewBuilder { + if isLoading { + ProgressView().onAppear { + Task.detached { + await loadSettings() + } + } + } else { + FormView { + FormSection { + Toggle("Hide VPN Icon", isOn: $excludeDefaultRoute) + .onChangeCompat(of: excludeDefaultRoute) { newValue in + Task { + await SharedPreferences.excludeDefaultRoute.set(newValue) + } + } + } footer: { + Text("Append `0.0.0.0/31` to `inet4_route_exclude_address` if not exists.") + } + + FormSection { + Toggle("No Default Route", isOn: $autoRouteUseSubRangesByDefault) + .onChangeCompat(of: autoRouteUseSubRangesByDefault) { newValue in + Task { + await SharedPreferences.autoRouteUseSubRangesByDefault.set(newValue) + } + } + } footer: { + Text("By default, segment routing is used in `auto_route` instead of global routing. If `*_` exists in the configuration, this item will not take effect on the corresponding network. (commonly used to resolve HomeKit compatibility issues)") + } + + FormButton { + Task { + await SharedPreferences.resetProfileOverride() + isLoading = true + } + } label: { + Label("Reset", systemImage: "eraser.fill") + } + .foregroundColor(.red) + } + } + } + .navigationTitle("Profile Override") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + + private func loadSettings() async { + excludeDefaultRoute = await SharedPreferences.excludeDefaultRoute.get() + autoRouteUseSubRangesByDefault = await SharedPreferences.autoRouteUseSubRangesByDefault.get() + isLoading = false + } +} diff --git a/ApplicationLibrary/Views/Setting/ServiceLogView.swift b/ApplicationLibrary/Views/Setting/ServiceLogView.swift index 2d70b40..e1402ae 100644 --- a/ApplicationLibrary/Views/Setting/ServiceLogView.swift +++ b/ApplicationLibrary/Views/Setting/ServiceLogView.swift @@ -1,18 +1,15 @@ import Foundation import Library import SwiftUI -import UniformTypeIdentifiers @MainActor public struct ServiceLogView: View { - #if os(macOS) - public static let windowID = "service-log" - #endif - @Environment(\.dismiss) private var dismiss + @State private var isLoading = true @State private var content = "" - @State private var fileExporterPresented = false + @State private var alert: Alert? + private let logFont = Font.system(.caption, design: .monospaced) public init() {} @@ -41,26 +38,22 @@ public struct ServiceLogView: View { #if !os(tvOS) .toolbar { if !content.isEmpty { - Button("Export") { - fileExporterPresented = true + ShareButtonCompat($alert) { + Label("Export", systemImage: "square.and.arrow.up.fill") + } itemURL: { + try content.generateShareFile(name: "service.log") } - Button("Delete", role: .destructive) { + Button(role: .destructive) { Task { await deleteContent() } + } label: { + Label("Delete", systemImage: "trash.fill") } } } #endif - #if !os(tvOS) - .fileExporter( - isPresented: $fileExporterPresented, - document: LogDocument(content), - contentType: .text, - defaultFilename: "service-log.txt", - onCompletion: { _ in } - ) - #endif + .alertBinding($alert) .navigationTitle("Service Log") #if os(tvOS) .focusable() @@ -77,6 +70,27 @@ public struct ServiceLogView: View { content = try String(contentsOf: FilePath.cacheDirectory.appendingPathComponent("stderr.log.old")) } catch {} } + #if DEBUG + if content.isEmpty { + content = "Empty content" + } + #endif + if !content.isEmpty { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let machineName = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + var deviceInfo = await "Machine: " + machineName + "\n" + #if os(iOS) + await deviceInfo += "System: " + (UIDevice.current.systemName) + " " + (UIDevice.current.systemVersion) + "\n" + #elseif os(macOS) + deviceInfo += "System: macOS " + ProcessInfo().operatingSystemVersionString + "\n" + #endif + content = deviceInfo + "\n" + content + } await MainActor.run { [content] in self.content = content isLoading = false @@ -91,28 +105,4 @@ public struct ServiceLogView: View { isLoading = true } } - - #if !os(tvOS) - private struct LogDocument: FileDocument { - static var readableContentTypes = [UTType.text] - - let content: String - - init(_ content: String) { - self.content = content - } - - init(configuration: ReadConfiguration) throws { - if let data = configuration.file.regularFileContents { - content = String(decoding: data, as: UTF8.self) - } else { - content = "" - } - } - - func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper { - FileWrapper(regularFileWithContents: Data(content.utf8)) - } - } - #endif } diff --git a/ApplicationLibrary/Views/Setting/SettingView.swift b/ApplicationLibrary/Views/Setting/SettingView.swift index 8b01521..0ec3d50 100644 --- a/ApplicationLibrary/Views/Setting/SettingView.swift +++ b/ApplicationLibrary/Views/Setting/SettingView.swift @@ -1,286 +1,137 @@ -import Foundation -import Libbox -import Library + +import StoreKit import SwiftUI -#if os(macOS) - import AppKit - import ServiceManagement -#endif -@MainActor public struct SettingView: View { - #if os(macOS) - @Environment(\.openWindow) private var openWindow - #endif + private enum Tabs: Int, CaseIterable, Identifiable { + public var id: Self { + self + } - @State private var isLoading = true + #if os(macOS) + case app + #endif - #if os(macOS) - @State private var startAtLogin = false - @Environment(\.showMenuBarExtra) private var showMenuBarExtra - @State private var keepMenuBarInBackground = false - #endif + case core, packetTunnel, profileOverride, sponsor - @State private var alwaysOn = false - @State private var disableMemoryLimit = false + var label: some View { + Label(title, systemImage: iconImage) + } - #if !os(tvOS) - @State private var includeAllNetworks = false - #endif + var title: String { + switch self { + #if os(macOS) + case .app: + return NSLocalizedString("App", comment: "") + #endif + case .core: + return NSLocalizedString("Core", comment: "") + case .packetTunnel: + return NSLocalizedString("Packet Tunnel", comment: "") + case .profileOverride: + return NSLocalizedString("Profile Override", comment: "") + case .sponsor: + return NSLocalizedString("Sponsor", comment: "") + } + } - @State private var ignoreDeviceSleep = false + private var iconImage: String { + switch self { + #if os(macOS) + case .app: + return "app.badge.fill" + #endif + case .core: + return "shippingbox.fill" + case .packetTunnel: + return "aspectratio.fill" + case .profileOverride: + return "square.dashed.inset.filled" + case .sponsor: + return "heart.fill" + } + } - @State private var version = "" - @State private var dataSize = "" + @MainActor + var contentView: some View { + viewBuilder { + switch self { + #if os(macOS) + case .app: + MacAppView() + #endif + case .core: + CoreView() + case .packetTunnel: + PacketTunnelView() + case .profileOverride: + ProfileOverrideView() + case .sponsor: + SponsorView() + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + #if os(iOS) + .background(Color(uiColor: .systemGroupedBackground)) + #endif + } + + @MainActor + var navigationLink: some View { + NavigationLink { + contentView + } label: { + label + } + } + } + + @State private var isLoading = false @State private var taiwanFlagAvailable = false - @State private var alert: Alert? public init() {} - public var body: some View { - viewBuilder { - if isLoading { - ProgressView().onAppear { - Task { - await loadSettings() - } + FormView { + #if os(macOS) + Tabs.app.navigationLink + #endif + ForEach([Tabs.core, Tabs.packetTunnel, Tabs.profileOverride]) { it in + it.navigationLink + } + Section("About") { + Link(destination: URL(string: "https://sing-box.sagernet.org/")!) { + Label("Documentation", systemImage: "doc.on.doc.fill") } - } else { - FormView { - #if os(macOS) - Section("MacOS") { - Toggle("Start At Login", isOn: $startAtLogin) - .onChangeCompat(of: startAtLogin) { newValue in - Task { - updateLoginItems(newValue) - } - } - Toggle("Show in Menu Bar", isOn: showMenuBarExtra) - .onChange(of: showMenuBarExtra.wrappedValue) { newValue in - Task { - await SharedPreferences.showMenuBarExtra.set(newValue) - if !newValue { - keepMenuBarInBackground = false - } - } - } - if showMenuBarExtra.wrappedValue { - Toggle("Keep Menu Bar in Background", isOn: $keepMenuBarInBackground) - .onChangeCompat(of: keepMenuBarInBackground) { newValue in - Task { - await SharedPreferences.menuBarExtraInBackground.set(newValue) - } - } - } - } - #endif - Section("Packet Tunnel") { - Toggle("Always On", isOn: $alwaysOn) - .onChangeCompat(of: alwaysOn) { newValue in - Task { - await SharedPreferences.alwaysOn.set(newValue) - await updateAlwaysOn(newValue) - } - } - Toggle("Disable Memory Limit", isOn: $disableMemoryLimit) - .onChangeCompat(of: disableMemoryLimit) { newValue in - Task { - await SharedPreferences.disableMemoryLimit.set(newValue) - } - } - #if !os(tvOS) - Toggle("Include All Networks", isOn: $includeAllNetworks) - .onChangeCompat(of: includeAllNetworks) { newValue in - Task { - await SharedPreferences.includeAllNetworks.set(newValue) - } - } - #endif - Toggle("Ignore Device Sleep", isOn: $ignoreDeviceSleep) - .onChangeCompat(of: ignoreDeviceSleep) { newValue in - Task { - await SharedPreferences.ignoreDeviceSleep.set(newValue) - } - } - #if os(macOS) - if Variant.useSystemExtension { - HStack { - Button("Update System Extension") { - Task { - do { - if let result = try await SystemExtension.install(forceUpdate: true) { - switch result { - case .completed: - alert = Alert( - title: Text("Update"), - message: Text("System Extension updated."), - dismissButton: .default(Text("Ok")) {} - ) - case .willCompleteAfterReboot: - alert = Alert( - title: Text("Update"), - message: Text("Reboot required."), - dismissButton: .default(Text("Ok")) {} - ) - } - } - } catch { - alert = Alert(error) - } - } - } - Button { - Task { - do { - if let result = try await SystemExtension.uninstall() { - switch result { - case .completed: - alert = Alert( - title: Text("Uninstall"), - message: Text("System Extension removed."), - dismissButton: .default(Text("Ok")) {} - ) - case .willCompleteAfterReboot: - alert = Alert( - title: Text("Uninstall"), - message: Text("Reboot required."), - dismissButton: .default(Text("Ok")) {} - ) - } - } - } catch { - alert = Alert(error) - } - } - } label: { - Text("Uninstall System Extension").foregroundColor(.red) - } - }.frame(maxWidth: .infinity, alignment: .trailing) - } - #endif + .buttonStyle(.plain) + .foregroundColor(.accentColor) + #if !os(tvOS) + RequestReviewButton { + Label("Rate on the App Store", systemImage: "text.bubble.fill") } - Section("Core") { - FormTextItem("Version", version) - FormTextItem("Data Size", dataSize) - #if os(iOS) || os(tvOS) - NavigationLink(destination: ServiceLogView()) { - Text("View Service Log") - } - Button("Clear Working Directory") { - Task { - await clearWorkingDirectory() + #endif + Tabs.sponsor.navigationLink + } + Section("Debug") { + NavigationLink { + ServiceLogView() + } label: { + Label("Service Log", systemImage: "doc.on.clipboard") + } + FormTextItem("Taiwan Flag Available", "touchid") { + if isLoading { + Text("Loading...") + .onAppear { + Task.detached { + taiwanFlagAvailable = !DeviceCensorship.isChinaDevice() + isLoading = false } } - .foregroundColor(.red) - #elseif os(macOS) - HStack { - Button("View Service Log") { - openWindow(id: ServiceLogView.windowID) - } - Button("Open Working Directory") { - NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: FilePath.workingDirectory.relativePath) - } - Button { - Task { - await clearWorkingDirectory() - } - } label: { - Text("Clear Working Directory").foregroundColor(.red) - } - }.frame(maxWidth: .infinity, alignment: .trailing) - #endif - } - Section("Debug") { - FormTextItem("Taiwan Flag Available", taiwanFlagAvailable.description) + } else { + Text(taiwanFlagAvailable.description) } } } } - .alertBinding($alert) - } - - #if os(macOS) - private func updateLoginItems(_ startAtLogin: Bool) { - do { - if startAtLogin { - if SMAppService.mainApp.status == .enabled { - try? SMAppService.mainApp.unregister() - } - - try SMAppService.mainApp.register() - } else { - try SMAppService.mainApp.unregister() - } - } catch { - alert = Alert(error) - } - } - #endif - - private func loadSettings() async { - #if os(macOS) - startAtLogin = SMAppService.mainApp.status == .enabled - keepMenuBarInBackground = await SharedPreferences.menuBarExtraInBackground.get() - #endif - alwaysOn = await SharedPreferences.alwaysOn.get() - disableMemoryLimit = await SharedPreferences.disableMemoryLimit.get() - #if !os(tvOS) - includeAllNetworks = await SharedPreferences.includeAllNetworks.get() - #endif - ignoreDeviceSleep = await SharedPreferences.ignoreDeviceSleep.get() - if ApplicationLibrary.inPreview { - version = "" - dataSize = LibboxFormatBytes(1000 * 1000 * 10) - taiwanFlagAvailable = true - isLoading = false - } else { - version = LibboxVersion() - dataSize = "Loading..." - taiwanFlagAvailable = !DeviceCensorship.isChinaDevice() - isLoading = false - await loadSettingsBackground() - } - } - - private nonisolated func loadSettingsBackground() async { - let dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown" - await MainActor.run { - self.dataSize = dataSize - } - } - - private nonisolated func clearWorkingDirectory() async { - try? FileManager.default.removeItem(at: FilePath.workingDirectory) - await MainActor.run { - isLoading = true - } - } - - private func updateAlwaysOn(_ newState: Bool) async { - guard let profile = try? await ExtensionProfile.load() else { - return - } - do { - try await profile.updateAlwaysOn(newState) - } catch { - alert = Alert(error) - } - } -} - -private extension URL { - func formattedSize() throws -> String? { - guard let urls = FileManager.default.enumerator(at: self, includingPropertiesForKeys: nil)?.allObjects as? [URL] else { - return nil - } - let size = try urls.lazy.reduce(0) { - try ($1.resourceValues(forKeys: [.totalFileAllocatedSizeKey]).totalFileAllocatedSize ?? 0) + $0 - } - let formatter = ByteCountFormatter() - formatter.countStyle = .file - guard let byteCount = formatter.string(for: size) else { - return nil - } - return byteCount + .navigationTitle("Settings") } } diff --git a/ApplicationLibrary/Views/Setting/SponsorView.swift b/ApplicationLibrary/Views/Setting/SponsorView.swift new file mode 100644 index 0000000..64d4df6 --- /dev/null +++ b/ApplicationLibrary/Views/Setting/SponsorView.swift @@ -0,0 +1,86 @@ +import Foundation +import StoreKit +import SwiftUI + +public struct SponsorView: View { + @Environment(\.openURL) private var openURL + + @State private var isLoading = true + @State private var products: [Product] = [] + @State private var subscriptionError: Error? + @State private var isPurchasing = false + @State private var alert: Alert? + + public init() {} + public var body: some View { + FormView { + Section { + EmptyView() + } footer: { + Text("**If I’ve defended your modern life, please consider sponsoring me.**") + .frame(maxWidth: .infinity, alignment: .leading) + } + + Section("Without commission") { + FormButton("GitHub Sponsor (recommended)") { + openURL(URL(string: "https://github.com/sponsors/nekohasekai")!) + } + FormButton("Other methods") { + openURL(URL(string: "https://sekai.icu/sponsor/")!) + } + } + Section("Via App Store") { + if isLoading { + ProgressView() + .onAppear { + Task.detached { + await loadProducts() + } + } + } else if let subscriptionError { + Text("Sponsor via App Store not available: \(subscriptionError.localizedDescription)") + } else { + ForEach(products, id: \.id) { it in + FormButton(it.displayName) { + isPurchasing = true + Task.detached { + do { + let result = try await it.purchase() + switch result { + case .success: + alert = Alert(title: Text("Success"), message: Text("Thank u.")) + case .pending: + break + case .userCancelled: + break + } + } catch { + alert = Alert(error) + } + isPurchasing = false + } + } + .disabled(isPurchasing) + } + } + } + } + .alertBinding($alert) + .navigationTitle("Sponsor") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + + private func loadProducts() async { + defer { + isLoading = false + } + do { + let productIds = ["sponsor_1_1", "sponsor_10", "sponsor_100"] + products = try await Product.products(for: productIds) + } catch { + subscriptionError = error + } + } +} diff --git a/Library/Database/Profile+Transferable.swift b/Library/Database/Profile+Transferable.swift index 2a79e99..ebd2118 100644 --- a/Library/Database/Profile+Transferable.swift +++ b/Library/Database/Profile+Transferable.swift @@ -64,6 +64,16 @@ public extension LibboxProfileContent { } } +public extension String { + func generateShareFile(name: String) throws -> URL { + let shareDirectory = FilePath.cacheDirectory.appendingPathComponent("share", isDirectory: true) + try FileManager.default.createDirectory(at: shareDirectory, withIntermediateDirectories: true) + let shareFile = shareDirectory.appendingPathComponent(name) + try write(to: shareFile, atomically: true, encoding: .utf8) + return shareFile + } +} + @available(iOS 16.0, macOS 13.0, *) public struct TypedProfile: Transferable, Codable { public let content: LibboxProfileContent diff --git a/Library/Database/ShadredPreferences+Database.swift b/Library/Database/ShadredPreferences+Database.swift index 5f3fff7..2cd9056 100644 --- a/Library/Database/ShadredPreferences+Database.swift +++ b/Library/Database/ShadredPreferences+Database.swift @@ -27,7 +27,7 @@ extension SharedPreferences { } } - public nonisolated func set(_ newValue: T) async { + public nonisolated func set(_ newValue: T?) async { do { try await SharedPreferences.write(name, newValue) } catch { diff --git a/Library/Database/SharedPreferences.swift b/Library/Database/SharedPreferences.swift index a0e64a1..c1f8955 100644 --- a/Library/Database/SharedPreferences.swift +++ b/Library/Database/SharedPreferences.swift @@ -4,25 +4,54 @@ public enum SharedPreferences { public static let selectedProfileID = Preference("selected_profile_id", defaultValue: -1) #if os(macOS) - private static let disableMemoryLimitByDefault = true + private static let ignoreMemoryLimitByDefault = true #else - private static let disableMemoryLimitByDefault = false + private static let ignoreMemoryLimitByDefault = false #endif - public static let disableMemoryLimit = Preference("disable_memory_limit", defaultValue: disableMemoryLimitByDefault) + public static let alwaysOn = Preference("always_on", defaultValue: false) + + public static let ignoreMemoryLimit = Preference("ignore_memory_limit", defaultValue: ignoreMemoryLimitByDefault) + public static let ignoreDeviceSleep = Preference("ignore_device_sleep", defaultValue: false) + + #if os(iOS) + public static let excludeLocalNetworksByDefault = true + #elseif os(macOS) + public static let excludeLocalNetworksByDefault = false + #endif #if !os(tvOS) public static let includeAllNetworks = Preference("include_all_networks", defaultValue: false) + public static let excludeAPNs = Preference("exclude_apns", defaultValue: true) + public static let excludeLocalNetworks = Preference("exclude_local_networks", defaultValue: excludeLocalNetworksByDefault) + public static let excludeCellularServices = Preference("exclude_celluar_services", defaultValue: true) + public static let enforceRoutes = Preference("enforce_routes", defaultValue: false) + #endif + public static func resetPacketTunnel() async { + await ignoreMemoryLimit.set(nil) + await ignoreDeviceSleep.set(nil) + #if !os(tvOS) + await includeAllNetworks.set(nil) + await excludeAPNs.set(nil) + await excludeLocalNetworks.set(nil) + await excludeCellularServices.set(nil) + await enforceRoutes.set(nil) + #endif + } + public static let maxLogLines = Preference("max_log_lines", defaultValue: 300) - public static let alwaysOn = Preference("always_on", defaultValue: false) - public static let ignoreDeviceSleep = Preference("ignore_device_sleep", defaultValue: false) #if os(macOS) public static let showMenuBarExtra = Preference("show_menu_bar_extra", defaultValue: true) public static let menuBarExtraInBackground = Preference("menu_bar_extra_in_background", defaultValue: false) public static let startedByUser = Preference("started_by_user", defaultValue: false) + + public static func resetMacOS() async { + await showMenuBarExtra.set(nil) + await menuBarExtraInBackground.set(nil) + } #endif #if os(iOS) @@ -30,4 +59,20 @@ public enum SharedPreferences { #endif public static let systemProxyEnabled = Preference("system_proxy_enabled", defaultValue: true) + + // Profile Override + + public static let excludeDefaultRoute = Preference("exclude_default_route", defaultValue: false) + public static let autoRouteUseSubRangesByDefault = Preference("auto_route_use_sub_ranges_by_default", defaultValue: false) + + public static func resetProfileOverride() async { + await excludeDefaultRoute.set(nil) + await autoRouteUseSubRangesByDefault.set(nil) + } + + #if DEBUG + public static let inDebug = true + #else + public static let inDebug = false + #endif } diff --git a/Library/Network/ExtensionEnvironments.swift b/Library/Network/ExtensionEnvironments.swift index cabbd90..1076251 100644 --- a/Library/Network/ExtensionEnvironments.swift +++ b/Library/Network/ExtensionEnvironments.swift @@ -5,8 +5,10 @@ public class ExtensionEnvironments: ObservableObject { @Published public var logClient = CommandClient(.log) @Published public var extensionProfileLoading = true @Published public var extensionProfile: ExtensionProfile? + public let profileUpdate = ObjectWillChangePublisher() public let selectedProfileUpdate = ObjectWillChangePublisher() + public let openSettings = ObjectWillChangePublisher() public init() {} diff --git a/Library/Network/ExtensionPlatformInterface.swift b/Library/Network/ExtensionPlatformInterface.swift index 34e548c..285d2cd 100644 --- a/Library/Network/ExtensionPlatformInterface.swift +++ b/Library/Network/ExtensionPlatformInterface.swift @@ -27,6 +27,8 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc throw NSError(domain: "nil return pointer", code: 0) } + let autoRouteUseSubRangesByDefault = await SharedPreferences.autoRouteUseSubRangesByDefault.get() + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") if options.getAutoRoute() { settings.mtu = NSNumber(value: options.getMTU()) @@ -57,6 +59,15 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc let ipv4RoutePrefix = inet4RouteAddressIterator.next()! ipv4Routes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address(), subnetMask: ipv4RoutePrefix.mask())) } + } else if autoRouteUseSubRangesByDefault { + ipv4Routes.append(NEIPv4Route(destinationAddress: "1.0.0.0", subnetMask: "255.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "2.0.0.0", subnetMask: "254.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "4.0.0.0", subnetMask: "252.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "8.0.0.0", subnetMask: "248.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "16.0.0.0", subnetMask: "240.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "32.0.0.0", subnetMask: "224.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "64.0.0.0", subnetMask: "192.0.0.0")) + ipv4Routes.append(NEIPv4Route(destinationAddress: "128.0.0.0", subnetMask: "128.0.0.0")) } else { ipv4Routes.append(NEIPv4Route.default()) } @@ -66,6 +77,13 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc let ipv4RoutePrefix = inet4RouteExcludeAddressIterator.next()! ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address(), subnetMask: ipv4RoutePrefix.mask())) } + if await SharedPreferences.excludeDefaultRoute.get(), !ipv4Routes.isEmpty { + if !ipv4ExcludeRoutes.contains(where: { it in + it.destinationAddress == "0.0.0.0" && it.destinationSubnetMask == "255.255.255.254" + }) { + ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "255.255.255.254")) + } + } ipv4Settings.includedRoutes = ipv4Routes ipv4Settings.excludedRoutes = ipv4ExcludeRoutes @@ -89,6 +107,15 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc let ipv6RoutePrefix = inet6RouteAddressIterator.next()! ipv6Routes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.address(), networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix()))) } + } else if autoRouteUseSubRangesByDefault { + ipv6Routes.append(NEIPv6Route(destinationAddress: "100::", networkPrefixLength: 8)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "200::", networkPrefixLength: 7)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "400::", networkPrefixLength: 6)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "800::", networkPrefixLength: 5)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "1000::", networkPrefixLength: 4)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "2000::", networkPrefixLength: 3)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "4000::", networkPrefixLength: 2)) + ipv6Routes.append(NEIPv6Route(destinationAddress: "8000::", networkPrefixLength: 1)) } else { ipv6Routes.append(NEIPv6Route.default()) } diff --git a/Library/Network/ExtensionProvider.swift b/Library/Network/ExtensionProvider.swift index c7bcfad..d0a21f5 100644 --- a/Library/Network/ExtensionProvider.swift +++ b/Library/Network/ExtensionProvider.swift @@ -35,7 +35,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { writeError("(packet-tunnel) redirect stderr error: \(error.localizedDescription)") } - await LibboxSetMemoryLimit(!SharedPreferences.disableMemoryLimit.get()) + await LibboxSetMemoryLimit(!SharedPreferences.ignoreMemoryLimit.get()) ignoreDeviceSleep = await SharedPreferences.ignoreDeviceSleep.get() if platformInterface == nil { diff --git a/Library/Network/SystemExtension.swift b/Library/Network/SystemExtension.swift index 43267d7..58c5eff 100644 --- a/Library/Network/SystemExtension.swift +++ b/Library/Network/SystemExtension.swift @@ -111,13 +111,13 @@ return false } - public static func install(forceUpdate: Bool = false, inBackground: Bool = false) async throws -> OSSystemExtensionRequest.Result? { + public nonisolated static func install(forceUpdate: Bool = false, inBackground: Bool = false) async throws -> OSSystemExtensionRequest.Result? { try await Task.detached { try SystemExtension(forceUpdate, inBackground).activation() }.result.get() } - public static func uninstall() async throws -> OSSystemExtensionRequest.Result? { + public nonisolated static func uninstall() async throws -> OSSystemExtensionRequest.Result? { try await Task.detached { try SystemExtension().deactivation() }.result.get() diff --git a/Library/Shared/Color+Extension.swift b/Library/Shared/Color+Extension.swift new file mode 100644 index 0000000..3efa58c --- /dev/null +++ b/Library/Shared/Color+Extension.swift @@ -0,0 +1,25 @@ +import Foundation +import SwiftUI +#if canImport(UIKit) + import UIKit +#elseif canImport(AppKit) + import AppKit +#endif + +public extension Color { + static var textColor: Color { + #if canImport(UIKit) + return Color(uiColor: .label) + #elseif canImport(AppKit) + return Color(nsColor: .textColor) + #endif + } + + static var linkColor: Color { + #if canImport(UIKit) + return Color(uiColor: .link) + #elseif canImport(AppKit) + return Color(nsColor: .linkColor) + #endif + } +} diff --git a/MacLibrary/ApplicationDelegate.swift b/MacLibrary/ApplicationDelegate.swift index c0491ce..9cbe221 100644 --- a/MacLibrary/ApplicationDelegate.swift +++ b/MacLibrary/ApplicationDelegate.swift @@ -12,7 +12,7 @@ open class ApplicationDelegate: NSObject, NSApplicationDelegate { let launchedAsLogInItem = event?.eventID == kAEOpenApplication && event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem - if !launchedAsLogInItem || !SharedPreferences.showMenuBarExtra.getBlocking() || !SharedPreferences.menuBarExtraInBackground.getBlocking() { + if SharedPreferences.inDebug || !launchedAsLogInItem || !SharedPreferences.showMenuBarExtra.getBlocking() || !SharedPreferences.menuBarExtraInBackground.getBlocking() { NSApp.setActivationPolicy(.regular) NSApp.activate(ignoringOtherApps: true) } else { @@ -35,7 +35,7 @@ open class ApplicationDelegate: NSObject, NSApplicationDelegate { } public func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { - !SharedPreferences.menuBarExtraInBackground.getBlocking() + SharedPreferences.inDebug || !SharedPreferences.menuBarExtraInBackground.getBlocking() } public func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows flag: Bool) -> Bool { diff --git a/MacLibrary/MacApplication.swift b/MacLibrary/MacApplication.swift index 0ec36fa..fe71828 100644 --- a/MacLibrary/MacApplication.swift +++ b/MacLibrary/MacApplication.swift @@ -35,27 +35,14 @@ public struct MacApplication: Scene { } } SidebarCommands() + CommandGroup(replacing: .appSettings) { + Button("Settings") { + environments.openSettings.send() + } + .keyboardShortcut(",", modifiers: [.command]) + } } - WindowGroup("New Profile", id: NewProfileView.windowID, for: NewProfileView.ImportRequest.self) { importRequest in - NewProfileView(importRequest.wrappedValue) - .environmentObject(environments) - }.commandsRemoved() - - WindowGroup("Edit Profile", id: EditProfileWindowView.windowID, for: Int64.self) { profileID in - EditProfileWindowView(profileID.wrappedValue) - .environmentObject(environments) - }.commandsRemoved() - - WindowGroup("Edit Content", id: EditProfileContentView.windowID, for: EditProfileContentView.Context.self) { context in - EditProfileContentView(context.wrappedValue) - .environmentObject(environments) - }.commandsRemoved() - - Window("Service Log", id: ServiceLogView.windowID) { - ServiceLogView() - .environmentObject(environments) - } MenuBarExtra(isInserted: $showMenuBarExtra) { MenuView(isMenuPresented: $isMenuPresented) .environmentObject(environments) diff --git a/MacLibrary/MainView.swift b/MacLibrary/MainView.swift index 339da9a..7937954 100644 --- a/MacLibrary/MainView.swift +++ b/MacLibrary/MainView.swift @@ -26,8 +26,10 @@ public struct MainView: View { NavigationSplitView { SidebarView() } detail: { - selection.contentView - .navigationTitle(selection.title) + NavigationStack { + selection.contentView + .navigationTitle(selection.title) + } } .onAppear { environments.postReload() @@ -55,6 +57,9 @@ public struct MainView: View { environments.connectLog() } } + .onReceive(environments.openSettings) { + selection = .settings + } .formStyle(.grouped) .environment(\.selection, $selection) .environment(\.importProfile, $importProfile) diff --git a/MacLibrary/SidebarView.swift b/MacLibrary/SidebarView.swift index 0aa1964..df83edf 100644 --- a/MacLibrary/SidebarView.swift +++ b/MacLibrary/SidebarView.swift @@ -25,24 +25,30 @@ public struct SidebarView: View { var body: some View { VStack { - if extensionProfile.status.isConnectedStrict { - List(selection: selection) { - Section(NavigationPage.dashboard.title) { - Label("Overview", systemImage: "text.and.command.macwindow").tag(NavigationPage.dashboard) - NavigationPage.groups.label.tag(NavigationPage.groups) + viewBuilder { + if extensionProfile.status.isConnectedStrict { + List(selection: selection) { + Section(NavigationPage.dashboard.title) { + Label("Overview", systemImage: "text.and.command.macwindow") + .tint(.textColor) + .tag(NavigationPage.dashboard) + NavigationPage.groups.label.tag(NavigationPage.groups) + } + Divider() + ForEach(NavigationPage.macosDefaultPages, id: \.self) { it in + it.label + } } - Divider() - ForEach(NavigationPage.macosDefaultPages, id: \.self) { it in + } else { + List(NavigationPage.allCases.filter { it in + it.visible(extensionProfile) + }, selection: selection) { it in it.label } } - } else { - List(NavigationPage.allCases.filter { it in - it.visible(extensionProfile) - }, selection: selection) { it in - it.label - } } + .listStyle(.sidebar) + .scrollDisabled(true) } .onChangeCompat(of: extensionProfile.status) { if !selection.wrappedValue.visible(extensionProfile) { diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index 071bc19..6112960 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -25,7 +25,10 @@ 3A2EAEED2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */; }; 3A3AA7FC2A4EFDAE002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; 3A3AA7FF2A4EFDB3002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; + 3A3AB2A72B70C146001815AE /* CoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3AB2A62B70C146001815AE /* CoreView.swift */; }; + 3A3AB2A92B70C5F1001815AE /* RequestReviewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */; }; 3A3DEBEB2A4FFE2D00373BF4 /* AppIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */; }; + 3A411CEC2B734959000D9501 /* MacAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A411CEB2B734959000D9501 /* MacAppView.swift */; }; 3A44BB822A4DC28700E4C9F8 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A44BB812A4DC28700E4C9F8 /* MainView.swift */; }; 3A4A020D2B53E3DC004EFB87 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = 3A4A020C2B53E3DC004EFB87 /* QRCode */; }; 3A4EAD1B2A4FEB02005435B3 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; @@ -37,15 +40,13 @@ 3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF342D02A4AACC4002B34AC /* ExtensionStatusView.swift */; }; 3A4EAD272A4FEB65005435B3 /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */; }; 3A4EAD282A4FEB65005435B3 /* ActiveDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */; }; - 3A4EAD292A4FEB6D005435B3 /* Formtem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF342D32A4AADB2002B34AC /* Formtem.swift */; }; + 3A4EAD292A4FEB6D005435B3 /* FormItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF342D32A4AADB2002B34AC /* FormItem.swift */; }; 3A4EAD2A2A4FEB6D005435B3 /* Binding+Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */; }; 3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7E90302A46745A00D53052 /* ViewBuilder.swift */; }; - 3A4EAD2C2A4FEB77005435B3 /* EditProfileWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DFC2A4B096000900CC8 /* EditProfileWindowView.swift */; }; 3A4EAD2D2A4FEB77005435B3 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DF62A4AFB2C00900CC8 /* ProfileView.swift */; }; 3A4EAD2E2A4FEB77005435B3 /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8E002A4B0F6300900CC8 /* EditProfileView.swift */; }; 3A4EAD2F2A4FEB77005435B3 /* EditProfileContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */; }; 3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DF82A4AFCB400900CC8 /* NewProfileView.swift */; }; - 3A4EAD312A4FEB7B005435B3 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E712A4BF6F6009757F1 /* SettingView.swift */; }; 3A4EAD322A4FEB7B005435B3 /* ServiceLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */; }; 3A4EAD342A4FEB7F005435B3 /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA1ABB92A4C4054000FD4BA /* LogView.swift */; }; 3A4EAD352A4FEB9C005435B3 /* UIProfileUpdateTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A55F9572A4D137E003C4EF4 /* UIProfileUpdateTask.swift */; }; @@ -62,6 +63,9 @@ 3A57DF422A4D927A00690BC5 /* Profile+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A57DF412A4D927A00690BC5 /* Profile+Hashable.swift */; }; 3A5F26C82A503D4A00C27EDF /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; 3A5F26C92A503D4A00C27EDF /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3A60CC272B70880100D2D682 /* PacketTunnelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A60CC262B70880100D2D682 /* PacketTunnelView.swift */; }; + 3A60CC292B70A7C400D2D682 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A60CC282B70A7C400D2D682 /* Color+Extension.swift */; }; + 3A60CC2B2B70AD6700D2D682 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A60CC2A2B70AD6700D2D682 /* SettingView.swift */; }; 3A648D2D2A4EEAA600D95A12 /* Library.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A648D2C2A4EEAA600D95A12 /* Library.swift */; }; 3A648D542A4EF4C700D95A12 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF342B12A4AA520002B34AC /* NetworkExtension.framework */; }; 3A6CA5A02A71317A0027933B /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3A6CA59F2A71317A0027933B /* MarkdownUI */; }; @@ -73,6 +77,7 @@ 3A76504C2A4F08BA003945C5 /* Libbox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC20DB2A4599D000A63465 /* Libbox.xcframework */; }; 3A7701702A4E6B34008F031F /* IntentsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A77016F2A4E6B34008F031F /* IntentsExtension.swift */; }; 3A7701722A4E6B34008F031F /* Intents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7701712A4E6B34008F031F /* Intents.swift */; }; + 3A7904502B6E7BAC006C08D5 /* SponsorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A79044F2B6E7BAC006C08D5 /* SponsorView.swift */; }; 3A7E90352A46756300D53052 /* SharedPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7E90342A46756300D53052 /* SharedPreferences.swift */; }; 3A7E90382A46778E00D53052 /* BinaryCodable in Frameworks */ = {isa = PBXBuildFile; productRef = 3A7E90372A46778E00D53052 /* BinaryCodable */; }; 3A8655142A4FA26600B7181F /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -142,6 +147,11 @@ 3AEECC4F2A6DFFE1006A0E0C /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEC210D2A459B1900A63465 /* MainView.swift */; }; 3AF342A02A4A9916002B34AC /* ExtensionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF3429F2A4A9916002B34AC /* ExtensionProfile.swift */; }; 3AF3A3D22B2207F3001FD7C1 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF3A3D12B2207E1001FD7C1 /* libresolv.tbd */; }; + 3AF5E3E72B6F90640058B9E8 /* ProfileOverrideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF5E3E62B6F90640058B9E8 /* ProfileOverrideView.swift */; }; + 3AF5E3E92B6F9CFB0058B9E8 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF5E3E82B6F9CFB0058B9E8 /* StoreKit.framework */; }; + 3AF5E3EB2B6F9D020058B9E8 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF5E3EA2B6F9D020058B9E8 /* StoreKit.framework */; }; + 3AF5E3EC2B6F9D070058B9E8 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF5E3EA2B6F9D020058B9E8 /* StoreKit.framework */; }; + 3AF5E3EE2B6F9D0F0058B9E8 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF5E3ED2B6F9D0F0058B9E8 /* StoreKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -445,8 +455,11 @@ 3A27D8FF2A89BE230031EBCC /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = ""; }; 3A27D9012A89C6870031EBCC /* ExtensionEnvironments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionEnvironments.swift; sourceTree = ""; }; 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = ""; }; + 3A3AB2A62B70C146001815AE /* CoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreView.swift; sourceTree = ""; }; + 3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestReviewButton.swift; sourceTree = ""; }; 3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExtensionFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/ExtensionFoundation.framework; sourceTree = DEVELOPER_DIR; }; 3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppIntents.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/AppIntents.framework; sourceTree = DEVELOPER_DIR; }; + 3A411CEB2B734959000D9501 /* MacAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppView.swift; sourceTree = ""; }; 3A44BB662A4DBF7900E4C9F8 /* SFI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SFI.entitlements; sourceTree = ""; }; 3A44BB802A4DC25E00E4C9F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 3A44BB812A4DC28700E4C9F8 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; @@ -463,6 +476,9 @@ 3A57DF362A4D5D2600690BC5 /* Profile+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Date.swift"; sourceTree = ""; }; 3A57DF3F2A4D70B600690BC5 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; 3A57DF412A4D927A00690BC5 /* Profile+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Hashable.swift"; sourceTree = ""; }; + 3A60CC262B70880100D2D682 /* PacketTunnelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelView.swift; sourceTree = ""; }; + 3A60CC282B70A7C400D2D682 /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = ""; }; + 3A60CC2A2B70AD6700D2D682 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; 3A648D2C2A4EEAA600D95A12 /* Library.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Library.swift; sourceTree = ""; }; 3A6CA5A52A713AA10027933B /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = ""; }; 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = IntentsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -470,6 +486,7 @@ 3A7701712A4E6B34008F031F /* Intents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Intents.swift; sourceTree = ""; }; 3A7701732A4E6B34008F031F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3A7701802A4E71F5008F031F /* IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IntentsExtension.entitlements; sourceTree = ""; }; + 3A79044F2B6E7BAC006C08D5 /* SponsorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorView.swift; sourceTree = ""; }; 3A7E90302A46745A00D53052 /* ViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewBuilder.swift; sourceTree = ""; }; 3A7E90342A46756300D53052 /* SharedPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedPreferences.swift; sourceTree = ""; }; 3A9144D82A46AE370036E9AD /* ShadredPreferences+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShadredPreferences+Database.swift"; sourceTree = ""; }; @@ -477,7 +494,6 @@ 3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCompat.swift; sourceTree = ""; }; 3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationDestinationCompat.swift; sourceTree = ""; }; 3AA1ABB92A4C4054000FD4BA /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogView.swift; sourceTree = ""; }; - 3AAB5E712A4BF6F6009757F1 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; 3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLogView.swift; sourceTree = ""; }; 3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileContentView.swift; sourceTree = ""; }; 3AAB5E7A2A4C1446009757F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -502,7 +518,6 @@ 3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; }; 3ADF8DF62A4AFB2C00900CC8 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; 3ADF8DF82A4AFCB400900CC8 /* NewProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileView.swift; sourceTree = ""; }; - 3ADF8DFC2A4B096000900CC8 /* EditProfileWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileWindowView.swift; sourceTree = ""; }; 3ADF8E002A4B0F6300900CC8 /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = ""; }; 3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Unwrap.swift"; sourceTree = ""; }; 3AE171992A8128DD00393060 /* TVExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TVExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -550,8 +565,12 @@ 3AF342B12A4AA520002B34AC /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 3AF342CC2A4AA88C002B34AC /* StartStopButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartStopButton.swift; sourceTree = ""; }; 3AF342D02A4AACC4002B34AC /* ExtensionStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionStatusView.swift; sourceTree = ""; }; - 3AF342D32A4AADB2002B34AC /* Formtem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formtem.swift; sourceTree = ""; }; + 3AF342D32A4AADB2002B34AC /* FormItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormItem.swift; sourceTree = ""; }; 3AF3A3D12B2207E1001FD7C1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/usr/lib/libresolv.tbd; sourceTree = DEVELOPER_DIR; }; + 3AF5E3E62B6F90640058B9E8 /* ProfileOverrideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileOverrideView.swift; sourceTree = ""; }; + 3AF5E3E82B6F9CFB0058B9E8 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.2.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; + 3AF5E3EA2B6F9D020058B9E8 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + 3AF5E3ED2B6F9D0F0058B9E8 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.2.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -587,6 +606,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3AF5E3EE2B6F9D0F0058B9E8 /* StoreKit.framework in Frameworks */, 3A4FB1572A73467F007012B9 /* Library.framework in Frameworks */, 3A4FB15C2A73468C007012B9 /* ApplicationLibrary.framework in Frameworks */, ); @@ -605,6 +625,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3AF5E3E92B6F9CFB0058B9E8 /* StoreKit.framework in Frameworks */, 3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */, 3A4EAD372A4FEC20005435B3 /* ApplicationLibrary.framework in Frameworks */, ); @@ -617,6 +638,7 @@ 3A5F26C82A503D4A00C27EDF /* Library.framework in Frameworks */, 3AEECC352A6DFDAD006A0E0C /* MacLibrary.framework in Frameworks */, 3AEECC412A6DFE29006A0E0C /* MacLibrary.framework in Frameworks */, + 3AF5E3EB2B6F9D020058B9E8 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -644,6 +666,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3AF5E3EC2B6F9D070058B9E8 /* StoreKit.framework in Frameworks */, 3AEECC1D2A6DFA79006A0E0C /* Library.framework in Frameworks */, 3AEECC3A2A6DFDC5006A0E0C /* MacLibrary.framework in Frameworks */, ); @@ -733,8 +756,13 @@ 3AAB5E702A4BF6EA009757F1 /* Setting */ = { isa = PBXGroup; children = ( - 3AAB5E712A4BF6F6009757F1 /* SettingView.swift */, 3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */, + 3A79044F2B6E7BAC006C08D5 /* SponsorView.swift */, + 3AF5E3E62B6F90640058B9E8 /* ProfileOverrideView.swift */, + 3A60CC262B70880100D2D682 /* PacketTunnelView.swift */, + 3A60CC2A2B70AD6700D2D682 /* SettingView.swift */, + 3A3AB2A62B70C146001815AE /* CoreView.swift */, + 3A411CEB2B734959000D9501 /* MacAppView.swift */, ); path = Setting; sourceTree = ""; @@ -767,7 +795,6 @@ 3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */, 3ADF8DF62A4AFB2C00900CC8 /* ProfileView.swift */, 3ADF8DF82A4AFCB400900CC8 /* NewProfileView.swift */, - 3ADF8DFC2A4B096000900CC8 /* EditProfileWindowView.swift */, 3ADF8E002A4B0F6300900CC8 /* EditProfileView.swift */, 3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */, ); @@ -839,6 +866,9 @@ 3AEC21012A459AE300A63465 /* Frameworks */ = { isa = PBXGroup; children = ( + 3AF5E3ED2B6F9D0F0058B9E8 /* StoreKit.framework */, + 3AF5E3EA2B6F9D020058B9E8 /* StoreKit.framework */, + 3AF5E3E82B6F9CFB0058B9E8 /* StoreKit.framework */, 3AF3A3D12B2207E1001FD7C1 /* libresolv.tbd */, 3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */, 3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */, @@ -911,6 +941,7 @@ 3AEC21472A45A9DE00A63465 /* Bundle+Version.swift */, 3AEC214B2A45AA8E00A63465 /* FilePath.swift */, 3A2223552A6E1BDE00C50B23 /* Variant.swift */, + 3A60CC282B70A7C400D2D682 /* Color+Extension.swift */, ); path = Shared; sourceTree = ""; @@ -988,7 +1019,7 @@ isa = PBXGroup; children = ( 3A7E90302A46745A00D53052 /* ViewBuilder.swift */, - 3AF342D32A4AADB2002B34AC /* Formtem.swift */, + 3AF342D32A4AADB2002B34AC /* FormItem.swift */, 3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */, 3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */, 3AB1220A2A70FD500087CD55 /* Alert.swift */, @@ -997,6 +1028,7 @@ 3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */, 3AC729F12A76088E00FE8EC1 /* ShareButton.swift */, 3ACE6DE22ACADF55009D9A8A /* Binding+Setter.swift */, + 3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */, ); path = Abstract; sourceTree = ""; @@ -1442,15 +1474,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A60CC2B2B70AD6700D2D682 /* SettingView.swift in Sources */, 3A99B42A2A7526990010D4B0 /* NavigationStackCompat.swift in Sources */, 3A4EAD2E2A4FEB77005435B3 /* EditProfileView.swift in Sources */, 3A4EAD2A2A4FEB6D005435B3 /* Binding+Unwrap.swift in Sources */, 3A4EAD2D2A4FEB77005435B3 /* ProfileView.swift in Sources */, 3A4EAD352A4FEB9C005435B3 /* UIProfileUpdateTask.swift in Sources */, + 3A60CC272B70880100D2D682 /* PacketTunnelView.swift in Sources */, 3A4EAD222A4FEB54005435B3 /* NavigationPage.swift in Sources */, + 3A411CEC2B734959000D9501 /* MacAppView.swift in Sources */, 3AC8CF9B2A736C750002AF3C /* ImportProfileView.swift in Sources */, - 3A4EAD292A4FEB6D005435B3 /* Formtem.swift in Sources */, + 3A4EAD292A4FEB6D005435B3 /* FormItem.swift in Sources */, + 3AF5E3E72B6F90640058B9E8 /* ProfileOverrideView.swift in Sources */, 3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */, + 3A3AB2A72B70C146001815AE /* CoreView.swift in Sources */, 3A4EAD242A4FEB65005435B3 /* InstallProfileButton.swift in Sources */, 3AE4D0C12A6E4852009FEA9E /* InstallSystemExtensionButton.swift in Sources */, 3A4EAD232A4FEB5A005435B3 /* EnvironmentValues.swift in Sources */, @@ -1458,7 +1495,6 @@ 3A4EAD282A4FEB65005435B3 /* ActiveDashboardView.swift in Sources */, 3A1CF2F02A50E5EE000A8289 /* GroupListView.swift in Sources */, 3A4EAD322A4FEB7B005435B3 /* ServiceLogView.swift in Sources */, - 3A4EAD312A4FEB7B005435B3 /* SettingView.swift in Sources */, 3A1CF2F82A50F0A5000A8289 /* GroupItemView.swift in Sources */, 3A4EAD2F2A4FEB77005435B3 /* EditProfileContentView.swift in Sources */, 3A4EAD272A4FEB65005435B3 /* DashboardView.swift in Sources */, @@ -1468,8 +1504,8 @@ 3A4EAD362A4FEB9C005435B3 /* ProfileUpdateTask.swift in Sources */, 3A1CF2F62A50EE9C000A8289 /* GroupView.swift in Sources */, 3A4EAD212A4FEB3C005435B3 /* ApplicationLibrary.swift in Sources */, + 3A7904502B6E7BAC006C08D5 /* SponsorView.swift in Sources */, 3A1CF2F22A50E613000A8289 /* OutboundGroup.swift in Sources */, - 3A4EAD2C2A4FEB77005435B3 /* EditProfileWindowView.swift in Sources */, 3A1CF2FA2A50F0BD000A8289 /* OutboundGroupItem.swift in Sources */, 3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */, 3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */, @@ -1480,6 +1516,7 @@ 3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */, 3AC5EC082A6417470077AF34 /* DeviceCensorship.swift in Sources */, 3A0C6D3E2A79D6A600A4DF2B /* DashboardPage.swift in Sources */, + 3A3AB2A92B70C5F1001815AE /* RequestReviewButton.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1547,6 +1584,7 @@ 3A648D2D2A4EEAA600D95A12 /* Library.swift in Sources */, 3AEC21452A45A93800A63465 /* HTTPClient.swift in Sources */, 3AEC213C2A459FDF00A63465 /* Databse.swift in Sources */, + 3A60CC292B70A7C400D2D682 /* Color+Extension.swift in Sources */, 3A4EAD3C2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift in Sources */, 3ADBB4252A7389640041D44F /* ProfileServer.swift in Sources */, 3AEC21422A45A8FF00A63465 /* Profile+Update.swift in Sources */, @@ -1974,7 +2012,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_NAME = "sing-box"; SDKROOT = appletvos; @@ -2008,7 +2046,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_NAME = "sing-box"; SDKROOT = appletvos; @@ -2246,7 +2284,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0; OTHER_CODE_SIGN_FLAGS = "--deep"; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_NAME = "sing-box"; @@ -2286,7 +2324,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0; OTHER_CODE_SIGN_FLAGS = "--deep"; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_NAME = "sing-box"; @@ -2309,7 +2347,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 192; + CURRENT_PROJECT_VERSION = 195; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z56Z6NYZN2; ENABLE_HARDENED_RUNTIME = YES; @@ -2325,7 +2363,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0; OTHER_CODE_SIGN_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_NAME = "sing-box"; @@ -2347,7 +2385,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 192; + CURRENT_PROJECT_VERSION = 195; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z56Z6NYZN2; ENABLE_HARDENED_RUNTIME = YES; @@ -2363,7 +2401,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0; OTHER_CODE_SIGN_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_NAME = "sing-box"; @@ -2505,7 +2543,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0-alpha.1; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system; PRODUCT_NAME = "$(inherited)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2541,7 +2579,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0-alpha.1; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system; PRODUCT_NAME = "$(inherited)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2581,7 +2619,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0-alpha.1; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent; PRODUCT_NAME = SFM; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2620,7 +2658,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.8.4; + MARKETING_VERSION = 1.9.0-alpha.1; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent; PRODUCT_NAME = SFM; PROVISIONING_PROFILE_SPECIFIER = "";