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 {
|
||||
Text("Empty profiles")
|
||||
} else {
|
||||
#if os(iOS)
|
||||
StartStopButton()
|
||||
#endif
|
||||
if profile.status.isConnected {
|
||||
Section("Status") {
|
||||
ExtensionStatusView()
|
||||
}
|
||||
}
|
||||
Section("Profile") {
|
||||
VStack {
|
||||
#if os(iOS)
|
||||
Picker(selection: $selectedProfileID) {
|
||||
ForEach(profileList, id: \.id) { profile in
|
||||
Text(profile.name).tag(profile.id)
|
||||
if profile.status.isConnected {
|
||||
ExtensionStatusView()
|
||||
.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)
|
||||
|
||||
#elseif os(macOS)
|
||||
ForEach(profileList, id: \.id) { profile in
|
||||
Picker(profile.name, selection: $selectedProfileID) {
|
||||
Text("").tag(profile.id)
|
||||
}
|
||||
#else
|
||||
if profile.status.isConnected {
|
||||
ExtensionStatusView()
|
||||
}
|
||||
FormView {
|
||||
Section("Profile") {
|
||||
ForEach(profileList, id: \.id) { profile in
|
||||
Picker(profile.name, selection: $selectedProfileID) {
|
||||
Text("").tag(profile.id)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.radioGroup)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.radioGroup)
|
||||
#endif
|
||||
}
|
||||
.onChange(of: selectedProfileID) { _ in
|
||||
|
||||
@@ -6,11 +6,13 @@ public struct DashboardView: View {
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
FormView {
|
||||
viewBuilder {
|
||||
if let profile = extensionProfile.wrappedValue {
|
||||
ActiveDashboardView().environmentObject(profile)
|
||||
} else {
|
||||
InstallProfileButton()
|
||||
FormView {
|
||||
InstallProfileButton()
|
||||
}
|
||||
}
|
||||
}.navigationTitle("Dashboard")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ public struct ExtensionStatusView: View {
|
||||
@State private var commandClient: LibboxCommandClient?
|
||||
@State private var message: LibboxStatusMessage?
|
||||
@State private var connectTask: Task<Void, Error>?
|
||||
@State private var columnCount: Int = 4
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
|
||||
@@ -15,23 +16,43 @@ public struct ExtensionStatusView: View {
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
if let message {
|
||||
FormTextItem("Memory", LibboxFormatBytes(message.memory))
|
||||
FormTextItem("Goroutines", "\(message.goroutines)")
|
||||
FormTextItem("Connections", "\(message.connections)").contextMenu {
|
||||
Button("Close", role: .destructive) {
|
||||
Task.detached {
|
||||
closeConnections()
|
||||
VStack {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
|
||||
if let message {
|
||||
StatusItem("Memory", LibboxFormatBytes(message.memory))
|
||||
StatusItem("Goroutines", "\(message.goroutines)")
|
||||
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))
|
||||
} 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)
|
||||
.onDisappear {
|
||||
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() {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.closeConnections()
|
||||
@@ -113,4 +143,38 @@ 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
|
||||
}
|
||||
|
||||
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 version = ""
|
||||
@State private var dataSize = ""
|
||||
@State private var taiwanFlagAvailable = false
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@@ -80,6 +81,9 @@ public struct SettingView: View {
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
Section("Debug") {
|
||||
FormTextItem("Taiwan Flag Available", taiwanFlagAvailable.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +125,7 @@ public struct SettingView: View {
|
||||
dataSize = "Loading..."
|
||||
isLoading = false
|
||||
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown"
|
||||
taiwanFlagAvailable = !DeviceCensorship.isChinaDevice()
|
||||
}
|
||||
|
||||
private func clearWorkingDirectory() {
|
||||
|
||||
@@ -16,4 +16,8 @@ public enum SharedPreferences {
|
||||
@Preference<Bool>("show_menu_bar_extra", defaultValue: true) public static var showMenuBarExtra
|
||||
@Preference<Bool>("started_by_user", defaultValue: false) public static var startedByUser
|
||||
#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)")
|
||||
}
|
||||
}
|
||||
Task.detached {
|
||||
await self.requestNetworkPermission()
|
||||
}
|
||||
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 */; };
|
||||
3AC194502A502DFE00BD8CB9 /* ServiceNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944C2A50206C00BD8CB9 /* ServiceNotification.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 */; };
|
||||
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 */; };
|
||||
@@ -298,6 +299,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -615,9 +617,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3AF342A22A4A9B9B002B34AC /* InstallProfileButton.swift */,
|
||||
3AF342CC2A4AA88C002B34AC /* StartStopButton.swift */,
|
||||
3AF342D02A4AACC4002B34AC /* ExtensionStatusView.swift */,
|
||||
3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */,
|
||||
3AF342CC2A4AA88C002B34AC /* StartStopButton.swift */,
|
||||
3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */,
|
||||
);
|
||||
path = Dashboard;
|
||||
@@ -629,6 +631,7 @@
|
||||
3A7E90302A46745A00D53052 /* ViewBuilder.swift */,
|
||||
3AF342D32A4AADB2002B34AC /* Formtem.swift */,
|
||||
3ADF8E022A4B118700900CC8 /* Binding+Unwrap.swift */,
|
||||
3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */,
|
||||
);
|
||||
path = Abstract;
|
||||
sourceTree = "<group>";
|
||||
@@ -921,6 +924,7 @@
|
||||
3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */,
|
||||
3A4EAD252A4FEB65005435B3 /* StartStopButton.swift in Sources */,
|
||||
3A4EAD2B2A4FEB6D005435B3 /* ViewBuilder.swift in Sources */,
|
||||
3AC5EC082A6417470077AF34 /* DeviceCensorship.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1431,7 +1435,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
@@ -1473,7 +1477,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
@@ -1513,7 +1517,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
PRODUCT_NAME = "sing-box β";
|
||||
@@ -1551,7 +1555,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
PRODUCT_NAME = "sing-box β";
|
||||
|
||||
+27
-27
@@ -12,42 +12,42 @@
|
||||
<key>Associations (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>8</integer>
|
||||
</dict>
|
||||
<key>Associations (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>Associations (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>27</integer>
|
||||
</dict>
|
||||
<key>Associations (Playground) 4.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
<key>Associations (Playground) 5.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>29</integer>
|
||||
</dict>
|
||||
<key>Associations (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
@@ -69,42 +69,42 @@
|
||||
<key>MyPlayground (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>6</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 4.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 5.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>26</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
@@ -136,84 +136,84 @@
|
||||
<key>Tour (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>17</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>33</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground) 4.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>34</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground) 5.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>35</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>14</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>12</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>13</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>30</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 4.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>31</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 5.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>32</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false />
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
@@ -238,17 +238,17 @@
|
||||
<key>3A096F852A4ED3DE00D4A2ED</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true />
|
||||
<true/>
|
||||
</dict>
|
||||
<key>3A77016C2A4E6B34008F031F</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true />
|
||||
<true/>
|
||||
</dict>
|
||||
<key>3AEC211C2A459B4700A63465</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true />
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
Reference in New Issue
Block a user