Improve UI

This commit is contained in:
世界
2024-02-08 10:37:37 +08:00
parent 70d43abd52
commit 1f6e99f576
32 changed files with 1201 additions and 646 deletions
@@ -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
}
@@ -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
}
@@ -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<Label: View>: 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<Label: View>: View {
private let label: () -> Label
init(label: @escaping () -> Label) {
self.label = label
}
var body: some View {
Button(action: {
SKStoreReviewController.requestReview()
}, label: label)
}
}
#endif
@@ -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
@@ -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)
}
@@ -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)
}
@@ -17,12 +17,15 @@ public enum NavigationPage: Int, CaseIterable, Identifiable {
}
public extension NavigationPage {
#if os(macOS)
static var macosDefaultPages: [NavigationPage] {
[.logs, .profiles, .settings]
}
#endif
var label: some View {
Label(title, systemImage: iconImage)
.tint(.textColor)
}
var title: String {
@@ -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
@@ -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)
#if os(iOS) || os(macOS)
NavigationLink {
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
} label: {
Text("Edit Content").foregroundColor(.accentColor)
Label("Edit Content", systemImage: "pencil")
.foregroundColor(.accentColor)
}
#endif
} else {
#if os(iOS)
#if os(iOS) || os(macOS)
NavigationLink {
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
} label: {
Text("View Content").foregroundColor(.accentColor)
Label("View Content", systemImage: "doc.fill")
.foregroundColor(.accentColor)
}
#endif
Button("Update") {
FormButton {
isLoading = true
Task {
await updateProfile()
}
} label: {
Label("Update", systemImage: "arrow.clockwise")
}
.foregroundColor(.accentColor)
.disabled(isLoading)
}
Button("Delete", role: .destructive) {
FormButton(role: .destructive) {
Task {
await deleteProfile()
}
} label: {
Label("Delete", systemImage: "trash.fill")
}
.foregroundColor(.red)
}
#endif
}
.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)
@@ -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
@@ -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()
@@ -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,7 +37,6 @@ public struct ProfileView: View {
}
}
} else {
#if os(iOS) || os(tvOS)
ZStack {
if let importRemoteProfileRequest {
NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) {
@@ -54,6 +51,12 @@ public struct ProfileView: View {
Text("New Profile").foregroundColor(.accentColor)
}
.disabled(editMode.isEditing)
#elseif os(macOS)
NavigationLink {
NewProfileView()
} label: {
Text("New Profile")
}
#elseif os(tvOS)
Section {
NavigationLink {
@@ -78,34 +81,23 @@ public struct ProfileView: View {
List {
ForEach(profileList, id: \.id) { profile in
viewBuilder {
#if os(iOS) || os(tvOS)
if editMode.isEditing == true {
Text(profile.name)
} else {
ProfileItem(self, profile)
}
}
}
.onMove(perform: moveProfile)
.onDelete(perform: deleteProfile)
}
}
}
}
#elseif os(macOS)
if profileList.isEmpty {
Text("Empty profiles")
} else {
FormView {
List {
ForEach(profileList, id: \.id) { profile in
#else
ProfileItem(self, profile)
#endif
}
}
.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
},
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,9 +324,13 @@ public struct ProfileView: View {
}
} label: {
Label("Delete", systemImage: "trash.fill")
.foregroundColor(.red)
}
}
#else
NavigationLink {
EditProfileView().environmentObject(profile.origin)
} label: {
HStack {
VStack(alignment: .leading) {
Text(profile.name)
@@ -368,11 +350,14 @@ public struct ProfileView: View {
} label: {
Image(systemName: "arrow.clockwise")
}
.padding(.leading, 4)
Button {
shareLinkPresented = true
} label: {
Image(systemName: "qrcode")
}
.padding(.leading, 4)
.popover(isPresented: $shareLinkPresented, arrowEdge: .bottom) {
shareLinkView
}
@@ -380,11 +365,7 @@ public struct ProfileView: View {
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")
}
.padding(.leading, 4)
Button {
Task {
await parent.deleteProfile(profile.origin)
@@ -392,11 +373,14 @@ public struct ProfileView: View {
} label: {
Image(systemName: "trash.fill")
}
.padding([.leading, .trailing], 4)
}
.buttonStyle(.plain)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.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
@@ -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 = "<redacted>"
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
}
}
@@ -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
@@ -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 doesnt 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
}
}
@@ -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 `*_<route_address/route_exclude_address>` 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
}
}
@@ -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
}
+101 -250
View File
@@ -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
@State private var isLoading = true
private enum Tabs: Int, CaseIterable, Identifiable {
public var id: Self {
self
}
#if os(macOS)
@State private var startAtLogin = false
@Environment(\.showMenuBarExtra) private var showMenuBarExtra
@State private var keepMenuBarInBackground = false
case app
#endif
@State private var alwaysOn = false
@State private var disableMemoryLimit = false
case core, packetTunnel, profileOverride, sponsor
#if !os(tvOS)
@State private var includeAllNetworks = false
var label: some View {
Label(title, systemImage: iconImage)
}
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()
}
}
} 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)
}
}
}
}
Tabs.app.navigationLink
#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)
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")
}
.buttonStyle(.plain)
.foregroundColor(.accentColor)
#if !os(tvOS)
Toggle("Include All Networks", isOn: $includeAllNetworks)
.onChangeCompat(of: includeAllNetworks) { newValue in
Task {
await SharedPreferences.includeAllNetworks.set(newValue)
}
RequestReviewButton {
Label("Rate on the App Store", systemImage: "text.bubble.fill")
}
#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
}
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()
}
}
.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
Tabs.sponsor.navigationLink
}
Section("Debug") {
FormTextItem("Taiwan Flag Available", taiwanFlagAvailable.description)
NavigationLink {
ServiceLogView()
} label: {
Label("Service Log", systemImage: "doc.on.clipboard")
}
}
}
}
.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 = "<redacted>"
dataSize = LibboxFormatBytes(1000 * 1000 * 10)
taiwanFlagAvailable = true
isLoading = false
} else {
version = LibboxVersion()
dataSize = "Loading..."
FormTextItem("Taiwan Flag Available", "touchid") {
if isLoading {
Text("Loading...")
.onAppear {
Task.detached {
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
} else {
Text(taiwanFlagAvailable.description)
}
}
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")
}
}
@@ -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 Ive 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
}
}
}
@@ -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
@@ -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 {
+50 -5
View File
@@ -4,25 +4,54 @@ public enum SharedPreferences {
public static let selectedProfileID = Preference<Int64>("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<Bool>("disable_memory_limit", defaultValue: disableMemoryLimitByDefault)
public static let alwaysOn = Preference<Bool>("always_on", defaultValue: false)
public static let ignoreMemoryLimit = Preference<Bool>("ignore_memory_limit", defaultValue: ignoreMemoryLimitByDefault)
public static let ignoreDeviceSleep = Preference<Bool>("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<Bool>("include_all_networks", defaultValue: false)
public static let excludeAPNs = Preference<Bool>("exclude_apns", defaultValue: true)
public static let excludeLocalNetworks = Preference<Bool>("exclude_local_networks", defaultValue: excludeLocalNetworksByDefault)
public static let excludeCellularServices = Preference<Bool>("exclude_celluar_services", defaultValue: true)
public static let enforceRoutes = Preference<Bool>("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<Int>("max_log_lines", defaultValue: 300)
public static let alwaysOn = Preference<Bool>("always_on", defaultValue: false)
public static let ignoreDeviceSleep = Preference<Bool>("ignore_device_sleep", defaultValue: false)
#if os(macOS)
public static let showMenuBarExtra = Preference<Bool>("show_menu_bar_extra", defaultValue: true)
public static let menuBarExtraInBackground = Preference<Bool>("menu_bar_extra_in_background", defaultValue: false)
public static let startedByUser = Preference<Bool>("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<Bool>("system_proxy_enabled", defaultValue: true)
// Profile Override
public static let excludeDefaultRoute = Preference<Bool>("exclude_default_route", defaultValue: false)
public static let autoRouteUseSubRangesByDefault = Preference<Bool>("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
}
@@ -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() {}
@@ -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())
}
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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()
+25
View File
@@ -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
}
}
+2 -2
View File
@@ -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 {
+6 -19
View File
@@ -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)
+5
View File
@@ -26,9 +26,11 @@ public struct MainView: View {
NavigationSplitView {
SidebarView()
} detail: {
NavigationStack {
selection.contentView
.navigationTitle(selection.title)
}
}
.onAppear {
environments.postReload()
#if !DEBUG
@@ -55,6 +57,9 @@ public struct MainView: View {
environments.connectLog()
}
}
.onReceive(environments.openSettings) {
selection = .settings
}
.formStyle(.grouped)
.environment(\.selection, $selection)
.environment(\.importProfile, $importProfile)
+7 -1
View File
@@ -25,10 +25,13 @@ public struct SidebarView: View {
var body: some View {
VStack {
viewBuilder {
if extensionProfile.status.isConnectedStrict {
List(selection: selection) {
Section(NavigationPage.dashboard.title) {
Label("Overview", systemImage: "text.and.command.macwindow").tag(NavigationPage.dashboard)
Label("Overview", systemImage: "text.and.command.macwindow")
.tint(.textColor)
.tag(NavigationPage.dashboard)
NavigationPage.groups.label.tag(NavigationPage.groups)
}
Divider()
@@ -44,6 +47,9 @@ public struct SidebarView: View {
}
}
}
.listStyle(.sidebar)
.scrollDisabled(true)
}
.onChangeCompat(of: extensionProfile.status) {
if !selection.wrappedValue.visible(extensionProfile) {
selection.wrappedValue = NavigationPage.dashboard
+62 -24
View File
@@ -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 = "<group>"; };
3A27D9012A89C6870031EBCC /* ExtensionEnvironments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionEnvironments.swift; sourceTree = "<group>"; };
3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = "<group>"; };
3A3AB2A62B70C146001815AE /* CoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreView.swift; sourceTree = "<group>"; };
3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestReviewButton.swift; sourceTree = "<group>"; };
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 = "<group>"; };
3A44BB662A4DBF7900E4C9F8 /* SFI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SFI.entitlements; sourceTree = "<group>"; };
3A44BB802A4DC25E00E4C9F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
3A44BB812A4DC28700E4C9F8 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
@@ -463,6 +476,9 @@
3A57DF362A4D5D2600690BC5 /* Profile+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Date.swift"; sourceTree = "<group>"; };
3A57DF3F2A4D70B600690BC5 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
3A57DF412A4D927A00690BC5 /* Profile+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Hashable.swift"; sourceTree = "<group>"; };
3A60CC262B70880100D2D682 /* PacketTunnelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelView.swift; sourceTree = "<group>"; };
3A60CC282B70A7C400D2D682 /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = "<group>"; };
3A60CC2A2B70AD6700D2D682 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = "<group>"; };
3A648D2C2A4EEAA600D95A12 /* Library.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Library.swift; sourceTree = "<group>"; };
3A6CA5A52A713AA10027933B /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; };
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 = "<group>"; };
3A7701732A4E6B34008F031F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3A7701802A4E71F5008F031F /* IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IntentsExtension.entitlements; sourceTree = "<group>"; };
3A79044F2B6E7BAC006C08D5 /* SponsorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorView.swift; sourceTree = "<group>"; };
3A7E90302A46745A00D53052 /* ViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewBuilder.swift; sourceTree = "<group>"; };
3A7E90342A46756300D53052 /* SharedPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedPreferences.swift; sourceTree = "<group>"; };
3A9144D82A46AE370036E9AD /* ShadredPreferences+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShadredPreferences+Database.swift"; sourceTree = "<group>"; };
@@ -477,7 +494,6 @@
3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCompat.swift; sourceTree = "<group>"; };
3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationDestinationCompat.swift; sourceTree = "<group>"; };
3AA1ABB92A4C4054000FD4BA /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogView.swift; sourceTree = "<group>"; };
3AAB5E712A4BF6F6009757F1 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = "<group>"; };
3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLogView.swift; sourceTree = "<group>"; };
3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileContentView.swift; sourceTree = "<group>"; };
3AAB5E7A2A4C1446009757F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -502,7 +518,6 @@
3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = "<group>"; };
3ADF8DF62A4AFB2C00900CC8 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
3ADF8DF82A4AFCB400900CC8 /* NewProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileView.swift; sourceTree = "<group>"; };
3ADF8DFC2A4B096000900CC8 /* EditProfileWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileWindowView.swift; sourceTree = "<group>"; };
3ADF8E002A4B0F6300900CC8 /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = "<group>"; };
3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Unwrap.swift"; sourceTree = "<group>"; };
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 = "<group>"; };
3AF342D02A4AACC4002B34AC /* ExtensionStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionStatusView.swift; sourceTree = "<group>"; };
3AF342D32A4AADB2002B34AC /* Formtem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formtem.swift; sourceTree = "<group>"; };
3AF342D32A4AADB2002B34AC /* FormItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormItem.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>";
@@ -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 = "<group>";
@@ -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 = "<group>";
@@ -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 = "";