From 5e0573d15c654f707bf949f8ab19c99c2c6777c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 2 Aug 2023 13:16:24 +0800 Subject: [PATCH] Improve dashboard --- .../Assets.xcassets/Contents.json | 6 +- .../MenuIcon.symbolset/Contents.json | 15 ++ .../MenuIcon.symbolset/singbox-menubar.svg | 194 ++++++++++++++++++ .../Abstract/NavigationStackCompat.swift | 2 +- .../Views/Dashboard/ActiveDashboardView.swift | 146 ++++--------- .../Views/Dashboard/DashboardPage.swift | 43 ++++ .../Views/Dashboard/DashboardView.swift | 1 - .../Views/Dashboard/ExtensionStatusView.swift | 106 +++++++--- .../Views/Dashboard/OverviewView.swift | 87 ++++++++ .../Views/Groups/GroupItemView.swift | 80 ++++---- .../Views/Groups/GroupListView.swift | 4 +- .../Views/Groups/GroupView.swift | 36 ++-- ApplicationLibrary/Views/Log/LogView.swift | 3 +- ApplicationLibrary/Views/NavigationPage.swift | 49 +++-- .../Views/Profile/ProfileView.swift | 1 - .../Views/Setting/SettingView.swift | 1 - MacLibrary/MacApplication.swift | 2 +- MacLibrary/MainView.swift | 7 +- MacLibrary/MenuView.swift | 6 +- MacLibrary/SidebarView.swift | 30 ++- SFI/MainView.swift | 11 +- SFT/MainView.swift | 12 +- sing-box.xcodeproj/project.pbxproj | 28 +-- .../xcschemes/xcschememanagement.plist | 6 +- 24 files changed, 619 insertions(+), 257 deletions(-) create mode 100644 ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/Contents.json create mode 100644 ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/singbox-menubar.svg create mode 100644 ApplicationLibrary/Views/Dashboard/DashboardPage.swift create mode 100644 ApplicationLibrary/Views/Dashboard/OverviewView.swift diff --git a/ApplicationLibrary/Assets.xcassets/Contents.json b/ApplicationLibrary/Assets.xcassets/Contents.json index 74d6a72..73c0059 100644 --- a/ApplicationLibrary/Assets.xcassets/Contents.json +++ b/ApplicationLibrary/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { - "info": { - "author": "xcode", - "version": 1 + "info" : { + "author" : "xcode", + "version" : 1 } } diff --git a/ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/Contents.json b/ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/Contents.json new file mode 100644 index 0000000..ce9c51a --- /dev/null +++ b/ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/Contents.json @@ -0,0 +1,15 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "symbol-rendering-intent" : "template" + }, + "symbols" : [ + { + "filename" : "singbox-menubar.svg", + "idiom" : "universal" + } + ] +} diff --git a/ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/singbox-menubar.svg b/ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/singbox-menubar.svg new file mode 100644 index 0000000..af59e8f --- /dev/null +++ b/ApplicationLibrary/Assets.xcassets/MenuIcon.symbolset/singbox-menubar.svg @@ -0,0 +1,194 @@ + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from custom.shippingbox + Typeset at 100 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift b/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift index aed7cb8..3822d5b 100644 --- a/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift +++ b/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift @@ -1,6 +1,6 @@ import SwiftUI -public func NavigationStackCompat(@ViewBuilder content: () -> T) -> some View where T: View{ +public func NavigationStackCompat(@ViewBuilder content: () -> some View) -> some View { viewBuilder { if #available(iOS 17.0, *) { // view not updating in iOS 16, but why? diff --git a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift index 69d8d08..76bb1be 100644 --- a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift @@ -4,82 +4,53 @@ import Library import SwiftUI public struct ActiveDashboardView: View { - public static let NotificationUpdateSelectedProfile = Notification.Name("update-selected-profile") - @Environment(\.scenePhase) var scenePhase - @Environment(\.selection) private var selection - + @Environment(\.selection) private var parentSelection @EnvironmentObject private var profile: ExtensionProfile - @State private var isLoading = true @State private var profileList: [Profile] = [] @State private var selectedProfileID: Int64! - @State private var reasserting = false - @State private var observer: Any? @State private var alert: Alert? + @State private var selection = DashboardPage.overview public init() {} - public var body: some View { - viewBuilder { - if isLoading { - ProgressView().onAppear { - Task.detached { - await doReload() - } - } - } else { - if profileList.isEmpty { - Text("Empty profiles") - } else { - VStack { - #if os(iOS) || os(tvOS) - if ApplicationLibrary.inPreview || profile.status.isConnected { - ExtensionStatusView() - .listStyle(.automatic) - #if os(iOS) - .navigationBarTitleDisplayMode(.inline) - #endif - } - FormView { - StartStopButton() - Section("Profile") { - Picker(selection: $selectedProfileID) { - ForEach(profileList, id: \.id) { profile in - Text(profile.name).tag(profile.id) - } - } label: {} - .pickerStyle(.inline) - } - } - #elseif os(macOS) - if ApplicationLibrary.inPreview || profile.status.isConnected { - ExtensionStatusView() - } - FormView { - Section("Profile") { - ForEach(profileList, id: \.id) { profile in - Picker(profile.name, selection: $selectedProfileID) { - Text("").tag(profile.id) - } - } - .pickerStyle(.radioGroup) - } - } - #endif - } - .onChangeCompat(of: selectedProfileID) { - reasserting = true - Task.detached { - await switchProfile(selectedProfileID!) - } - } - .disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting)) + if isLoading { + ProgressView().onAppear { + Task.detached { + await doReload() } } - } - .alertBinding($alert) - #if os(iOS) || os(tvOS) + } else { + viewBuilder { + #if os(iOS) || os(tvOS) + if ApplicationLibrary.inPreview || profile.status.isConnectedStrict { + Picker("Page", selection: $selection) { + ForEach(DashboardPage.allCases) { page in + page.label + } + } + .pickerStyle(.segmented) + #if os(iOS) + + .padding([.leading, .trailing]) + .navigationBarTitleDisplayMode(.inline) + #endif + TabView(selection: $selection) { + ForEach(DashboardPage.allCases) { page in + page.contentView($profileList, $selectedProfileID) + .tag(page) + } + } + .tabViewStyle(.page(indexDisplayMode: .always)) + } else { + OverviewView($profileList, $selectedProfileID) + } + #elseif os(macOS) + OverviewView($profileList, $selectedProfileID) + #endif + } + #if os(iOS) || os(tvOS) .onChangeCompat(of: scenePhase) { newValue in if newValue == .active { Task.detached { @@ -87,29 +58,16 @@ public struct ActiveDashboardView: View { } } } - .onChangeCompat(of: selection.wrappedValue) { newValue in + .onChangeCompat(of: parentSelection.wrappedValue) { newValue in if newValue == .dashboard { Task.detached { await doReload() } } } - #elseif os(macOS) - .onAppear { - if observer == nil { - observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in - Task.detached { - await doReload() - } - }) - } - } - .onDisappear { - if let observer { - NotificationCenter.default.removeObserver(observer) - } - } - #endif + #endif + .alertBinding($alert) + } } private func doReload() { @@ -143,28 +101,4 @@ public struct ActiveDashboardView: View { } } } - - private func switchProfile(_ newProfileID: Int64) { - SharedPreferences.selectedProfileID = newProfileID - NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil) - if profile.status.isConnected { - do { - try LibboxNewStandaloneCommandClient()!.serviceReload() - } catch { - alert = Alert(error) - } - } - reasserting = false - } - - private struct ServiceErrorReporter: View { - private let parent: ActiveDashboardView - init(_ parent: ActiveDashboardView) { - self.parent = parent - } - - var body: some View { - EmptyView() - } - } } diff --git a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift new file mode 100644 index 0000000..2d500fb --- /dev/null +++ b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift @@ -0,0 +1,43 @@ +import Foundation +import Library +import SwiftUI + +public enum DashboardPage: Int, CaseIterable, Identifiable { + public var id: Self { + self + } + + case overview + case groups +} + +public extension DashboardPage { + var title: String { + switch self { + case .overview: + return NSLocalizedString("Overview", comment: "") + case .groups: + return NSLocalizedString("Groups", comment: "") + } + } + + var label: some View { + switch self { + case .overview: + return Label("Overview", systemImage: "text.and.command.macwindow") + case .groups: + return Label("Groups", systemImage: "rectangle.3.group.fill") + } + } + + func contentView(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding) -> some View { + viewBuilder { + switch self { + case .overview: + OverviewView(profileList, selectedProfileID) + case .groups: + GroupListView() + } + } + } +} diff --git a/ApplicationLibrary/Views/Dashboard/DashboardView.swift b/ApplicationLibrary/Views/Dashboard/DashboardView.swift index 4a7bf17..74d1b86 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardView.swift @@ -40,7 +40,6 @@ public struct DashboardView: View { } } #endif - .navigationTitle("Dashboard") } #if os(macOS) diff --git a/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift b/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift index 4adc655..784d22b 100644 --- a/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift +++ b/ApplicationLibrary/Views/Dashboard/ExtensionStatusView.swift @@ -18,28 +18,50 @@ public struct ExtensionStatusView: View { VStack { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) { if ApplicationLibrary.inPreview { - StatusItem("Memory", "6.4 MiB") - StatusItem("Goroutines", "89") - StatusItem("Inbound Connections", "34") - StatusItem("Outbound Connections", "28") - StatusItem("Uplink", "38 B/s") - StatusItem("Downlink", "249 MiB/s") - StatusItem("Uplink Total", "52 MiB") - StatusItem("Downlink Total", "5.6 GiB") + StatusItem("Status") { + StatusLine("Memory", "6.4 MiB") + StatusLine("Goroutines", "89") + } + StatusItem("Connections") { + StatusLine("Inbound", "34") + StatusLine("Outbound", "28") + } + StatusItem("Traffic") { + StatusLine("Uplink", "38 B/s") + StatusLine("Downlink", "249 MiB/s") + } + StatusItem("TrafficTotal") { + StatusLine("Uplink", "52 MiB") + StatusLine("Downlink", "5.6 GiB") + } } else if let message { - StatusItem("Memory", LibboxFormatBytes(message.memory)) - StatusItem("Goroutines", "\(message.goroutines)") + StatusItem("Status") { + StatusLine("Memory", LibboxFormatBytes(message.memory)) + StatusLine("Goroutines", "\(message.goroutines)") + } + StatusItem("Connections") { + StatusLine("Inbound", "\(message.connectionsIn)") + StatusLine("Outbound", "\(message.connectionsOut)") + } if message.trafficAvailable { - StatusItem("Inbound Connections", "\(message.connectionsIn)") - StatusItem("Outbound Connections", "\(message.connectionsOut)") - StatusItem("Uplink", LibboxFormatBytes(message.uplink) + "/s") - StatusItem("Downlink", LibboxFormatBytes(message.downlink) + "/s") - StatusItem("Uplink Total", LibboxFormatBytes(message.uplinkTotal)) - StatusItem("Downlink Total", LibboxFormatBytes(message.downlinkTotal)) + StatusItem("Traffic") { + StatusLine("Uplink", "\(LibboxFormatBytes(message.uplink))/s") + StatusLine("Downlink", "\(LibboxFormatBytes(message.downlink))/s") + } + StatusItem("TrafficTotal") { + StatusLine("Uplink", LibboxFormatBytes(message.uplinkTotal)) + StatusLine("Downlink", LibboxFormatBytes(message.downlinkTotal)) + } } } else { - StatusItem("Memory", "Loading...") - StatusItem("Goroutines", "Loading...") + StatusItem("Status") { + StatusLine("Memory", "...") + StatusLine("Goroutines", "...") + } + StatusItem("Connections") { + StatusLine("Inbound", "...") + StatusLine("Outbound", "...") + } } }.background { GeometryReader { geometry in @@ -142,26 +164,25 @@ public struct ExtensionStatusView: View { func writeGroups(_: LibboxOutboundGroupIteratorProtocol?) {} } - private struct StatusItem: View { - private let name: String - private let value: String - init(_ name: String, _ value: String) { - self.name = name - self.value = value + private struct StatusItem: View where T: View { + private let title: String + @ViewBuilder private let content: () -> T + + init(_ title: String, @ViewBuilder content: @escaping () -> T) { + self.title = title + self.content = content } var body: some View { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading) { HStack { - Text(name) - .font(.subheadline) - .foregroundColor(.secondary) + Text(title) + .font(.headline) Spacer() - } - Text(value) - .font(.system(size: 16)) + }.padding(.bottom, 8) + content() } - .frame(minWidth: 125) + .frame(minWidth: 125, alignment: .topLeading) #if os(tvOS) .padding(EdgeInsets(top: 20, leading: 26, bottom: 20, trailing: 26)) #else @@ -181,4 +202,25 @@ public struct ExtensionStatusView: View { #endif } } + + private struct StatusLine: View { + private let name: String + private let value: String + + init(_ name: String, _ value: String) { + self.name = name + self.value = value + } + + var body: some View { + HStack { + Text(name) + .font(.subheadline) + .foregroundColor(.secondary) + Spacer() + Text(value) + .font(.subheadline) + } + } + } } diff --git a/ApplicationLibrary/Views/Dashboard/OverviewView.swift b/ApplicationLibrary/Views/Dashboard/OverviewView.swift new file mode 100644 index 0000000..dd8ca67 --- /dev/null +++ b/ApplicationLibrary/Views/Dashboard/OverviewView.swift @@ -0,0 +1,87 @@ +import Foundation +import Libbox +import Library +import SwiftUI + +public struct OverviewView: View { + public static let NotificationUpdateSelectedProfile = Notification.Name("update-selected-profile") + + @Environment(\.scenePhase) var scenePhase + @Environment(\.selection) private var selection + @EnvironmentObject private var profile: ExtensionProfile + @Binding private var profileList: [Profile] + @Binding private var selectedProfileID: Int64! + @State private var alert: Alert? + @State private var reasserting = false + @State private var observer: Any? + + public init(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding) { + _profileList = profileList + _selectedProfileID = selectedProfileID + } + + public var body: some View { + VStack { + if ApplicationLibrary.inPreview || profile.status.isConnected { + ExtensionStatusView() + } + FormView { + #if os(iOS) || os(tvOS) + StartStopButton() + Section("Profile") { + Picker(selection: $selectedProfileID) { + ForEach(profileList, id: \.id) { profile in + Text(profile.name).tag(profile.id) + } + } label: {} + .pickerStyle(.inline) + } + #elseif os(macOS) + Section("Profile") { + ForEach(profileList, id: \.id) { profile in + Picker(profile.name, selection: $selectedProfileID) { + Text("").tag(profile.id) + } + } + .pickerStyle(.radioGroup) + } + #endif + } + } + .alertBinding($alert) + .onChangeCompat(of: selectedProfileID) { + reasserting = true + Task.detached { + await switchProfile(selectedProfileID!) + } + } + .disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting)) + #if os(macOS) + .onAppear { + if observer == nil { + observer = NotificationCenter.default.addObserver(forName: OverviewView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { newProfileID in + selectedProfileID = newProfileID.object as! Int64 + }) + } + } + .onDisappear { + if let observer { + NotificationCenter.default.removeObserver(observer) + } + } + #endif + } + + private func switchProfile(_ newProfileID: Int64) { + SharedPreferences.selectedProfileID = newProfileID + NotificationCenter.default.post(name: OverviewView.NotificationUpdateSelectedProfile, object: newProfileID) + if profile.status.isConnected { + do { + try LibboxNewStandaloneCommandClient()!.serviceReload() + } catch { + alert = Alert(error) + } + } + reasserting = false + } +} diff --git a/ApplicationLibrary/Views/Groups/GroupItemView.swift b/ApplicationLibrary/Views/Groups/GroupItemView.swift index cb379d0..5a25b2d 100644 --- a/ApplicationLibrary/Views/Groups/GroupItemView.swift +++ b/ApplicationLibrary/Views/Groups/GroupItemView.swift @@ -17,48 +17,54 @@ public struct GroupItemView: View { @State private var alert: Alert? public var body: some View { - HStack { - if group.selected == item.tag { - Rectangle() - .fill(Color.accentColor) - .frame(width: 6) - } else { - Rectangle() - .fill(.clear) - .frame(width: 6) - } - VStack { - HStack { - Text(item.tag) - .truncationMode(.tail) - .lineLimit(1) - .font(.system(size: 14)) - Spacer(minLength: 6) - } - Spacer(minLength: 6) - HStack(alignment: .center) { - Text(item.type) - .foregroundColor(.secondary) - .font(.system(size: 12)) - Spacer(minLength: 6) - if item.urlTestDelay > 0 { - Text(item.delayString) - .foregroundColor(item.delayColor) - .font(.system(size: 11)) - } - } - } - .frame(height: 36) - .padding([.top, .bottom, .trailing], 12) - } - .background(backgroundColor) - .onTapGesture { + Button(action: { if group.selectable, group.selected != item.tag { Task.detached { selectOutbound() } } - } + }, label: { + HStack { + VStack { + HStack { + Text(item.tag) + .font(.caption) + .foregroundStyle(.foreground) + .lineLimit(1) + .truncationMode(.tail) + Spacer(minLength: 0) + + } + Spacer(minLength: 8) + HStack { + Text(item.type) + .font(.caption) + .foregroundColor(.secondary) + Spacer(minLength: 0) + + } + } + Spacer(minLength: 0) + VStack { + if group.selected == item.tag { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(Color.accentColor) + } + Spacer(minLength: 0) + if item.urlTestDelay > 0 { + Text(item.delayString) + .font(.caption) + .foregroundColor(item.delayColor) + } + } + } + }) + #if !os(tvOS) + .buttonStyle(.borderless) + .padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13)) + .background(backgroundColor) + .cornerRadius(10) + #endif .alertBinding($alert) } diff --git a/ApplicationLibrary/Views/Groups/GroupListView.swift b/ApplicationLibrary/Views/Groups/GroupListView.swift index 7d0ed44..09937c8 100644 --- a/ApplicationLibrary/Views/Groups/GroupListView.swift +++ b/ApplicationLibrary/Views/Groups/GroupListView.swift @@ -23,7 +23,6 @@ public struct GroupListView: View { }, set: { newValue in groupExpand[it.tag] = newValue })) - Spacer() } }.padding() } @@ -40,7 +39,6 @@ public struct GroupListView: View { } commandClient = nil } - .navigationTitle("Groups") } private func doReload() { @@ -51,7 +49,7 @@ public struct GroupListView: View { OutboundGroupItem(tag: "server2", type: "WireGuard", urlTestTime: .now, urlTestDelay: 34), OutboundGroupItem(tag: "auto", type: "URLTest", urlTestTime: .now, urlTestDelay: 100), ]), - OutboundGroup(tag: "group2", type: "urltest", selected: "client", selectable: false, items: + OutboundGroup(tag: "group2", type: "urltest", selected: "client", selectable: true, items: (0 ..< 234).map { index in OutboundGroupItem(tag: "client\(index)", type: "Shadowsocks", urlTestTime: .now, urlTestDelay: UInt16(100 + index * 10)) }), diff --git a/ApplicationLibrary/Views/Groups/GroupView.swift b/ApplicationLibrary/Views/Groups/GroupView.swift index c3b68a5..9da8e40 100644 --- a/ApplicationLibrary/Views/Groups/GroupView.swift +++ b/ApplicationLibrary/Views/Groups/GroupView.swift @@ -15,12 +15,12 @@ public struct GroupView: View { private var title: some View { HStack { Text(group.tag) - .font(.system(size: 17)) + .font(.headline) Text(group.displayType) - .font(.system(size: 13)) + .font(.subheadline) .foregroundColor(.secondary) Text("\(group.items.count)") - .font(.system(size: 11)) + .font(.subheadline) .padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)) .background(Color.gray.opacity(0.5)) .cornerRadius(4) @@ -33,7 +33,7 @@ public struct GroupView: View { Image(systemName: "arrow.up.to.line") } } - #if os(macOS) + #if os(macOS) || os(tvOS) .buttonStyle(.plain) #endif Button { @@ -43,11 +43,10 @@ public struct GroupView: View { } label: { Image(systemName: "bolt.fill") } - #if os(macOS) + #if os(macOS) || os(tvOS) .buttonStyle(.plain) #endif - Spacer(minLength: 6) - } + }.padding([.top, .bottom], 8) } public var body: some View { @@ -61,13 +60,17 @@ public struct GroupView: View { } } } else { - VStack { + VStack(spacing: 5) { ForEach(Array(itemGroups.enumerated()), id: \.offset) { items in - HStack { + HStack(spacing: 5) { ForEach(items.element, id: \.tag) { it in Rectangle() .fill(it.delayColor) + #if !os(tvOS) .frame(width: 10, height: 10) + #else + .frame(width: 30, height: 30) + #endif } }.frame(maxWidth: .infinity, alignment: .topLeading) } @@ -93,7 +96,12 @@ public struct GroupView: View { } private var itemGroups: [[OutboundGroupItem]] { - let count = Int(Int(geometryWidth) / 20) + let count: Int + #if os(tvOS) + count = Int(Int(geometryWidth) / 40) + #else + count = Int(Int(geometryWidth) / 20) + #endif if count == 0 { return [group.items] } else { @@ -104,11 +112,13 @@ public struct GroupView: View { } private func explandColumnCount() -> Int { - let count = Int(Int(geometryWidth) / 180) + let standardCount = Int(Int(geometryWidth) / 180) #if os(iOS) - return count < 2 ? 2 : count + return standardCount < 2 ? 2 : standardCount + #elseif os(tvOS) + return Int(Int(geometryWidth) / 400) #else - return count < 1 ? 1 : count + return standardCount < 1 ? 1 : standardCount #endif } diff --git a/ApplicationLibrary/Views/Log/LogView.swift b/ApplicationLibrary/Views/Log/LogView.swift index a62e502..ffb99af 100644 --- a/ApplicationLibrary/Views/Log/LogView.swift +++ b/ApplicationLibrary/Views/Log/LogView.swift @@ -13,7 +13,6 @@ public struct LogView: View { Text("Service not started") } } - .navigationTitle("Logs") } private struct LogView0: View { @@ -43,7 +42,7 @@ public struct LogView: View { #if os(tvOS) .focusable() #endif - Spacer(minLength: 5) + Spacer(minLength: 8) } .onChangeCompat(of: logClient.logList.count) { newCount in diff --git a/ApplicationLibrary/Views/NavigationPage.swift b/ApplicationLibrary/Views/NavigationPage.swift index 7f4e8f5..8ee0f5f 100644 --- a/ApplicationLibrary/Views/NavigationPage.swift +++ b/ApplicationLibrary/Views/NavigationPage.swift @@ -8,13 +8,19 @@ public enum NavigationPage: Int, CaseIterable, Identifiable { } case dashboard - case groups + #if os(macOS) + case groups + #endif case logs case profiles case settings } public extension NavigationPage { + static var macosDefaultPages: [NavigationPage] { + [.logs, .profiles, .settings] + } + var label: some View { Label(title, systemImage: iconImage) } @@ -23,8 +29,10 @@ public extension NavigationPage { switch self { case .dashboard: return NSLocalizedString("Dashboard", comment: "") - case .groups: - return NSLocalizedString("Groups", comment: "") + #if os(macOS) + case .groups: + return NSLocalizedString("Groups", comment: "") + #endif case .logs: return NSLocalizedString("Logs", comment: "") case .profiles: @@ -38,8 +46,10 @@ public extension NavigationPage { switch self { case .dashboard: return "text.and.command.macwindow" - case .groups: - return "rectangle.3.group.fill" + #if os(macOS) + case .groups: + return "rectangle.3.group.fill" + #endif case .logs: return "doc.text.fill" case .profiles: @@ -54,9 +64,10 @@ public extension NavigationPage { switch self { case .dashboard: DashboardView() - - case .groups: - GroupListView() + #if os(macOS) + case .groups: + GroupListView() + #endif case .logs: LogView() case .profiles: @@ -70,20 +81,14 @@ public extension NavigationPage { #endif } - func visible(_ profile: ExtensionProfile?) -> Bool { - switch self { - case .groups: - #if os(tvOS) - // TODO: fix groups ui - return false - #else - if ApplicationLibrary.inPreview { - return true - } + #if os(macOS) + func visible(_ profile: ExtensionProfile?) -> Bool { + switch self { + case .groups: return profile?.status.isConnectedStrict == true - #endif - default: - return true + default: + return true + } } - } + #endif } diff --git a/ApplicationLibrary/Views/Profile/ProfileView.swift b/ApplicationLibrary/Views/Profile/ProfileView.swift index 552bfb8..5de8b99 100644 --- a/ApplicationLibrary/Views/Profile/ProfileView.swift +++ b/ApplicationLibrary/Views/Profile/ProfileView.swift @@ -115,7 +115,6 @@ public struct ProfileView: View { } } .disabled(isUpdating) - .navigationTitle("Profiles") .alertBinding($alert, $isLoading) .onAppear { if let profile = importProfile.wrappedValue { diff --git a/ApplicationLibrary/Views/Setting/SettingView.swift b/ApplicationLibrary/Views/Setting/SettingView.swift index 95806d5..edcce66 100644 --- a/ApplicationLibrary/Views/Setting/SettingView.swift +++ b/ApplicationLibrary/Views/Setting/SettingView.swift @@ -129,7 +129,6 @@ public struct SettingView: View { } } } - .navigationTitle("Settings") .alertBinding($alert) } diff --git a/MacLibrary/MacApplication.swift b/MacLibrary/MacApplication.swift index bdc5c4d..6de43e6 100644 --- a/MacLibrary/MacApplication.swift +++ b/MacLibrary/MacApplication.swift @@ -53,7 +53,7 @@ public struct MacApplication: Scene { MenuBarExtra(isInserted: $showMenuBarExtra) { MenuView(isMenuPresented: $isMenuPresented) } label: { - Image(systemName: "network.badge.shield.half.filled") + Image("MenuIcon") } .menuBarExtraStyle(.window) .menuBarExtraAccess(isPresented: $isMenuPresented) diff --git a/MacLibrary/MainView.swift b/MacLibrary/MainView.swift index 97118f5..0530178 100644 --- a/MacLibrary/MainView.swift +++ b/MacLibrary/MainView.swift @@ -17,11 +17,7 @@ public struct MainView: View { public init() {} public var body: some View { NavigationSplitView { - VStack { - SidebarView() - } - - .frame(minWidth: 150) + SidebarView() } detail: { if profileLoading { ProgressView().onAppear { @@ -32,6 +28,7 @@ public struct MainView: View { } } else { selection.contentView + .navigationTitle(selection.title) } } #if !DEBUG diff --git a/MacLibrary/MenuView.swift b/MacLibrary/MenuView.swift index 9f09b12..63b8fcd 100644 --- a/MacLibrary/MenuView.swift +++ b/MacLibrary/MenuView.swift @@ -149,8 +149,8 @@ public struct MenuView: View { } .onAppear { if observer == nil { - observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in - doReload() + observer = NotificationCenter.default.addObserver(forName: OverviewView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { notification in + selectedProfileID = notification.object as! Int64 }) } } @@ -188,7 +188,7 @@ public struct MenuView: View { private func switchProfile(_ newProfileID: Int64) { SharedPreferences.selectedProfileID = newProfileID - NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil) + NotificationCenter.default.post(name: OverviewView.NotificationUpdateSelectedProfile, object: newProfileID) if profile.status.isConnected { do { try LibboxNewStandaloneCommandClient()?.serviceReload() diff --git a/MacLibrary/SidebarView.swift b/MacLibrary/SidebarView.swift index 4596dde..262ca42 100644 --- a/MacLibrary/SidebarView.swift +++ b/MacLibrary/SidebarView.swift @@ -8,13 +8,13 @@ public struct SidebarView: View { public init() {} public var body: some View { - viewBuilder { + VStack { if let profile = extensionProfile.wrappedValue { SidebarView0().environmentObject(profile) } else { SidebarView1() } - } + }.frame(minWidth: 150) } struct SidebarView0: View { @@ -22,11 +22,27 @@ public struct SidebarView: View { @EnvironmentObject private var extensionProfile: ExtensionProfile var body: some View { - List(NavigationPage.allCases.filter { it in - it.visible(extensionProfile) - }, selection: selection) { it in - it.label - }.onChangeCompat(of: extensionProfile.status) { + 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) + } + Divider() + ForEach(NavigationPage.macosDefaultPages, id: \.self) { it in + it.label + } + } + } else { + List(NavigationPage.allCases.filter { it in + it.visible(extensionProfile) + }, selection: selection) { it in + it.label + } + } + } + .onChangeCompat(of: extensionProfile.status) { if !selection.wrappedValue.visible(extensionProfile) { selection.wrappedValue = NavigationPage.dashboard } diff --git a/SFI/MainView.swift b/SFI/MainView.swift index e66a2ee..c844c0b 100644 --- a/SFI/MainView.swift +++ b/SFI/MainView.swift @@ -24,7 +24,16 @@ struct MainView: View { } } } else { - ContentView() + TabView(selection: $selection) { + ForEach(NavigationPage.allCases, id: \.self) { page in + NavigationStackCompat { + page.contentView + .navigationTitle(page.title) + } + .tag(page) + .tabItem { page.label } + } + } } } .alertBinding($alert) diff --git a/SFT/MainView.swift b/SFT/MainView.swift index 73df2cb..35bb820 100644 --- a/SFT/MainView.swift +++ b/SFT/MainView.swift @@ -22,7 +22,17 @@ struct MainView: View { } } } else { - ContentView() + TabView(selection: $selection) { + ForEach(NavigationPage.allCases, id: \.self) { page in + NavigationStackCompat { + page.contentView + .navigationTitle(page.title) + .focusSection() + } + .tag(page) + .tabItem { page.label } + } + } } } .onChangeCompat(of: scenePhase) { newValue in diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index 7677432..25a7f94 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 3A017F922A4AB2E4009149FA /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 3A017F912A4AB2E4009149FA /* GRDB */; }; 3A096F8F2A4ED3DE00D4A2ED /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */; }; + 3A0C6D3E2A79D6A600A4DF2B /* DashboardPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */; }; 3A1CF2F02A50E5EE000A8289 /* GroupListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2EF2A50E5EE000A8289 /* GroupListView.swift */; }; 3A1CF2F22A50E613000A8289 /* OutboundGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2F12A50E613000A8289 /* OutboundGroup.swift */; }; 3A1CF2F62A50EE9C000A8289 /* GroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1CF2F52A50EE9C000A8289 /* GroupView.swift */; }; @@ -72,7 +74,6 @@ 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, ); }; }; - 3A9108E42A511AE70088B196 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9108E32A511AE70088B196 /* ContentView.swift */; }; 3A9144D92A46AE370036E9AD /* ShadredPreferences+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9144D82A46AE370036E9AD /* ShadredPreferences+Database.swift */; }; 3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; 3A9759212A4EB69C00E4404B /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -81,7 +82,6 @@ 3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */; }; 3AB1220B2A70FD500087CD55 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB1220A2A70FD500087CD55 /* Alert.swift */; }; 3AC03B992A72BF3300B7946F /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC03B982A72BF3300B7946F /* Application.swift */; }; - 3AC03B9B2A72BF3300B7946F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC03B9A2A72BF3300B7946F /* ContentView.swift */; }; 3AC03B9D2A72BF3500B7946F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AC03B9C2A72BF3500B7946F /* Assets.xcassets */; }; 3AC194492A50013F00BD8CB9 /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3AC1944F2A50247300BD8CB9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */; }; @@ -413,6 +413,8 @@ 3A096F892A4ED3DE00D4A2ED /* ExtensionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionProvider.swift; sourceTree = ""; }; 3A096F8B2A4ED3DE00D4A2ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3A096F8C2A4ED3DE00D4A2ED /* Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Extension.entitlements; sourceTree = ""; }; + 3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = ""; }; + 3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPage.swift; sourceTree = ""; }; 3A1CF2EF2A50E5EE000A8289 /* GroupListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListView.swift; sourceTree = ""; }; 3A1CF2F12A50E613000A8289 /* OutboundGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundGroup.swift; sourceTree = ""; }; 3A1CF2F32A50E937000A8289 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; @@ -450,7 +452,6 @@ 3A7701802A4E71F5008F031F /* IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IntentsExtension.entitlements; 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 = ""; }; - 3A9108E32A511AE70088B196 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 3A9144D82A46AE370036E9AD /* ShadredPreferences+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShadredPreferences+Database.swift"; sourceTree = ""; }; 3A99B4292A7526990010D4B0 /* NavigationStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackCompat.swift; sourceTree = ""; }; 3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCompat.swift; sourceTree = ""; }; @@ -465,7 +466,6 @@ 3ABA46D22A6A32A100D8366B /* Messages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Messages.framework; path = Library/Frameworks/Messages.framework; sourceTree = DEVELOPER_DIR; }; 3AC03B962A72BF3300B7946F /* sing-box.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sing-box.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 3AC03B982A72BF3300B7946F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; - 3AC03B9A2A72BF3300B7946F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 3AC03B9C2A72BF3500B7946F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = ""; }; 3AC194512A50303300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = ""; }; @@ -707,7 +707,6 @@ children = ( 3A4FB1642A73568E007012B9 /* SFT.entitlements */, 3AC03B982A72BF3300B7946F /* Application.swift */, - 3AC03B9A2A72BF3300B7946F /* ContentView.swift */, 3AC03B9C2A72BF3500B7946F /* Assets.xcassets */, 3A4FB1652A73574B007012B9 /* Info.plist */, 3A4FB1672A7358C9007012B9 /* ApplicationDelegate.swift */, @@ -783,7 +782,6 @@ 3AEC20F92A459AB500A63465 /* Assets.xcassets */, 3A44BB812A4DC28700E4C9F8 /* MainView.swift */, 3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */, - 3A9108E32A511AE70088B196 /* ContentView.swift */, ); path = SFI; sourceTree = ""; @@ -925,6 +923,8 @@ 3AF342CC2A4AA88C002B34AC /* StartStopButton.swift */, 3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */, 3AE4D0C02A6E4852009FEA9E /* InstallSystemExtensionButton.swift */, + 3A0C6D3B2A79D46500A4DF2B /* OverviewView.swift */, + 3A0C6D3D2A79D6A600A4DF2B /* DashboardPage.swift */, ); path = Dashboard; sourceTree = ""; @@ -1384,10 +1384,12 @@ 3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */, 3A4EAD332A4FEB7F005435B3 /* LogClient.swift in Sources */, 3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */, + 3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */, 3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */, 3A4EAD252A4FEB65005435B3 /* StartStopButton.swift in Sources */, 3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */, 3AC5EC082A6417470077AF34 /* DeviceCensorship.swift in Sources */, + 3A0C6D3E2A79D6A600A4DF2B /* DashboardPage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1406,7 +1408,6 @@ files = ( 3A4FB1682A7358C9007012B9 /* ApplicationDelegate.swift in Sources */, 3A4FB16A2A735AC9007012B9 /* MainView.swift in Sources */, - 3AC03B9B2A72BF3300B7946F /* ContentView.swift in Sources */, 3AC03B992A72BF3300B7946F /* Application.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1417,7 +1418,6 @@ files = ( 3AC1944F2A50247300BD8CB9 /* ApplicationDelegate.swift in Sources */, 3AEC20F62A459AB400A63465 /* Application.swift in Sources */, - 3A9108E42A511AE70088B196 /* ContentView.swift in Sources */, 3A44BB822A4DC28700E4C9F8 /* MainView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2074,7 +2074,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.5; + MARKETING_VERSION = 1.3.6; OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_LDFLAGS = "-ld_classic"; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; @@ -2115,7 +2115,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.5; + MARKETING_VERSION = 1.3.6; OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_LDFLAGS = "-ld_classic"; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; @@ -2139,7 +2139,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 21; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z56Z6NYZN2; ENABLE_HARDENED_RUNTIME = YES; @@ -2155,7 +2155,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.3.5; + MARKETING_VERSION = 1.3.6; OTHER_CODE_SIGN_FLAGS = ""; OTHER_LDFLAGS = "-ld_classic"; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; @@ -2178,7 +2178,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 21; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = Z56Z6NYZN2; ENABLE_HARDENED_RUNTIME = YES; @@ -2194,7 +2194,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.3.5; + MARKETING_VERSION = 1.3.6; OTHER_CODE_SIGN_FLAGS = ""; OTHER_LDFLAGS = "-ld_classic"; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; diff --git a/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist b/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist index 4f58539..3cb7054 100644 --- a/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ ApplicationLibrary.xcscheme_^#shared#^_ orderHint - 8 + 2 Associations (Playground) 1.xcscheme @@ -141,7 +141,7 @@ SFT.xcscheme_^#shared#^_ orderHint - 7 + 5 ShareExtension.xcscheme_^#shared#^_ @@ -151,7 +151,7 @@ SystemExtension.xcscheme_^#shared#^_ orderHint - 5 + 4 Test.xcscheme_^#shared#^_