This commit is contained in:
世界
2023-10-02 22:12:33 +08:00
parent c1c4f084b9
commit c5a29e0956
16 changed files with 256 additions and 247 deletions
@@ -0,0 +1,12 @@
import Foundation
import SwiftUI
public extension Binding {
func withSetter(_ setter: @escaping (Value) -> Void) -> Binding<Value> {
Binding {
wrappedValue
} set: { [setter] newValue, _ in
setter(newValue)
}
}
}
@@ -7,10 +7,11 @@ import SwiftUI
public struct ActiveDashboardView: View { public struct ActiveDashboardView: View {
@Environment(\.scenePhase) var scenePhase @Environment(\.scenePhase) var scenePhase
@Environment(\.selection) private var parentSelection @Environment(\.selection) private var parentSelection
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile @EnvironmentObject private var profile: ExtensionProfile
@State private var isLoading = true @State private var isLoading = true
@State private var profileList: [Profile] = [] @State private var profileList: [ProfilePreview] = []
@State private var selectedProfileID: Int64! @State private var selectedProfileID: Int64 = 0
@State private var alert: Alert? @State private var alert: Alert?
@State private var selection = DashboardPage.overview @State private var selection = DashboardPage.overview
@State private var systemProxyAvailable = false @State private var systemProxyAvailable = false
@@ -73,22 +74,19 @@ public struct ActiveDashboardView: View {
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled) OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
#endif #endif
} }
#if os(iOS) || os(tvOS) .onReceive(environments.profileUpdate) { _ in
.onChangeCompat(of: scenePhase) { newValue in
if newValue == .active {
Task { Task {
await doReload() await doReload()
} }
} }
} .onReceive(environments.selectedProfileUpdate) { _ in
.onChangeCompat(of: parentSelection.wrappedValue) { newValue in
if newValue == .dashboard {
Task { Task {
await doReload() selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profile.status.isConnected {
await doReloadSystemProxy()
} }
} }
} }
#endif
.alertBinding($alert) .alertBinding($alert)
} }
@@ -98,8 +96,8 @@ public struct ActiveDashboardView: View {
} }
if ApplicationLibrary.inPreview { if ApplicationLibrary.inPreview {
profileList = [ profileList = [
Profile(id: 0, name: "profile local", type: .local, path: ""), ProfilePreview(Profile(id: 0, name: "profile local", type: .local, path: "")),
Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)), ProfilePreview(Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0))),
] ]
systemProxyAvailable = true systemProxyAvailable = true
systemProxyEnabled = true systemProxyEnabled = true
@@ -107,7 +105,7 @@ public struct ActiveDashboardView: View {
} else { } else {
do { do {
profileList = try await ProfileManager.list() profileList = try await ProfileManager.list().map { ProfilePreview($0) }
if profileList.isEmpty { if profileList.isEmpty {
return return
} }
@@ -116,7 +114,7 @@ public struct ActiveDashboardView: View {
profile.id == selectedProfileID profile.id == selectedProfileID
}) })
.isEmpty { .isEmpty {
selectedProfileID = profileList[0].id! selectedProfileID = profileList[0].id
await SharedPreferences.selectedProfileID.set(selectedProfileID) await SharedPreferences.selectedProfileID.set(selectedProfileID)
} }
@@ -31,7 +31,7 @@ public extension DashboardPage {
} }
@MainActor @MainActor
func contentView(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding<Int64?>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>) -> some View { func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>) -> some View {
viewBuilder { viewBuilder {
switch self { switch self {
case .overview: case .overview:
@@ -5,19 +5,26 @@ import SwiftUI
@MainActor @MainActor
public struct OverviewView: View { public struct OverviewView: View {
public static let NotificationUpdateSelectedProfile = Notification.Name("update-selected-profile")
@Environment(\.selection) private var selection @Environment(\.selection) private var selection
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile @EnvironmentObject private var profile: ExtensionProfile
@Binding private var profileList: [Profile] @Binding private var profileList: [ProfilePreview]
@Binding private var selectedProfileID: Int64! @Binding private var selectedProfileID: Int64
@Binding private var systemProxyAvailable: Bool @Binding private var systemProxyAvailable: Bool
@Binding private var systemProxyEnabled: Bool @Binding private var systemProxyEnabled: Bool
@State private var alert: Alert? @State private var alert: Alert?
@State private var reasserting = false @State private var reasserting = false
@State private var observer: Any?
public init(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding<Int64?>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>) { private var selectedProfileIDLocal: Binding<Int64> {
$selectedProfileID.withSetter { newValue in
reasserting = true
Task { [self] in
await switchProfile(newValue)
}
}
}
public init(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>) {
_profileList = profileList _profileList = profileList
_selectedProfileID = selectedProfileID _selectedProfileID = selectedProfileID
_systemProxyAvailable = systemProxyAvailable _systemProxyAvailable = systemProxyAvailable
@@ -45,7 +52,7 @@ public struct OverviewView: View {
} }
} }
Section("Profile") { Section("Profile") {
Picker(selection: $selectedProfileID) { Picker(selection: selectedProfileIDLocal) {
ForEach(profileList, id: \.id) { profile in ForEach(profileList, id: \.id) { profile in
Text(profile.name).tag(profile.id) Text(profile.name).tag(profile.id)
} }
@@ -63,7 +70,7 @@ public struct OverviewView: View {
} }
Section("Profile") { Section("Profile") {
ForEach(profileList, id: \.id) { profile in ForEach(profileList, id: \.id) { profile in
Picker(profile.name, selection: $selectedProfileID) { Picker(profile.name, selection: selectedProfileIDLocal) {
Text("").tag(profile.id) Text("").tag(profile.id)
} }
} }
@@ -74,44 +81,24 @@ public struct OverviewView: View {
} }
} }
.alertBinding($alert) .alertBinding($alert)
.onChangeCompat(of: selectedProfileID) {
reasserting = true
Task {
await switchProfile(selectedProfileID!)
}
}
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting)) .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 nonisolated func switchProfile(_ newProfileID: Int64) async { private func switchProfile(_ newProfileID: Int64) async {
await SharedPreferences.selectedProfileID.set(newProfileID) await SharedPreferences.selectedProfileID.set(newProfileID)
NotificationCenter.default.post(name: OverviewView.NotificationUpdateSelectedProfile, object: newProfileID) environments.selectedProfileUpdate.send()
if await profile.status.isConnected { if profile.status.isConnected {
do { do {
try LibboxNewStandaloneCommandClient()!.serviceReload() try await serviceReload()
} catch { } catch {
await MainActor.run {
alert = Alert(error) alert = Alert(error)
} }
} }
}
await MainActor.run {
reasserting = false reasserting = false
} }
private nonisolated func serviceReload() async throws {
try LibboxNewStandaloneCommandClient()?.serviceReload()
} }
private nonisolated func setSystemProxyEnabled(_ isEnabled: Bool) async { private nonisolated func setSystemProxyEnabled(_ isEnabled: Bool) async {
+15 -7
View File
@@ -5,11 +5,18 @@ public struct LogView: View {
@Environment(\.selection) private var selection @Environment(\.selection) private var selection
@EnvironmentObject private var environments: ExtensionEnvironments @EnvironmentObject private var environments: ExtensionEnvironments
private let logFont = Font.system(.caption2, design: .monospaced)
public init() {} public init() {}
public var body: some View { public var body: some View {
LogView0().environmentObject(environments.logClient)
}
private struct LogView0: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var logClient: CommandClient
private let logFont = Font.system(.caption2, design: .monospaced)
var body: some View {
if ApplicationLibrary.inPreview { if ApplicationLibrary.inPreview {
let logList = [ let logList = [
"(packet-tunnel) log server started", "(packet-tunnel) log server started",
@@ -37,9 +44,9 @@ public struct LogView: View {
.focusEffectDisabled() .focusEffectDisabled()
.focusSection() .focusSection()
#endif #endif
} else if environments.logClient.logList.isEmpty { } else if logClient.logList.isEmpty {
VStack { VStack {
if environments.logClient.isConnected { if logClient.isConnected {
Text("Empty logs") Text("Empty logs")
} else { } else {
Text("Service not started").onAppear { Text("Service not started").onAppear {
@@ -51,7 +58,7 @@ public struct LogView: View {
ScrollViewReader { reader in ScrollViewReader { reader in
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
ForEach(Array(environments.logClient.logList.enumerated()), id: \.offset) { it in ForEach(Array(logClient.logList.enumerated()), id: \.offset) { it in
Text(it.element) Text(it.element)
.font(logFont) .font(logFont)
#if os(tvOS) #if os(tvOS)
@@ -60,7 +67,7 @@ public struct LogView: View {
Spacer(minLength: 8) Spacer(minLength: 8)
} }
.onChangeCompat(of: environments.logClient.logList.count) { newCount in .onChangeCompat(of: logClient.logList.count) { newCount in
withAnimation { withAnimation {
reader.scrollTo(newCount - 1) reader.scrollTo(newCount - 1)
} }
@@ -74,7 +81,8 @@ public struct LogView: View {
.focusSection() .focusSection()
#endif #endif
.onAppear { .onAppear {
reader.scrollTo(environments.logClient.logList.count - 1) reader.scrollTo(logClient.logList.count - 1)
}
} }
} }
} }
@@ -7,17 +7,15 @@ public struct EditProfileView: View {
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
#endif #endif
@EnvironmentObject private var environments: ExtensionEnvironments
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var profile: Profile @EnvironmentObject private var profile: Profile
@State private var isLoading = false @State private var isLoading = false
@State private var isChanged = false @State private var isChanged = false
@State private var alert: Alert? @State private var alert: Alert?
private let updateCallback: (() -> Void)?
public init(_ updateCallback: (() -> Void)? = nil) {
self.updateCallback = updateCallback
}
public init() {}
public var body: some View { public var body: some View {
FormView { FormView {
FormItem("Name") { FormItem("Name") {
@@ -170,7 +168,7 @@ public struct EditProfileView: View {
do { do {
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC))) try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
try await profile.updateRemoteProfile() try await profile.updateRemoteProfile()
await performCallback() environments.profileUpdate.send()
} catch { } catch {
alert = Alert(error) alert = Alert(error)
} }
@@ -183,7 +181,7 @@ public struct EditProfileView: View {
alert = Alert(error) alert = Alert(error)
return return
} }
await performCallback() environments.profileUpdate.send()
dismiss() dismiss()
} }
@@ -201,14 +199,6 @@ public struct EditProfileView: View {
} }
isChanged = false isChanged = false
isLoading = false isLoading = false
await performCallback() environments.profileUpdate.send()
}
private func performCallback() async {
if let updateCallback {
updateCallback()
} else {
NotificationCenter.default.post(name: ProfileView.notificationName, object: nil)
}
} }
} }
@@ -9,6 +9,7 @@ public struct NewProfileView: View {
public static let windowID = "new-profile" public static let windowID = "new-profile"
#endif #endif
@EnvironmentObject private var environments: ExtensionEnvironments
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var isSaving = false @State private var isSaving = false
@@ -27,9 +28,7 @@ public struct NewProfileView: View {
public let url: String public let url: String
} }
private let callback: (() async -> Void)? public init(_ importRequest: ImportRequest? = nil) {
public init(_ importRequest: ImportRequest? = nil, _ callback: (() async -> Void)? = nil) {
self.callback = callback
if let importRequest { if let importRequest {
_profileName = .init(initialValue: importRequest.name) _profileName = .init(initialValue: importRequest.name)
_profileType = .init(initialValue: .remote) _profileType = .init(initialValue: .remote)
@@ -156,12 +155,9 @@ public struct NewProfileView: View {
alert = Alert(error) alert = Alert(error)
return return
} }
if let callback { environments.profileUpdate.send()
await callback()
}
dismiss() dismiss()
#if os(macOS) #if os(macOS)
NotificationCenter.default.post(name: ProfileView.notificationName, object: nil)
resetFields() resetFields()
#endif #endif
} }
@@ -6,8 +6,7 @@ import SwiftUI
@MainActor @MainActor
public struct ProfileView: View { public struct ProfileView: View {
public static let notificationName = Notification.Name("\(FilePath.packageName).update-profile") @EnvironmentObject private var environments: ExtensionEnvironments
@Environment(\.importProfile) private var importProfile @Environment(\.importProfile) private var importProfile
@Environment(\.importRemoteProfile) private var importRemoteProfile @Environment(\.importRemoteProfile) private var importRemoteProfile
@State private var importRemoteProfileRequest: NewProfileView.ImportRequest? @State private var importRemoteProfileRequest: NewProfileView.ImportRequest?
@@ -17,7 +16,7 @@ public struct ProfileView: View {
@State private var isUpdating = false @State private var isUpdating = false
@State private var alert: Alert? @State private var alert: Alert?
@State private var profileList: [Profile] = [] @State private var profileList: [ProfilePreview] = []
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
@State private var editMode = EditMode.inactive @State private var editMode = EditMode.inactive
@@ -29,10 +28,7 @@ public struct ProfileView: View {
@Environment(\.devicePickerSupports) private var devicePickerSupports @Environment(\.devicePickerSupports) private var devicePickerSupports
#endif #endif
@State private var observer: Any?
public init() {} public init() {}
public var body: some View { public var body: some View {
VStack { VStack {
if isLoading { if isLoading {
@@ -46,17 +42,13 @@ public struct ProfileView: View {
ZStack { ZStack {
if let importRemoteProfileRequest { if let importRemoteProfileRequest {
NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) { NavigationDestinationCompat(isPresented: $importRemoteProfilePresented) {
NewProfileView(importRemoteProfileRequest) { NewProfileView(importRemoteProfileRequest)
await doReload()
}
} }
} }
FormView { FormView {
#if os(iOS) #if os(iOS)
NavigationLink { NavigationLink {
NewProfileView { NewProfileView()
await doReload()
}
} label: { } label: {
Text("New Profile").foregroundColor(.accentColor) Text("New Profile").foregroundColor(.accentColor)
} }
@@ -64,9 +56,7 @@ public struct ProfileView: View {
#elseif os(tvOS) #elseif os(tvOS)
Section { Section {
NavigationLink { NavigationLink {
NewProfileView { NewProfileView()
await doReload()
}
} label: { } label: {
Text("New Profile").foregroundColor(.accentColor) Text("New Profile").foregroundColor(.accentColor)
} }
@@ -85,7 +75,7 @@ public struct ProfileView: View {
Text("Empty profiles") Text("Empty profiles")
} else { } else {
List { List {
ForEach(profileList, id: \.mustID) { profile in ForEach(profileList, id: \.id) { profile in
viewBuilder { viewBuilder {
if editMode.isEditing == true { if editMode.isEditing == true {
Text(profile.name) Text(profile.name)
@@ -106,7 +96,7 @@ public struct ProfileView: View {
} else { } else {
FormView { FormView {
List { List {
ForEach(profileList, id: \.mustID) { profile in ForEach(profileList, id: \.id) { profile in
ProfileItem(self, profile) ProfileItem(self, profile)
} }
.onMove(perform: moveProfile) .onMove(perform: moveProfile)
@@ -128,15 +118,6 @@ public struct ProfileView: View {
importRemoteProfile.wrappedValue = nil importRemoteProfile.wrappedValue = nil
createImportRemoteProfileDialog(remoteProfile) createImportRemoteProfileDialog(remoteProfile)
} }
#if os(macOS)
if observer == nil {
observer = NotificationCenter.default.addObserver(forName: ProfileView.notificationName, object: nil, queue: .main) { _ in
Task {
await doReload()
}
}
}
#endif
} }
.onChangeCompat(of: importProfile.wrappedValue) { newValue in .onChangeCompat(of: importProfile.wrappedValue) { newValue in
if let newValue { if let newValue {
@@ -150,23 +131,14 @@ public struct ProfileView: View {
createImportRemoteProfileDialog(newValue) createImportRemoteProfileDialog(newValue)
} }
} }
#if os(macOS) .onReceive(environments.profileUpdate) { _ in
.onDisappear { profileList = []
if let observer { isLoading = true
NotificationCenter.default.removeObserver(observer) // not updated, but why?
// Task {
// await doReload()
// }
} }
observer = nil
}
.toolbar {
ToolbarItem {
Button {
openWindow(id: NewProfileView.windowID)
} label: {
Label("New Profile", systemImage: "plus.square.fill")
}
}
}
#endif
#if os(iOS) #if os(iOS)
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
@@ -232,18 +204,15 @@ public struct ProfileView: View {
private func doReload() async { private func doReload() async {
if ApplicationLibrary.inPreview { if ApplicationLibrary.inPreview {
profileList = [ profileList = [
Profile(id: 0, name: "profile local", type: .local, path: ""), ProfilePreview(Profile(id: 0, name: "profile local", type: .local, path: "")),
Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)), ProfilePreview(Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0))),
] ]
} else { } else {
defer { defer {
isLoading = false isLoading = false
} }
do { do {
if !profileList.isEmpty { profileList = try await ProfileManager.list().map { ProfilePreview($0) }
profileList.removeAll()
}
profileList = try await ProfileManager.list()
} catch { } catch {
alert = Alert(error) alert = Alert(error)
return return
@@ -279,11 +248,12 @@ public struct ProfileView: View {
private func moveProfile(from source: IndexSet, to destination: Int) { private func moveProfile(from source: IndexSet, to destination: Int) {
profileList.move(fromOffsets: source, toOffset: destination) profileList.move(fromOffsets: source, toOffset: destination)
for (index, profile) in profileList.enumerated() { for (index, profile) in profileList.enumerated() {
profile.order = UInt32(index) profileList[index].order = UInt32(index)
profile.origin.order = UInt32(index)
} }
Task { Task {
do { do {
try await ProfileManager.update(profileList) try await ProfileManager.update(profileList.map(\.origin))
} catch { } catch {
alert = Alert(error) alert = Alert(error)
} }
@@ -292,7 +262,7 @@ public struct ProfileView: View {
private func deleteProfile(where profileIndex: IndexSet) { private func deleteProfile(where profileIndex: IndexSet) {
let profileToDelete = profileIndex.map { index in let profileToDelete = profileIndex.map { index in
profileList[index] profileList[index].origin
} }
profileList.remove(atOffsets: profileIndex) profileList.remove(atOffsets: profileIndex)
Task { Task {
@@ -306,16 +276,16 @@ public struct ProfileView: View {
public struct ProfileItem: View { public struct ProfileItem: View {
private let parent: ProfileView private let parent: ProfileView
private let profile: Profile @State private var profile: ProfilePreview
public init(_ parent: ProfileView, _ profile: Profile) { public init(_ parent: ProfileView, _ profile: ProfilePreview) {
self.parent = parent self.parent = parent
self.profile = profile _profile = State(initialValue: profile)
} }
public var body: some View { public var body: some View {
#if os(iOS) || os(macOS) #if os(iOS) || os(macOS)
if #available(iOS 16.0, macOS 13.0,*) { if #available(iOS 16.0, macOS 13.0,*) {
body0.draggable(profile) body0.draggable(profile.origin)
} else { } else {
body0 body0
} }
@@ -329,23 +299,20 @@ public struct ProfileView: View {
viewBuilder { viewBuilder {
#if !os(macOS) #if !os(macOS)
NavigationLink { NavigationLink {
EditProfileView { EditProfileView().environmentObject(profile.origin)
Task {
await parent.doReload()
}
}.environmentObject(profile)
} label: { } label: {
Text(profile.name) Text(profile.name)
} }
.contextMenu { .contextMenu {
ProfileShareButton(parent.$alert, profile) { ProfileShareButton(parent.$alert, profile.origin) {
Label("Share", systemImage: "square.and.arrow.up.fill") Label("Share", systemImage: "square.and.arrow.up.fill")
} }
if profile.type == .remote { if profile.type == .remote {
Button { Button {
parent.isUpdating = true parent.isUpdating = true
Task { Task {
await parent.updateProfile(profile) await parent.updateProfile(profile.origin)
profile = ProfilePreview(profile.origin)
} }
} label: { } label: {
Label("Update", systemImage: "arrow.clockwise") Label("Update", systemImage: "arrow.clockwise")
@@ -353,7 +320,7 @@ public struct ProfileView: View {
} }
Button(role: .destructive) { Button(role: .destructive) {
Task { Task {
await parent.deleteProfile(profile) await parent.deleteProfile(profile.origin)
} }
} label: { } label: {
Label("Delete", systemImage: "trash.fill") Label("Delete", systemImage: "trash.fill")
@@ -365,7 +332,7 @@ public struct ProfileView: View {
Text(profile.name) Text(profile.name)
if profile.type == .remote { if profile.type == .remote {
Spacer(minLength: 4) Spacer(minLength: 4)
Text("Last Updated: \(profile.lastUpdatedString)").font(.caption) Text("Last Updated: \(profile.origin.lastUpdatedString)").font(.caption)
} }
} }
HStack { HStack {
@@ -373,23 +340,24 @@ public struct ProfileView: View {
Button { Button {
parent.isUpdating = true parent.isUpdating = true
Task { Task {
await parent.updateProfile(profile) await parent.updateProfile(profile.origin)
profile = ProfilePreview(profile.origin)
} }
} label: { } label: {
Image(systemName: "arrow.clockwise") Image(systemName: "arrow.clockwise")
} }
} }
ProfileShareButton(parent.$alert, profile) { ProfileShareButton(parent.$alert, profile.origin) {
Image(systemName: "square.and.arrow.up.fill") Image(systemName: "square.and.arrow.up.fill")
} }
Button { Button {
parent.openWindow(id: EditProfileWindowView.windowID, value: profile.mustID) parent.openWindow(id: EditProfileWindowView.windowID, value: profile.id)
} label: { } label: {
Image(systemName: "pencil") Image(systemName: "pencil")
} }
Button { Button {
Task { Task {
await parent.deleteProfile(profile) await parent.deleteProfile(profile.origin)
} }
} label: { } label: {
Image(systemName: "trash.fill") Image(systemName: "trash.fill")
+26
View File
@@ -69,6 +69,32 @@ public class Profile: Record, Identifiable, ObservableObject {
} }
} }
public struct ProfilePreview: Identifiable, Hashable {
public let id: Int64
public let name: String
public var order: UInt32
public let type: ProfileType
public let path: String
public let remoteURL: String?
public let autoUpdate: Bool
public let autoUpdateInterval: Int32
public let lastUpdated: Date?
public let origin: Profile
public init(_ profile: Profile) {
id = profile.mustID
name = profile.name
order = profile.order
type = profile.type
path = profile.path
remoteURL = profile.remoteURL
autoUpdate = profile.autoUpdate
autoUpdateInterval = profile.autoUpdateInterval
lastUpdated = profile.lastUpdated
origin = profile
}
}
public enum ProfileType: Int { public enum ProfileType: Int {
case local = 0, icloud, remote case local = 0, icloud, remote
} }
+16 -12
View File
@@ -93,33 +93,37 @@ public class CommandClient: ObservableObject {
} }
func connected() { func connected() {
DispatchQueue.main.sync { DispatchQueue.main.async { [self] in
commandClient.isConnected = true commandClient.isConnected = true
} }
} }
func disconnected(_: String?) { func disconnected(_: String?) {
DispatchQueue.main.sync { DispatchQueue.main.async { [self] in
commandClient.isConnected = false commandClient.isConnected = false
} }
} }
func clearLog() {
DispatchQueue.main.async { [self] in
commandClient.logList.removeAll()
}
}
func writeLog(_ message: String?) { func writeLog(_ message: String?) {
guard let message else { guard let message else {
return return
} }
var logList = commandClient.logList DispatchQueue.main.async { [self] in
if logList.count > commandClient.logMaxLines { if commandClient.logList.count > commandClient.logMaxLines {
logList.removeFirst() commandClient.logList.removeFirst()
} }
logList.append(message) commandClient.logList.append(message)
DispatchQueue.main.sync {
commandClient.logList = logList
} }
} }
func writeStatus(_ message: LibboxStatusMessage?) { func writeStatus(_ message: LibboxStatusMessage?) {
DispatchQueue.main.sync { DispatchQueue.main.async { [self] in
commandClient.status = message commandClient.status = message
} }
} }
@@ -132,20 +136,20 @@ public class CommandClient: ObservableObject {
while groups.hasNext() { while groups.hasNext() {
newGroups.append(groups.next()!) newGroups.append(groups.next()!)
} }
DispatchQueue.main.sync { DispatchQueue.main.async { [self] in
commandClient.groups = newGroups commandClient.groups = newGroups
} }
} }
func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {
DispatchQueue.main.sync { DispatchQueue.main.async { [self] in
commandClient.clashModeList = modeList!.toArray() commandClient.clashModeList = modeList!.toArray()
commandClient.clashMode = currentMode! commandClient.clashMode = currentMode!
} }
} }
func updateClashMode(_ newMode: String?) { func updateClashMode(_ newMode: String?) {
DispatchQueue.main.sync { DispatchQueue.main.async { [self] in
commandClient.clashMode = newMode! commandClient.clashMode = newMode!
} }
} }
@@ -1,9 +1,12 @@
import Foundation import Foundation
import SwiftUI
public class ExtensionEnvironments: ObservableObject { public class ExtensionEnvironments: ObservableObject {
@Published public var logClient = CommandClient(.log) @Published public var logClient = CommandClient(.log)
@Published public var extensionProfileLoading = true @Published public var extensionProfileLoading = true
@Published public var extensionProfile: ExtensionProfile? @Published public var extensionProfile: ExtensionProfile?
public let profileUpdate = ObjectWillChangePublisher()
public let selectedProfileUpdate = ObjectWillChangePublisher()
public init() {} public init() {}
@@ -173,7 +173,7 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc
} }
public func serviceReload() throws { public func serviceReload() throws {
Task { runBlocking { [self] in
await tunnel.reloadService() await tunnel.reloadService()
} }
} }
@@ -214,4 +214,8 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc
try await self.tunnel.setTunnelNetworkSettings(networkSettings) try await self.tunnel.setTunnelNetworkSettings(networkSettings)
} }
} }
func reset() {
networkSettings = nil
}
} }
+3
View File
@@ -131,6 +131,9 @@ open class ExtensionProvider: NEPacketTunnelProvider {
boxService = nil boxService = nil
commandServer.setService(nil) commandServer.setService(nil)
} }
if let platformInterface {
platformInterface.reset()
}
} }
func reloadService() async { func reloadService() async {
+5
View File
@@ -39,21 +39,26 @@ public struct MacApplication: Scene {
WindowGroup("New Profile", id: NewProfileView.windowID, for: NewProfileView.ImportRequest.self) { importRequest in WindowGroup("New Profile", id: NewProfileView.windowID, for: NewProfileView.ImportRequest.self) { importRequest in
NewProfileView(importRequest.wrappedValue) NewProfileView(importRequest.wrappedValue)
.environmentObject(environments)
}.commandsRemoved() }.commandsRemoved()
WindowGroup("Edit Profile", id: EditProfileWindowView.windowID, for: Int64.self) { profileID in WindowGroup("Edit Profile", id: EditProfileWindowView.windowID, for: Int64.self) { profileID in
EditProfileWindowView(profileID.wrappedValue) EditProfileWindowView(profileID.wrappedValue)
.environmentObject(environments)
}.commandsRemoved() }.commandsRemoved()
WindowGroup("Edit Content", id: EditProfileContentView.windowID, for: EditProfileContentView.Context.self) { context in WindowGroup("Edit Content", id: EditProfileContentView.windowID, for: EditProfileContentView.Context.self) { context in
EditProfileContentView(context.wrappedValue) EditProfileContentView(context.wrappedValue)
.environmentObject(environments)
}.commandsRemoved() }.commandsRemoved()
Window("Service Log", id: ServiceLogView.windowID) { Window("Service Log", id: ServiceLogView.windowID) {
ServiceLogView() ServiceLogView()
.environmentObject(environments)
} }
MenuBarExtra(isInserted: $showMenuBarExtra) { MenuBarExtra(isInserted: $showMenuBarExtra) {
MenuView(isMenuPresented: $isMenuPresented) MenuView(isMenuPresented: $isMenuPresented)
.environmentObject(environments)
} label: { } label: {
Image("MenuIcon") Image("MenuIcon")
} }
+22 -21
View File
@@ -106,6 +106,7 @@ public struct MenuView: View {
} }
private struct ProfilePicker: View { private struct ProfilePicker: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@ObservedObject private var profile: ExtensionProfile @ObservedObject private var profile: ExtensionProfile
init(_ profile: ExtensionProfile) { init(_ profile: ExtensionProfile) {
@@ -113,12 +114,20 @@ public struct MenuView: View {
} }
@State private var isLoading = true @State private var isLoading = true
@State private var profileList: [Profile] = [] @State private var profileList: [ProfilePreview] = []
@State private var selectedProfileID: Int64! @State private var selectedProfileID: Int64 = 0
@State private var reasserting = false @State private var reasserting = false
@State private var observer: Any?
@State private var alert: Alert? @State private var alert: Alert?
private var selectedProfileIDLocal: Binding<Int64> {
$selectedProfileID.withSetter { newValue in
reasserting = true
Task { [self] in
await switchProfile(newValue)
}
}
}
var body: some View { var body: some View {
viewBuilder { viewBuilder {
if isLoading { if isLoading {
@@ -132,32 +141,24 @@ public struct MenuView: View {
Text("Empty profiles") Text("Empty profiles")
} else { } else {
MenuSection("Profile") MenuSection("Profile")
Picker("", selection: $selectedProfileID) { Picker("", selection: selectedProfileIDLocal) {
ForEach(profileList, id: \.id) { profile in ForEach(profileList, id: \.id) { profile in
Text(profile.name) Text(profile.name)
} }
} }
.pickerStyle(.inline) .pickerStyle(.inline)
.onChangeCompat(of: selectedProfileID) {
reasserting = true
Task {
await switchProfile(selectedProfileID!)
}
}
.disabled(!profile.status.isSwitchable || reasserting) .disabled(!profile.status.isSwitchable || reasserting)
} }
} }
} }
.onAppear { .onReceive(environments.profileUpdate) { _ in
if observer == nil { Task {
observer = NotificationCenter.default.addObserver(forName: OverviewView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { notification in await doReload()
selectedProfileID = notification.object as! Int64
})
} }
} }
.onDisappear { .onReceive(environments.selectedProfileUpdate) { _ in
if let observer { Task {
NotificationCenter.default.removeObserver(observer) selectedProfileID = await SharedPreferences.selectedProfileID.get()
} }
} }
.alertBinding($alert) .alertBinding($alert)
@@ -168,7 +169,7 @@ public struct MenuView: View {
isLoading = false isLoading = false
} }
do { do {
profileList = try await ProfileManager.list() profileList = try await ProfileManager.list().map { ProfilePreview($0) }
} catch { } catch {
alert = Alert(error) alert = Alert(error)
return return
@@ -181,14 +182,14 @@ public struct MenuView: View {
profile.id == selectedProfileID profile.id == selectedProfileID
}) })
.isEmpty { .isEmpty {
selectedProfileID = profileList[0].id! selectedProfileID = profileList[0].id
await SharedPreferences.selectedProfileID.set(selectedProfileID) await SharedPreferences.selectedProfileID.set(selectedProfileID)
} }
} }
private func switchProfile(_ newProfileID: Int64) async { private func switchProfile(_ newProfileID: Int64) async {
await SharedPreferences.selectedProfileID.set(newProfileID) await SharedPreferences.selectedProfileID.set(newProfileID)
NotificationCenter.default.post(name: OverviewView.NotificationUpdateSelectedProfile, object: newProfileID) environments.selectedProfileUpdate.send()
if profile.status.isConnected { if profile.status.isConnected {
do { do {
try await serviceReload() try await serviceReload()
+18 -14
View File
@@ -92,6 +92,7 @@
3AC729F02A75D9D000FE8EC1 /* Profile+Transferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC729EF2A75D9D000FE8EC1 /* Profile+Transferable.swift */; }; 3AC729F02A75D9D000FE8EC1 /* Profile+Transferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC729EF2A75D9D000FE8EC1 /* Profile+Transferable.swift */; };
3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC729F12A76088E00FE8EC1 /* ShareButton.swift */; }; 3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC729F12A76088E00FE8EC1 /* ShareButton.swift */; };
3AC8CF9B2A736C750002AF3C /* ImportProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */; }; 3AC8CF9B2A736C750002AF3C /* ImportProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */; };
3ACE6DE32ACADF55009D9A8A /* Binding+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACE6DE22ACADF55009D9A8A /* Binding+Setter.swift */; };
3AD0953D2A70EB310052764E /* Profile+Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD0953C2A70EB310052764E /* Profile+Share.swift */; }; 3AD0953D2A70EB310052764E /* Profile+Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD0953C2A70EB310052764E /* Profile+Share.swift */; };
3ADBB4252A7389640041D44F /* ProfileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBB4242A7389640041D44F /* ProfileServer.swift */; }; 3ADBB4252A7389640041D44F /* ProfileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBB4242A7389640041D44F /* ProfileServer.swift */; };
3ADBB42A2A73A7060041D44F /* NWSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBB4292A73A7060041D44F /* NWSocket.swift */; }; 3ADBB42A2A73A7060041D44F /* NWSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBB4292A73A7060041D44F /* NWSocket.swift */; };
@@ -491,6 +492,7 @@
3AC729EF2A75D9D000FE8EC1 /* Profile+Transferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Transferable.swift"; sourceTree = "<group>"; }; 3AC729EF2A75D9D000FE8EC1 /* Profile+Transferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Transferable.swift"; sourceTree = "<group>"; };
3AC729F12A76088E00FE8EC1 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = "<group>"; }; 3AC729F12A76088E00FE8EC1 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = "<group>"; };
3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportProfileView.swift; sourceTree = "<group>"; }; 3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportProfileView.swift; sourceTree = "<group>"; };
3ACE6DE22ACADF55009D9A8A /* Binding+Setter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Setter.swift"; sourceTree = "<group>"; };
3AD0953C2A70EB310052764E /* Profile+Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Share.swift"; sourceTree = "<group>"; }; 3AD0953C2A70EB310052764E /* Profile+Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Share.swift"; sourceTree = "<group>"; };
3ADBB4242A7389640041D44F /* ProfileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileServer.swift; sourceTree = "<group>"; }; 3ADBB4242A7389640041D44F /* ProfileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileServer.swift; sourceTree = "<group>"; };
3ADBB4292A73A7060041D44F /* NWSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NWSocket.swift; sourceTree = "<group>"; }; 3ADBB4292A73A7060041D44F /* NWSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NWSocket.swift; sourceTree = "<group>"; };
@@ -988,6 +990,7 @@
3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */, 3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */,
3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */, 3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */,
3AC729F12A76088E00FE8EC1 /* ShareButton.swift */, 3AC729F12A76088E00FE8EC1 /* ShareButton.swift */,
3ACE6DE22ACADF55009D9A8A /* Binding+Setter.swift */,
); );
path = Abstract; path = Abstract;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1463,6 +1466,7 @@
3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */, 3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */,
3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */, 3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */,
3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */, 3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */,
3ACE6DE32ACADF55009D9A8A /* Binding+Setter.swift in Sources */,
3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */, 3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */,
3A4EAD252A4FEB65005435B3 /* StartStopButton.swift in Sources */, 3A4EAD252A4FEB65005435B3 /* StartStopButton.swift in Sources */,
3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */, 3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */,
@@ -1962,7 +1966,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = 1.6.0;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box"; PRODUCT_NAME = "sing-box";
SDKROOT = appletvos; SDKROOT = appletvos;
@@ -1996,7 +2000,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = 1.6.0;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box"; PRODUCT_NAME = "sing-box";
SDKROOT = appletvos; SDKROOT = appletvos;
@@ -2230,7 +2234,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = 1.6.0;
OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box"; PRODUCT_NAME = "sing-box";
@@ -2270,7 +2274,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = 1.6.0;
OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box"; PRODUCT_NAME = "sing-box";
@@ -2293,7 +2297,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 88;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = Z56Z6NYZN2; DEVELOPMENT_TEAM = Z56Z6NYZN2;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -2309,7 +2313,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = 1.6.0;
OTHER_CODE_SIGN_FLAGS = ""; OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box"; PRODUCT_NAME = "sing-box";
@@ -2331,7 +2335,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 88;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = Z56Z6NYZN2; DEVELOPMENT_TEAM = Z56Z6NYZN2;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -2347,7 +2351,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = 1.6.0;
OTHER_CODE_SIGN_FLAGS = ""; OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box"; PRODUCT_NAME = "sing-box";
@@ -2391,7 +2395,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACH_O_TYPE = mh_dylib; MACH_O_TYPE = mh_dylib;
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
@@ -2445,7 +2449,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACH_O_TYPE = mh_dylib; MACH_O_TYPE = mh_dylib;
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
@@ -2489,7 +2493,7 @@
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = "1.6.0-alpha.1";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system;
PRODUCT_NAME = "$(inherited)"; PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2525,7 +2529,7 @@
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = "1.6.0-alpha.1";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system;
PRODUCT_NAME = "$(inherited)"; PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2565,7 +2569,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = "1.6.0-alpha.1";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent;
PRODUCT_NAME = SFM; PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2604,7 +2608,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.5.1; MARKETING_VERSION = "1.6.0-alpha.1";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent;
PRODUCT_NAME = SFM; PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";