Improve dashboard & Request network on China iPhone model
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
import Foundation
|
||||||
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
#elseif canImport(AppKit)
|
||||||
|
import AppKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public enum DeviceCensorship {
|
||||||
|
public static func isChinaDevice() -> Bool {
|
||||||
|
let bannedCharacter = "\u{1F1F9}\u{1F1FC}" as NSString
|
||||||
|
var imageData: Data
|
||||||
|
#if canImport(UIKit)
|
||||||
|
let attributes = [NSAttributedString.Key.font:
|
||||||
|
UIFont.systemFont(ofSize: 8)]
|
||||||
|
UIGraphicsBeginImageContext(bannedCharacter.size(withAttributes: attributes))
|
||||||
|
bannedCharacter.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
|
||||||
|
var imagePNG: Data?
|
||||||
|
if let charImage = UIGraphicsGetImageFromCurrentImageContext() {
|
||||||
|
imagePNG = charImage.pngData()
|
||||||
|
}
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
guard let imagePNG else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let uiImage = UIImage(data: imagePNG) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let cgImage = uiImage.cgImage else { return false }
|
||||||
|
guard let cgImageData = cgImage.dataProvider?.data as Data? else { return false }
|
||||||
|
imageData = cgImageData
|
||||||
|
#elseif canImport(AppKit)
|
||||||
|
let attributes = [NSAttributedString.Key.font:
|
||||||
|
NSFont.systemFont(ofSize: 8)]
|
||||||
|
let characterSize = bannedCharacter.size(withAttributes: attributes)
|
||||||
|
let characterRect = NSRect(origin: .zero, size: characterSize)
|
||||||
|
|
||||||
|
let characterBitmap = NSBitmapImageRep(bitmapDataPlanes: nil,
|
||||||
|
pixelsWide: Int(characterSize.width),
|
||||||
|
pixelsHigh: Int(characterSize.height),
|
||||||
|
bitsPerSample: 8,
|
||||||
|
samplesPerPixel: 4,
|
||||||
|
hasAlpha: true,
|
||||||
|
isPlanar: false,
|
||||||
|
colorSpaceName: NSColorSpaceName.calibratedRGB,
|
||||||
|
bytesPerRow: 0,
|
||||||
|
bitsPerPixel: 0)
|
||||||
|
|
||||||
|
NSGraphicsContext.saveGraphicsState()
|
||||||
|
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: characterBitmap!)
|
||||||
|
NSGraphicsContext.current?.imageInterpolation = .high
|
||||||
|
|
||||||
|
bannedCharacter.draw(in: characterRect, withAttributes: attributes)
|
||||||
|
|
||||||
|
NSGraphicsContext.restoreGraphicsState()
|
||||||
|
|
||||||
|
guard let imagePNG = characterBitmap?.representation(using: .png, properties: [:]) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let nsImage = NSImage(data: imagePNG) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let cgImage = nsImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let cgImageData = cgImage.dataProvider?.data as Data? else { return false }
|
||||||
|
imageData = cgImageData
|
||||||
|
#endif
|
||||||
|
let rawData: UnsafePointer<UInt8> = CFDataGetBytePtr(imageData as CFData)
|
||||||
|
for index in stride(from: 0, to: imageData.count, by: 4) {
|
||||||
|
let r = rawData[index]
|
||||||
|
let g = rawData[index + 1]
|
||||||
|
let b = rawData[index + 2]
|
||||||
|
if !(r == g && g == b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,30 +34,38 @@ public struct ActiveDashboardView: View {
|
|||||||
if profileList.isEmpty {
|
if profileList.isEmpty {
|
||||||
Text("Empty profiles")
|
Text("Empty profiles")
|
||||||
} else {
|
} else {
|
||||||
#if os(iOS)
|
VStack {
|
||||||
StartStopButton()
|
|
||||||
#endif
|
|
||||||
if profile.status.isConnected {
|
|
||||||
Section("Status") {
|
|
||||||
ExtensionStatusView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Section("Profile") {
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
Picker(selection: $selectedProfileID) {
|
if profile.status.isConnected {
|
||||||
ForEach(profileList, id: \.id) { profile in
|
ExtensionStatusView()
|
||||||
Text(profile.name).tag(profile.id)
|
.listStyle(.automatic)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
}
|
||||||
|
FormView {
|
||||||
|
StartStopButton()
|
||||||
|
Section("Profile") {
|
||||||
|
Picker(selection: $selectedProfileID) {
|
||||||
|
ForEach(profileList, id: \.id) { profile in
|
||||||
|
Text(profile.name).tag(profile.id)
|
||||||
|
}
|
||||||
|
} label: {}
|
||||||
|
.pickerStyle(.inline)
|
||||||
}
|
}
|
||||||
} label: {}
|
}
|
||||||
.pickerStyle(.inline)
|
#else
|
||||||
|
if profile.status.isConnected {
|
||||||
#elseif os(macOS)
|
ExtensionStatusView()
|
||||||
ForEach(profileList, id: \.id) { profile in
|
}
|
||||||
Picker(profile.name, selection: $selectedProfileID) {
|
FormView {
|
||||||
Text("").tag(profile.id)
|
Section("Profile") {
|
||||||
|
ForEach(profileList, id: \.id) { profile in
|
||||||
|
Picker(profile.name, selection: $selectedProfileID) {
|
||||||
|
Text("").tag(profile.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.radioGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.radioGroup)
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.onChange(of: selectedProfileID) { _ in
|
.onChange(of: selectedProfileID) { _ in
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ public struct DashboardView: View {
|
|||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
FormView {
|
viewBuilder {
|
||||||
if let profile = extensionProfile.wrappedValue {
|
if let profile = extensionProfile.wrappedValue {
|
||||||
ActiveDashboardView().environmentObject(profile)
|
ActiveDashboardView().environmentObject(profile)
|
||||||
} else {
|
} else {
|
||||||
InstallProfileButton()
|
FormView {
|
||||||
|
InstallProfileButton()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.navigationTitle("Dashboard")
|
}.navigationTitle("Dashboard")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public struct ExtensionStatusView: View {
|
|||||||
@State private var commandClient: LibboxCommandClient?
|
@State private var commandClient: LibboxCommandClient?
|
||||||
@State private var message: LibboxStatusMessage?
|
@State private var message: LibboxStatusMessage?
|
||||||
@State private var connectTask: Task<Void, Error>?
|
@State private var connectTask: Task<Void, Error>?
|
||||||
|
@State private var columnCount: Int = 4
|
||||||
@State private var errorPresented = false
|
@State private var errorPresented = false
|
||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
|
|
||||||
@@ -15,23 +16,43 @@ public struct ExtensionStatusView: View {
|
|||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
viewBuilder {
|
||||||
if let message {
|
VStack {
|
||||||
FormTextItem("Memory", LibboxFormatBytes(message.memory))
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
|
||||||
FormTextItem("Goroutines", "\(message.goroutines)")
|
if let message {
|
||||||
FormTextItem("Connections", "\(message.connections)").contextMenu {
|
StatusItem("Memory", LibboxFormatBytes(message.memory))
|
||||||
Button("Close", role: .destructive) {
|
StatusItem("Goroutines", "\(message.goroutines)")
|
||||||
Task.detached {
|
if message.trafficAvailable {
|
||||||
closeConnections()
|
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))
|
||||||
|
} else {
|
||||||
|
StatusItem("Connections", "\(message.connectionsOut)")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
StatusItem("Memory", "Loading...")
|
||||||
|
StatusItem("Goroutines", "Loading...")
|
||||||
|
StatusItem("Connections", "Loading...")
|
||||||
}
|
}
|
||||||
|
}.background {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
Rectangle()
|
||||||
|
.fill(.clear)
|
||||||
|
.frame(height: 1)
|
||||||
|
.onChange(of: geometry.size.width) { newValue in
|
||||||
|
updateColumnCount(newValue)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
updateColumnCount(geometry.size.width)
|
||||||
|
}
|
||||||
|
}.padding()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
FormTextItem("Memory", "Loading...")
|
|
||||||
FormTextItem("Goroutines", "Loading...")
|
|
||||||
FormTextItem("Connections", "Loading...")
|
|
||||||
}
|
}
|
||||||
|
.frame(alignment: .topLeading)
|
||||||
|
.padding([.top, .leading, .trailing])
|
||||||
}
|
}
|
||||||
|
|
||||||
.onAppear(perform: doReload)
|
.onAppear(perform: doReload)
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
connectTask?.cancel()
|
connectTask?.cancel()
|
||||||
@@ -85,6 +106,15 @@ public struct ExtensionStatusView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateColumnCount(_ width: Double) {
|
||||||
|
let v = Int(Int(width) / 155)
|
||||||
|
let new = v < 1 ? 1 : (v > 4 ? 4 : (v % 2 == 0 ? v : v - 1))
|
||||||
|
|
||||||
|
if new != columnCount {
|
||||||
|
columnCount = new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func closeConnections() {
|
private func closeConnections() {
|
||||||
do {
|
do {
|
||||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.closeConnections()
|
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.closeConnections()
|
||||||
@@ -113,4 +143,38 @@ public struct ExtensionStatusView: View {
|
|||||||
|
|
||||||
func writeGroups(_: LibboxOutboundGroupIteratorProtocol?) {}
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
Text(name)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
Text(value)
|
||||||
|
.font(.system(size: 16))
|
||||||
|
}
|
||||||
|
.frame(minWidth: 125)
|
||||||
|
.padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13))
|
||||||
|
.background(backgroundColor)
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var backgroundColor: Color {
|
||||||
|
#if os(iOS)
|
||||||
|
return Color(uiColor: .secondarySystemGroupedBackground)
|
||||||
|
#elseif os(macOS)
|
||||||
|
return Color(nsColor: .textBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public struct SettingView: View {
|
|||||||
@State private var disableMemoryLimit = false
|
@State private var disableMemoryLimit = false
|
||||||
@State private var version = ""
|
@State private var version = ""
|
||||||
@State private var dataSize = ""
|
@State private var dataSize = ""
|
||||||
|
@State private var taiwanFlagAvailable = false
|
||||||
|
|
||||||
@State private var errorPresented = false
|
@State private var errorPresented = false
|
||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
@@ -80,6 +81,9 @@ public struct SettingView: View {
|
|||||||
}
|
}
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
}
|
}
|
||||||
|
Section("Debug") {
|
||||||
|
FormTextItem("Taiwan Flag Available", taiwanFlagAvailable.description)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +125,7 @@ public struct SettingView: View {
|
|||||||
dataSize = "Loading..."
|
dataSize = "Loading..."
|
||||||
isLoading = false
|
isLoading = false
|
||||||
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown"
|
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown"
|
||||||
|
taiwanFlagAvailable = !DeviceCensorship.isChinaDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func clearWorkingDirectory() {
|
private func clearWorkingDirectory() {
|
||||||
|
|||||||
@@ -16,4 +16,8 @@ public enum SharedPreferences {
|
|||||||
@Preference<Bool>("show_menu_bar_extra", defaultValue: true) public static var showMenuBarExtra
|
@Preference<Bool>("show_menu_bar_extra", defaultValue: true) public static var showMenuBarExtra
|
||||||
@Preference<Bool>("started_by_user", defaultValue: false) public static var startedByUser
|
@Preference<Bool>("started_by_user", defaultValue: false) public static var startedByUser
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
@Preference<Bool>("network_permission_requested", defaultValue: false) public static var networkPermissionRequested
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,29 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate {
|
|||||||
NSLog("setup background task error: \(error.localizedDescription)")
|
NSLog("setup background task error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task.detached {
|
||||||
|
await self.requestNetworkPermission()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func requestNetworkPermission() {
|
||||||
|
if UIDevice.current.userInterfaceIdiom != .phone {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if SharedPreferences.networkPermissionRequested {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !DeviceCensorship.isChinaDevice() {
|
||||||
|
SharedPreferences.networkPermissionRequested = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
URLSession.shared.dataTask(with: URL(string: "http://captive.apple.com")!) { _, response, _ in
|
||||||
|
if let response = response as? HTTPURLResponse {
|
||||||
|
if response.statusCode == 200 {
|
||||||
|
SharedPreferences.networkPermissionRequested = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@
|
|||||||
3AC1944F2A50247300BD8CB9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */; };
|
3AC1944F2A50247300BD8CB9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */; };
|
||||||
3AC194502A502DFE00BD8CB9 /* ServiceNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */; };
|
3AC194502A502DFE00BD8CB9 /* ServiceNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */; };
|
||||||
3AC194522A50303300BD8CB9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC194512A50303300BD8CB9 /* ApplicationDelegate.swift */; };
|
3AC194522A50303300BD8CB9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC194512A50303300BD8CB9 /* ApplicationDelegate.swift */; };
|
||||||
|
3AC5EC082A6417470077AF34 /* DeviceCensorship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */; };
|
||||||
3ADF8DFB2A4AFDF500900CC8 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEC210D2A459B1900A63465 /* MainView.swift */; };
|
3ADF8DFB2A4AFDF500900CC8 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEC210D2A459B1900A63465 /* MainView.swift */; };
|
||||||
3AEAEE992A4F16430059612D /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
3AEAEE992A4F16430059612D /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
3AEC20F62A459AB400A63465 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEC20F52A459AB400A63465 /* Application.swift */; };
|
3AEC20F62A459AB400A63465 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEC20F52A459AB400A63465 /* Application.swift */; };
|
||||||
@@ -298,6 +299,7 @@
|
|||||||
3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceNotification.swift; sourceTree = "<group>"; };
|
3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceNotification.swift; sourceTree = "<group>"; };
|
||||||
3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
||||||
3AC194512A50303300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
3AC194512A50303300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceCensorship.swift; sourceTree = "<group>"; };
|
||||||
3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveDashboardView.swift; sourceTree = "<group>"; };
|
3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveDashboardView.swift; sourceTree = "<group>"; };
|
||||||
3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = "<group>"; };
|
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>"; };
|
3ADF8DF62A4AFB2C00900CC8 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||||
@@ -615,9 +617,9 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3AF342A22A4A9B9B002B34AC /* InstallProfileButton.swift */,
|
3AF342A22A4A9B9B002B34AC /* InstallProfileButton.swift */,
|
||||||
3AF342CC2A4AA88C002B34AC /* StartStopButton.swift */,
|
|
||||||
3AF342D02A4AACC4002B34AC /* ExtensionStatusView.swift */,
|
3AF342D02A4AACC4002B34AC /* ExtensionStatusView.swift */,
|
||||||
3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */,
|
3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */,
|
||||||
|
3AF342CC2A4AA88C002B34AC /* StartStopButton.swift */,
|
||||||
3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */,
|
3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */,
|
||||||
);
|
);
|
||||||
path = Dashboard;
|
path = Dashboard;
|
||||||
@@ -629,6 +631,7 @@
|
|||||||
3A7E90302A46745A00D53052 /* ViewBuilder.swift */,
|
3A7E90302A46745A00D53052 /* ViewBuilder.swift */,
|
||||||
3AF342D32A4AADB2002B34AC /* Formtem.swift */,
|
3AF342D32A4AADB2002B34AC /* Formtem.swift */,
|
||||||
3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */,
|
3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */,
|
||||||
|
3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */,
|
||||||
);
|
);
|
||||||
path = Abstract;
|
path = Abstract;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -921,6 +924,7 @@
|
|||||||
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 */,
|
||||||
|
3AC5EC082A6417470077AF34 /* DeviceCensorship.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1431,7 +1435,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.3.1;
|
||||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||||
OTHER_LDFLAGS = "";
|
OTHER_LDFLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||||
@@ -1473,7 +1477,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.3.1;
|
||||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||||
OTHER_LDFLAGS = "";
|
OTHER_LDFLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||||
@@ -1513,7 +1517,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.3.1;
|
||||||
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 β";
|
||||||
@@ -1551,7 +1555,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.3.1;
|
||||||
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 β";
|
||||||
|
|||||||
+27
-27
@@ -12,42 +12,42 @@
|
|||||||
<key>Associations (Playground) 1.xcscheme</key>
|
<key>Associations (Playground) 1.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>8</integer>
|
<integer>8</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Associations (Playground) 2.xcscheme</key>
|
<key>Associations (Playground) 2.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>9</integer>
|
<integer>9</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Associations (Playground) 3.xcscheme</key>
|
<key>Associations (Playground) 3.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>27</integer>
|
<integer>27</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Associations (Playground) 4.xcscheme</key>
|
<key>Associations (Playground) 4.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>28</integer>
|
<integer>28</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Associations (Playground) 5.xcscheme</key>
|
<key>Associations (Playground) 5.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>29</integer>
|
<integer>29</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Associations (Playground).xcscheme</key>
|
<key>Associations (Playground).xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>7</integer>
|
<integer>7</integer>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -69,42 +69,42 @@
|
|||||||
<key>MyPlayground (Playground) 1.xcscheme</key>
|
<key>MyPlayground (Playground) 1.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>4</integer>
|
<integer>4</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MyPlayground (Playground) 2.xcscheme</key>
|
<key>MyPlayground (Playground) 2.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>6</integer>
|
<integer>6</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MyPlayground (Playground) 3.xcscheme</key>
|
<key>MyPlayground (Playground) 3.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>23</integer>
|
<integer>23</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MyPlayground (Playground) 4.xcscheme</key>
|
<key>MyPlayground (Playground) 4.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>24</integer>
|
<integer>24</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MyPlayground (Playground) 5.xcscheme</key>
|
<key>MyPlayground (Playground) 5.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>26</integer>
|
<integer>26</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MyPlayground (Playground).xcscheme</key>
|
<key>MyPlayground (Playground).xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>3</integer>
|
<integer>3</integer>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -136,84 +136,84 @@
|
|||||||
<key>Tour (Playground) 1.xcscheme</key>
|
<key>Tour (Playground) 1.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>15</integer>
|
<integer>15</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Tour (Playground) 2.xcscheme</key>
|
<key>Tour (Playground) 2.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>17</integer>
|
<integer>17</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Tour (Playground) 3.xcscheme</key>
|
<key>Tour (Playground) 3.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>33</integer>
|
<integer>33</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Tour (Playground) 4.xcscheme</key>
|
<key>Tour (Playground) 4.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>34</integer>
|
<integer>34</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Tour (Playground) 5.xcscheme</key>
|
<key>Tour (Playground) 5.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>35</integer>
|
<integer>35</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Tour (Playground).xcscheme</key>
|
<key>Tour (Playground).xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>14</integer>
|
<integer>14</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>TransactionObserver (Playground) 1.xcscheme</key>
|
<key>TransactionObserver (Playground) 1.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>12</integer>
|
<integer>12</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>TransactionObserver (Playground) 2.xcscheme</key>
|
<key>TransactionObserver (Playground) 2.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>13</integer>
|
<integer>13</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>TransactionObserver (Playground) 3.xcscheme</key>
|
<key>TransactionObserver (Playground) 3.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>30</integer>
|
<integer>30</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>TransactionObserver (Playground) 4.xcscheme</key>
|
<key>TransactionObserver (Playground) 4.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>31</integer>
|
<integer>31</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>TransactionObserver (Playground) 5.xcscheme</key>
|
<key>TransactionObserver (Playground) 5.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>32</integer>
|
<integer>32</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>TransactionObserver (Playground).xcscheme</key>
|
<key>TransactionObserver (Playground).xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>isShown</key>
|
<key>isShown</key>
|
||||||
<false />
|
<false/>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>11</integer>
|
<integer>11</integer>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -238,17 +238,17 @@
|
|||||||
<key>3A096F852A4ED3DE00D4A2ED</key>
|
<key>3A096F852A4ED3DE00D4A2ED</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>primary</key>
|
<key>primary</key>
|
||||||
<true />
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>3A77016C2A4E6B34008F031F</key>
|
<key>3A77016C2A4E6B34008F031F</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>primary</key>
|
<key>primary</key>
|
||||||
<true />
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>3AEC211C2A459B4700A63465</key>
|
<key>3AEC211C2A459B4700A63465</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>primary</key>
|
<key>primary</key>
|
||||||
<true />
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
Reference in New Issue
Block a user