Refactor command client
This commit is contained in:
@@ -52,11 +52,14 @@ public struct DashboardView: View {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct DashboardView0: View {
|
struct DashboardView0: View {
|
||||||
@Environment(\.extensionProfile) private var extensionProfile
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if ApplicationLibrary.inPreview {
|
if ApplicationLibrary.inPreview {
|
||||||
ActiveDashboardView()
|
ActiveDashboardView()
|
||||||
} else if let profile = extensionProfile.wrappedValue {
|
} else if environments.extensionProfileLoading {
|
||||||
|
ProgressView()
|
||||||
|
} else if let profile = environments.extensionProfile {
|
||||||
DashboardView1().environmentObject(profile)
|
DashboardView1().environmentObject(profile)
|
||||||
} else {
|
} else {
|
||||||
FormView {
|
FormView {
|
||||||
@@ -67,13 +70,13 @@ public struct DashboardView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct DashboardView1: View {
|
struct DashboardView1: View {
|
||||||
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@EnvironmentObject private var profile: ExtensionProfile
|
@EnvironmentObject private var profile: ExtensionProfile
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ActiveDashboardView()
|
ActiveDashboardView()
|
||||||
.environmentObject(profile)
|
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alertBinding($alert)
|
||||||
.onChangeCompat(of: profile.status) { newValue in
|
.onChangeCompat(of: profile.status) { newValue in
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct ExtensionStatusView: View {
|
public struct ExtensionStatusView: View {
|
||||||
@State private var commandClient: LibboxCommandClient?
|
@StateObject private var commandClient = CommandClient(.status)
|
||||||
@State private var message: LibboxStatusMessage?
|
|
||||||
@State private var connectTask: Task<Void, Error>?
|
|
||||||
@State private var columnCount: Int = 4
|
@State private var columnCount: Int = 4
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
|
|
||||||
private let infoFont = Font.system(.caption, design: .monospaced)
|
private let infoFont = Font.system(.caption, design: .monospaced)
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
viewBuilder {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -34,7 +31,7 @@ public struct ExtensionStatusView: View {
|
|||||||
StatusLine("Uplink", "52 MiB")
|
StatusLine("Uplink", "52 MiB")
|
||||||
StatusLine("Downlink", "5.6 GiB")
|
StatusLine("Downlink", "5.6 GiB")
|
||||||
}
|
}
|
||||||
} else if let message {
|
} else if let message = commandClient.status {
|
||||||
StatusItem("Status") {
|
StatusItem("Status") {
|
||||||
StatusLine("Memory", LibboxFormatBytes(message.memory))
|
StatusLine("Memory", LibboxFormatBytes(message.memory))
|
||||||
StatusLine("Goroutines", "\(message.goroutines)")
|
StatusLine("Goroutines", "\(message.goroutines)")
|
||||||
@@ -80,53 +77,15 @@ public struct ExtensionStatusView: View {
|
|||||||
.frame(alignment: .topLeading)
|
.frame(alignment: .topLeading)
|
||||||
.padding([.top, .leading, .trailing])
|
.padding([.top, .leading, .trailing])
|
||||||
}
|
}
|
||||||
.onAppear(perform: doReload)
|
.onAppear {
|
||||||
|
commandClient.connect()
|
||||||
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
connectTask?.cancel()
|
commandClient.disconnect()
|
||||||
if let commandClient {
|
|
||||||
try? commandClient.disconnect()
|
|
||||||
}
|
|
||||||
commandClient = nil
|
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alertBinding($alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doReload() {
|
|
||||||
connectTask?.cancel()
|
|
||||||
connectTask = Task.detached {
|
|
||||||
await connect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func connect() async {
|
|
||||||
let clientOptions = LibboxCommandClientOptions()
|
|
||||||
clientOptions.command = LibboxCommandStatus
|
|
||||||
clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC)
|
|
||||||
let client = LibboxNewCommandClient(FilePath.sharedDirectory.relativePath, statusHandler(self), clientOptions)!
|
|
||||||
|
|
||||||
do {
|
|
||||||
for i in 0 ..< 10 {
|
|
||||||
try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC)))
|
|
||||||
try Task.checkCancellation()
|
|
||||||
let isConnected: Bool
|
|
||||||
do {
|
|
||||||
try client.connect()
|
|
||||||
isConnected = true
|
|
||||||
} catch {
|
|
||||||
isConnected = false
|
|
||||||
}
|
|
||||||
try Task.checkCancellation()
|
|
||||||
if isConnected {
|
|
||||||
commandClient = client
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
NSLog("failed to connect status: \(error.localizedDescription)")
|
|
||||||
try? client.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateColumnCount(_ width: Double) {
|
private func updateColumnCount(_ width: Double) {
|
||||||
let v = Int(Int(width) / 155)
|
let v = Int(Int(width) / 155)
|
||||||
let new = v < 1 ? 1 : (v > 4 ? 4 : (v % 2 == 0 ? v : v - 1))
|
let new = v < 1 ? 1 : (v > 4 ? 4 : (v % 2 == 0 ? v : v - 1))
|
||||||
@@ -144,26 +103,6 @@ public struct ExtensionStatusView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class statusHandler: NSObject, LibboxCommandClientHandlerProtocol {
|
|
||||||
private let statusView: ExtensionStatusView
|
|
||||||
|
|
||||||
init(_ statusView: ExtensionStatusView) {
|
|
||||||
self.statusView = statusView
|
|
||||||
}
|
|
||||||
|
|
||||||
func connected() {}
|
|
||||||
|
|
||||||
func disconnected(_: String?) {}
|
|
||||||
|
|
||||||
func writeLog(_: String?) {}
|
|
||||||
|
|
||||||
func writeStatus(_ message: LibboxStatusMessage?) {
|
|
||||||
statusView.message = message
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeGroups(_: LibboxOutboundGroupIteratorProtocol?) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct StatusItem<T>: View where T: View {
|
private struct StatusItem<T>: View where T: View {
|
||||||
private let title: String
|
private let title: String
|
||||||
@ViewBuilder private let content: () -> T
|
@ViewBuilder private let content: () -> T
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct InstallProfileButton: View {
|
public struct InstallProfileButton: View {
|
||||||
@Environment(\.extensionProfile) private var extensionProfile
|
|
||||||
|
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import NetworkExtension
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct StartStopButton: View {
|
public struct StartStopButton: View {
|
||||||
@Environment(\.extensionProfile) private var extensionProfile
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ public struct StartStopButton: View {
|
|||||||
})
|
})
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} else if let profile = extensionProfile.wrappedValue {
|
} else if let profile = environments.extensionProfile {
|
||||||
Button0(profile)
|
Button0().environmentObject(profile)
|
||||||
} else {
|
} else {
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
Toggle(isOn: .constant(false)) {
|
Toggle(isOn: .constant(false)) {
|
||||||
@@ -39,14 +39,10 @@ public struct StartStopButton: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct Button0: View {
|
private struct Button0: View {
|
||||||
@Environment(\.logClient) private var logClient
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@ObservedObject private var profile: ExtensionProfile
|
@EnvironmentObject private var profile: ExtensionProfile
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
|
|
||||||
init(_ profile: ExtensionProfile) {
|
|
||||||
self.profile = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
viewBuilder {
|
viewBuilder {
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
@@ -81,7 +77,7 @@ public struct StartStopButton: View {
|
|||||||
do {
|
do {
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
try await profile.start()
|
try await profile.start()
|
||||||
logClient.wrappedValue?.reconnect()
|
environments.logClient.connect()
|
||||||
} else {
|
} else {
|
||||||
profile.stop()
|
profile.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,32 +30,6 @@ public extension EnvironmentValues {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct extensionProfileKey: EnvironmentKey {
|
|
||||||
static let defaultValue: Binding<ExtensionProfile?> = .constant(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
var extensionProfile: Binding<ExtensionProfile?> {
|
|
||||||
get {
|
|
||||||
self[extensionProfileKey.self]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self[extensionProfileKey.self] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct logClientKey: EnvironmentKey {
|
|
||||||
static let defaultValue: Binding<LogClient?> = .constant(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
var logClient: Binding<LogClient?> {
|
|
||||||
get {
|
|
||||||
self[logClientKey.self]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self[logClientKey.self] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct importRemoteProfileKey: EnvironmentKey {
|
private struct importRemoteProfileKey: EnvironmentKey {
|
||||||
static var defaultValue: Binding<LibboxImportRemoteProfile?> = .constant(nil)
|
static var defaultValue: Binding<LibboxImportRemoteProfile?> = .constant(nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import SwiftUI
|
|||||||
|
|
||||||
public struct GroupListView: View {
|
public struct GroupListView: View {
|
||||||
@State private var isLoading = true
|
@State private var isLoading = true
|
||||||
@State private var connectTask: Task<Void, Error>?
|
@StateObject private var commandClient = CommandClient(.groups)
|
||||||
@State private var commandClient: LibboxCommandClient?
|
|
||||||
@State private var groups: [OutboundGroup] = []
|
@State private var groups: [OutboundGroup] = []
|
||||||
@State private var groupExpand: [String: Bool] = [:]
|
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
@@ -27,17 +25,20 @@ public struct GroupListView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||||
.onAppear(perform: doReload)
|
.onAppear {
|
||||||
.onDisappear {
|
connect()
|
||||||
connectTask?.cancel()
|
|
||||||
if let commandClient {
|
|
||||||
try? commandClient.disconnect()
|
|
||||||
}
|
|
||||||
commandClient = nil
|
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
commandClient.disconnect()
|
||||||
|
}
|
||||||
|
.onReceive(commandClient.$groups, perform: { groups in
|
||||||
|
if let groups {
|
||||||
|
setGroups(groups)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doReload() {
|
private func connect() {
|
||||||
if ApplicationLibrary.inPreview {
|
if ApplicationLibrary.inPreview {
|
||||||
groups = [
|
groups = [
|
||||||
OutboundGroup(tag: "my_group", type: "selector", selected: "server", selectable: true, isExpand: true, items: [
|
OutboundGroup(tag: "my_group", type: "selector", selected: "server", selectable: true, isExpand: true, items: [
|
||||||
@@ -52,47 +53,11 @@ public struct GroupListView: View {
|
|||||||
]
|
]
|
||||||
isLoading = false
|
isLoading = false
|
||||||
} else {
|
} else {
|
||||||
connectTask?.cancel()
|
commandClient.connect()
|
||||||
connectTask = Task.detached {
|
|
||||||
await connect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func connect() async {
|
private func setGroups(_ goGroups: [LibboxOutboundGroup]) {
|
||||||
let clientOptions = LibboxCommandClientOptions()
|
|
||||||
clientOptions.command = LibboxCommandGroup
|
|
||||||
clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC)
|
|
||||||
let client = LibboxNewCommandClient(FilePath.sharedDirectory.relativePath, groupsHandler(self), clientOptions)!
|
|
||||||
|
|
||||||
do {
|
|
||||||
for i in 0 ..< 10 {
|
|
||||||
try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC)))
|
|
||||||
try Task.checkCancellation()
|
|
||||||
let isConnected: Bool
|
|
||||||
do {
|
|
||||||
try client.connect()
|
|
||||||
isConnected = true
|
|
||||||
} catch {
|
|
||||||
isConnected = false
|
|
||||||
}
|
|
||||||
try Task.checkCancellation()
|
|
||||||
if isConnected {
|
|
||||||
commandClient = client
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
NSLog("failed to connect status: \(error.localizedDescription)")
|
|
||||||
try? client.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setGroups(_ groupIterator: LibboxOutboundGroupIteratorProtocol) {
|
|
||||||
var goGroups = [LibboxOutboundGroup]()
|
|
||||||
while groupIterator.hasNext() {
|
|
||||||
goGroups.append(groupIterator.next()!)
|
|
||||||
}
|
|
||||||
var groups = [OutboundGroup]()
|
var groups = [OutboundGroup]()
|
||||||
for goGroup in goGroups {
|
for goGroup in goGroups {
|
||||||
var items = [OutboundGroupItem]()
|
var items = [OutboundGroupItem]()
|
||||||
@@ -106,24 +71,4 @@ public struct GroupListView: View {
|
|||||||
self.groups = groups
|
self.groups = groups
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private class groupsHandler: NSObject, LibboxCommandClientHandlerProtocol {
|
|
||||||
private let groupListView: GroupListView
|
|
||||||
|
|
||||||
init(_ statusView: GroupListView) {
|
|
||||||
groupListView = statusView
|
|
||||||
}
|
|
||||||
|
|
||||||
func connected() {}
|
|
||||||
|
|
||||||
func disconnected(_: String?) {}
|
|
||||||
|
|
||||||
func writeLog(_: String?) {}
|
|
||||||
|
|
||||||
func writeStatus(_: LibboxStatusMessage?) {}
|
|
||||||
|
|
||||||
func writeGroups(_ groupIterator: LibboxOutboundGroupIteratorProtocol?) {
|
|
||||||
groupListView.setGroups(groupIterator!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Libbox
|
|
||||||
import Library
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
public class LogClient: ObservableObject {
|
|
||||||
private var maxLines: Int
|
|
||||||
@Published public var isConnected: Bool
|
|
||||||
@Published public var logList: [String]
|
|
||||||
|
|
||||||
private var commandClient: LibboxCommandClient!
|
|
||||||
private var connectTask: Task<Void, Error>?
|
|
||||||
|
|
||||||
public init(_ maxLines: Int) {
|
|
||||||
self.maxLines = maxLines
|
|
||||||
isConnected = false
|
|
||||||
logList = []
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
if let connectTask {
|
|
||||||
connectTask.cancel()
|
|
||||||
}
|
|
||||||
if let commandClient {
|
|
||||||
try? commandClient.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func reconnect() {
|
|
||||||
if ApplicationLibrary.inPreview {
|
|
||||||
logList = [
|
|
||||||
"(packet-tunnel) log server started",
|
|
||||||
"INFO[0000] router: loaded geoip database: 250 codes",
|
|
||||||
"INFO[0000] router: loaded geosite database: 1400 codes",
|
|
||||||
"INFO[0000] router: updated default interface en0, index 11",
|
|
||||||
"inbound/tun[0]: started at utun3",
|
|
||||||
"sing-box started (1.666s)",
|
|
||||||
]
|
|
||||||
isConnected = true
|
|
||||||
} else {
|
|
||||||
if isConnected {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let connectTask {
|
|
||||||
connectTask.cancel()
|
|
||||||
}
|
|
||||||
connectTask = Task.detached {
|
|
||||||
await self.connect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func connect() async {
|
|
||||||
let clientOptions = LibboxCommandClientOptions()
|
|
||||||
clientOptions.command = LibboxCommandLog
|
|
||||||
clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC)
|
|
||||||
let client = LibboxNewCommandClient(FilePath.sharedDirectory.relativePath, logHandler(self), clientOptions)!
|
|
||||||
|
|
||||||
do {
|
|
||||||
for i in 0 ..< 10 {
|
|
||||||
try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC)))
|
|
||||||
try Task.checkCancellation()
|
|
||||||
let isConnected: Bool
|
|
||||||
do {
|
|
||||||
try client.connect()
|
|
||||||
isConnected = true
|
|
||||||
} catch {
|
|
||||||
isConnected = false
|
|
||||||
}
|
|
||||||
try Task.checkCancellation()
|
|
||||||
if isConnected {
|
|
||||||
commandClient = client
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
try? client.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class logHandler: NSObject, LibboxCommandClientHandlerProtocol {
|
|
||||||
private let logClient: LogClient
|
|
||||||
|
|
||||||
init(_ logClient: LogClient) {
|
|
||||||
self.logClient = logClient
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func connected() {
|
|
||||||
logClient.logList.removeAll()
|
|
||||||
logClient.isConnected = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func disconnected(_ message: String?) {
|
|
||||||
if let message {
|
|
||||||
logClient.logList.append("(log client closed) \(message)")
|
|
||||||
} else {
|
|
||||||
logClient.logList.append("(log client closed)")
|
|
||||||
}
|
|
||||||
try? logClient.commandClient?.disconnect()
|
|
||||||
logClient.commandClient = nil
|
|
||||||
logClient.isConnected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func writeLog(_ message: String?) {
|
|
||||||
guard let message else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if logClient.logList.count > logClient.maxLines {
|
|
||||||
logClient.logList.removeFirst()
|
|
||||||
}
|
|
||||||
logClient.logList.append(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeStatus(_: LibboxStatusMessage?) {}
|
|
||||||
func writeGroups(_: LibboxOutboundGroupIteratorProtocol?) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +1,53 @@
|
|||||||
|
import Library
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct LogView: View {
|
public struct LogView: View {
|
||||||
@Environment(\.logClient) private var logClient
|
@Environment(\.selection) private var selection
|
||||||
|
@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 {
|
||||||
viewBuilder {
|
if environments.logClient.logList.isEmpty {
|
||||||
if let logClient = logClient.wrappedValue {
|
VStack {
|
||||||
LogView0().environmentObject(logClient)
|
if environments.logClient.isConnected {
|
||||||
} else {
|
Text("Empty logs")
|
||||||
Text("Service not started")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct LogView0: View {
|
|
||||||
@Environment(\.selection) private var selection
|
|
||||||
@Environment(\.extensionProfile) private var extensionProfile
|
|
||||||
@EnvironmentObject private var logClient: LogClient
|
|
||||||
|
|
||||||
private let logFont = Font.system(.caption2, design: .monospaced)
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
viewBuilder {
|
|
||||||
if logClient.logList.isEmpty {
|
|
||||||
VStack {
|
|
||||||
if logClient.isConnected {
|
|
||||||
Text("Empty logs")
|
|
||||||
} else {
|
|
||||||
Text("Service not started").onAppear(perform: connectLog)
|
|
||||||
}
|
|
||||||
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
|
||||||
} else {
|
} else {
|
||||||
ScrollViewReader { reader in
|
Text("Service not started").onAppear {
|
||||||
ScrollView {
|
environments.connectLog()
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
ForEach(Array(logClient.logList.enumerated()), id: \.offset) { it in
|
|
||||||
Text(it.element)
|
|
||||||
.font(logFont)
|
|
||||||
#if os(tvOS)
|
|
||||||
.focusable()
|
|
||||||
#endif
|
|
||||||
Spacer(minLength: 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
.onChangeCompat(of: logClient.logList.count) { newCount in
|
|
||||||
withAnimation {
|
|
||||||
reader.scrollTo(newCount - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
#if os(tvOS)
|
|
||||||
.focusEffectDisabled()
|
|
||||||
.focusSection()
|
|
||||||
#endif
|
|
||||||
.onAppear {
|
|
||||||
reader.scrollTo(logClient.logList.count - 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||||
}
|
} else {
|
||||||
|
ScrollViewReader { reader in
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
ForEach(Array(environments.logClient.logList.enumerated()), id: \.offset) { it in
|
||||||
|
Text(it.element)
|
||||||
|
.font(logFont)
|
||||||
|
#if os(tvOS)
|
||||||
|
.focusable()
|
||||||
|
#endif
|
||||||
|
Spacer(minLength: 8)
|
||||||
|
}
|
||||||
|
|
||||||
private func connectLog() {
|
.onChangeCompat(of: environments.logClient.logList.count) { newCount in
|
||||||
if ApplicationLibrary.inPreview {
|
withAnimation {
|
||||||
logClient.reconnect()
|
reader.scrollTo(newCount - 1)
|
||||||
} else {
|
}
|
||||||
guard let profile = extensionProfile.wrappedValue else {
|
}
|
||||||
return
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
if profile.status.isConnected, !logClient.isConnected {
|
#if os(tvOS)
|
||||||
logClient.reconnect()
|
.focusEffectDisabled()
|
||||||
|
.focusSection()
|
||||||
|
#endif
|
||||||
|
.onAppear {
|
||||||
|
reader.scrollTo(environments.logClient.logList.count - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,8 @@ public struct ServiceLogView: View {
|
|||||||
if content.isEmpty {
|
if content.isEmpty {
|
||||||
do {
|
do {
|
||||||
content = try String(contentsOf: FilePath.cacheDirectory.appendingPathComponent("stderr.log.old"))
|
content = try String(contentsOf: FilePath.cacheDirectory.appendingPathComponent("stderr.log.old"))
|
||||||
} catch {
|
} catch {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +83,7 @@ public struct ServiceLogView: View {
|
|||||||
try? FileManager.default.removeItem(at: FilePath.cacheDirectory.appendingPathComponent("stderr.log.old"))
|
try? FileManager.default.removeItem(at: FilePath.cacheDirectory.appendingPathComponent("stderr.log.old"))
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
dismiss()
|
dismiss()
|
||||||
|
isLoading = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import Foundation
|
||||||
|
import Libbox
|
||||||
|
|
||||||
|
public class CommandClient: ObservableObject {
|
||||||
|
public enum ConnectionType {
|
||||||
|
case status
|
||||||
|
case groups
|
||||||
|
case log
|
||||||
|
}
|
||||||
|
|
||||||
|
private let connectionType: ConnectionType
|
||||||
|
private let logMaxLines: Int
|
||||||
|
private var commandClient: LibboxCommandClient?
|
||||||
|
private var connectTask: Task<Void, Error>?
|
||||||
|
|
||||||
|
@Published public var isConnected: Bool
|
||||||
|
@Published public var status: LibboxStatusMessage?
|
||||||
|
@Published public var groups: [LibboxOutboundGroup]?
|
||||||
|
@Published public var logList: [String]
|
||||||
|
|
||||||
|
public init(_ connectionType: ConnectionType, logMaxLines: Int = 300) {
|
||||||
|
self.connectionType = connectionType
|
||||||
|
self.logMaxLines = logMaxLines
|
||||||
|
logList = []
|
||||||
|
isConnected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func connect() {
|
||||||
|
if isConnected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let connectTask {
|
||||||
|
connectTask.cancel()
|
||||||
|
}
|
||||||
|
connectTask = Task.detached {
|
||||||
|
await self.connect0()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func disconnect() {
|
||||||
|
if let connectTask {
|
||||||
|
connectTask.cancel()
|
||||||
|
self.connectTask = nil
|
||||||
|
}
|
||||||
|
if let commandClient {
|
||||||
|
try? commandClient.disconnect()
|
||||||
|
self.commandClient = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func connect0() async {
|
||||||
|
let clientOptions = LibboxCommandClientOptions()
|
||||||
|
switch connectionType {
|
||||||
|
case .status:
|
||||||
|
clientOptions.command = LibboxCommandStatus
|
||||||
|
case .groups:
|
||||||
|
clientOptions.command = LibboxCommandGroup
|
||||||
|
case .log:
|
||||||
|
clientOptions.command = LibboxCommandLog
|
||||||
|
}
|
||||||
|
clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC)
|
||||||
|
let client = LibboxNewCommandClient(FilePath.sharedDirectory.relativePath, clientHandler(self), clientOptions)!
|
||||||
|
do {
|
||||||
|
for i in 0 ..< 10 {
|
||||||
|
try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC)))
|
||||||
|
try Task.checkCancellation()
|
||||||
|
let isConnected: Bool
|
||||||
|
do {
|
||||||
|
try client.connect()
|
||||||
|
isConnected = true
|
||||||
|
} catch {
|
||||||
|
isConnected = false
|
||||||
|
}
|
||||||
|
try Task.checkCancellation()
|
||||||
|
if isConnected {
|
||||||
|
commandClient = client
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
try? client.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol {
|
||||||
|
private let commandClient: CommandClient
|
||||||
|
|
||||||
|
init(_ commandClient: CommandClient) {
|
||||||
|
self.commandClient = commandClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func connected() {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
self.commandClient.isConnected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnected(_: String?) {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
self.commandClient.isConnected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLog(_ message: String?) {
|
||||||
|
guard let message else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var logList = commandClient.logList
|
||||||
|
if logList.count > commandClient.logMaxLines {
|
||||||
|
logList.removeFirst()
|
||||||
|
}
|
||||||
|
logList.append(message)
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
self.commandClient.logList = logList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStatus(_ message: LibboxStatusMessage?) {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
self.commandClient.status = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) {
|
||||||
|
guard let groups else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var newGroups: [LibboxOutboundGroup] = []
|
||||||
|
while groups.hasNext() {
|
||||||
|
newGroups.append(groups.next()!)
|
||||||
|
}
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
self.commandClient.groups = newGroups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class ExtensionEnvironments: ObservableObject {
|
||||||
|
@Published public var logClient = CommandClient(.log)
|
||||||
|
@Published public var extensionProfileLoading = true
|
||||||
|
@Published public var extensionProfile: ExtensionProfile?
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
logClient.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func postReload() {
|
||||||
|
Task.detached {
|
||||||
|
await self.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reload() async {
|
||||||
|
if let newProfile = try? await ExtensionProfile.load() {
|
||||||
|
if extensionProfile == nil || extensionProfile?.status == .invalid {
|
||||||
|
newProfile.register()
|
||||||
|
await MainActor.run {
|
||||||
|
extensionProfile = newProfile
|
||||||
|
extensionProfileLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await MainActor.run {
|
||||||
|
extensionProfile = nil
|
||||||
|
extensionProfileLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func connectLog() {
|
||||||
|
guard let profile = extensionProfile else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if profile.status.isConnected, !logClient.isConnected {
|
||||||
|
logClient.connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import SwiftUI
|
|||||||
public struct MacApplication: Scene {
|
public struct MacApplication: Scene {
|
||||||
@State private var showMenuBarExtra = false
|
@State private var showMenuBarExtra = false
|
||||||
@State private var isMenuPresented = false
|
@State private var isMenuPresented = false
|
||||||
|
@StateObject private var environments = ExtensionEnvironments()
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some Scene {
|
public var body: some Scene {
|
||||||
@@ -12,10 +13,11 @@ public struct MacApplication: Scene {
|
|||||||
MainView()
|
MainView()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
initialize()
|
await initialize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.showMenuBarExtra, $showMenuBarExtra)
|
.environment(\.showMenuBarExtra, $showMenuBarExtra)
|
||||||
|
.environmentObject(environments)
|
||||||
})
|
})
|
||||||
.commands {
|
.commands {
|
||||||
if showMenuBarExtra {
|
if showMenuBarExtra {
|
||||||
|
|||||||
+26
-68
@@ -5,11 +5,9 @@ import SwiftUI
|
|||||||
|
|
||||||
public struct MainView: View {
|
public struct MainView: View {
|
||||||
@Environment(\.controlActiveState) private var controlActiveState
|
@Environment(\.controlActiveState) private var controlActiveState
|
||||||
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
|
|
||||||
@State private var selection = NavigationPage.dashboard
|
@State private var selection = NavigationPage.dashboard
|
||||||
@State private var extensionProfile: ExtensionProfile?
|
|
||||||
@State private var profileLoading = true
|
|
||||||
@State private var logClient: LogClient!
|
|
||||||
@State private var importProfile: LibboxProfileContent?
|
@State private var importProfile: LibboxProfileContent?
|
||||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
@@ -19,55 +17,41 @@ public struct MainView: View {
|
|||||||
NavigationSplitView {
|
NavigationSplitView {
|
||||||
SidebarView()
|
SidebarView()
|
||||||
} detail: {
|
} detail: {
|
||||||
if profileLoading {
|
selection.contentView
|
||||||
ProgressView().onAppear {
|
.navigationTitle(selection.title)
|
||||||
Task {
|
|
||||||
logClient = LogClient(SharedPreferences.maxLogLines)
|
|
||||||
await loadProfile()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selection.contentView
|
|
||||||
.navigationTitle(selection.title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#if !DEBUG
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
environments.postReload()
|
||||||
|
#if !DEBUG
|
||||||
if Variant.useSystemExtension {
|
if Variant.useSystemExtension {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
checkApplicationPath()
|
checkApplicationPath()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.alertBinding($alert)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigation) {
|
||||||
|
StartStopButton()
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
.alertBinding($alert)
|
.onChangeCompat(of: controlActiveState) { newValue in
|
||||||
.toolbar {
|
if newValue != .inactive {
|
||||||
ToolbarItem(placement: .navigation) {
|
environments.postReload()
|
||||||
StartStopButton()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onChangeCompat(of: controlActiveState) { newValue in
|
}
|
||||||
if newValue != .inactive {
|
.onChangeCompat(of: selection) { value in
|
||||||
Task {
|
if value == .logs {
|
||||||
await loadProfile()
|
environments.connectLog()
|
||||||
connectLog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onChangeCompat(of: selection) { value in
|
}
|
||||||
if value == .logs {
|
.formStyle(.grouped)
|
||||||
connectLog()
|
.environment(\.selection, $selection)
|
||||||
}
|
.environment(\.importProfile, $importProfile)
|
||||||
}
|
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||||
.formStyle(.grouped)
|
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||||
.environment(\.selection, $selection)
|
.onOpenURL(perform: openURL)
|
||||||
.environment(\.extensionProfile, $extensionProfile)
|
|
||||||
.environment(\.logClient, $logClient)
|
|
||||||
|
|
||||||
.environment(\.importProfile, $importProfile)
|
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
|
||||||
.onOpenURL(perform: openURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openURL(url: URL) {
|
private func openURL(url: URL) {
|
||||||
@@ -97,32 +81,6 @@ public struct MainView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadProfile() async {
|
|
||||||
defer {
|
|
||||||
profileLoading = false
|
|
||||||
}
|
|
||||||
if let newProfile = try? await ExtensionProfile.load() {
|
|
||||||
if extensionProfile == nil {
|
|
||||||
newProfile.register()
|
|
||||||
extensionProfile = newProfile
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
extensionProfile = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func connectLog() {
|
|
||||||
guard let profile = extensionProfile else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let logClient else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if profile.status.isConnected, !logClient.isConnected {
|
|
||||||
logClient.reconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func checkApplicationPath() {
|
private func checkApplicationPath() {
|
||||||
let directoryName = URL(filePath: Bundle.main.bundlePath).deletingLastPathComponent().pathComponents.last
|
let directoryName = URL(filePath: Bundle.main.bundlePath).deletingLastPathComponent().pathComponents.last
|
||||||
if directoryName != "Applications" {
|
if directoryName != "Applications" {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import Libbox
|
||||||
|
import Library
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct MenuLabel: View {
|
||||||
|
@EnvironmentObject private var environments: ExtensionEnvirnments
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
public var body: some View {
|
||||||
|
// if let profile = environments.extensionProfile {
|
||||||
|
// MenuLabel0().environmentObject(profile)
|
||||||
|
// } else {
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct MenuLabel0: View {
|
||||||
|
@EnvironmentObject private var environments: ExtensionEnvirnments
|
||||||
|
@EnvironmentObject private var extensionProfile: ExtensionProfile
|
||||||
|
@StateObject private var commandClient = CommandClient(.status)
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
if extensionProfile.status.isConnectedStrict, let message = commandClient.status {
|
||||||
|
Image("MenuIcon")
|
||||||
|
Text(" ↑ \(LibboxFormatBytes(message.uplink))/s ↓ \(LibboxFormatBytes(message.downlink))/s")
|
||||||
|
} else {
|
||||||
|
Image("MenuIcon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
commandClient.connect()
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: extensionProfile.status) { newValue in
|
||||||
|
if newValue.isConnectedStrict {
|
||||||
|
commandClient.connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,14 @@ import SwiftUI
|
|||||||
|
|
||||||
public struct SidebarView: View {
|
public struct SidebarView: View {
|
||||||
@Environment(\.selection) private var selection
|
@Environment(\.selection) private var selection
|
||||||
@Environment(\.extensionProfile) private var extensionProfile
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
if let profile = extensionProfile.wrappedValue {
|
if environments.extensionProfileLoading {
|
||||||
|
ProgressView()
|
||||||
|
} else if let profile = environments.extensionProfile {
|
||||||
SidebarView0().environmentObject(profile)
|
SidebarView0().environmentObject(profile)
|
||||||
} else {
|
} else {
|
||||||
SidebarView1()
|
SidebarView1()
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import ApplicationLibrary
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Library
|
import Library
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Application: App {
|
struct Application: App {
|
||||||
@UIApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
|
@UIApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
|
||||||
|
@StateObject private var environments = ExtensionEnvironments()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
MainView()
|
MainView()
|
||||||
|
.environmentObject(environments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-56
@@ -4,49 +4,40 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
|
|
||||||
@State private var selection = NavigationPage.dashboard
|
@State private var selection = NavigationPage.dashboard
|
||||||
@State private var extensionProfile: ExtensionProfile?
|
|
||||||
@State private var profileLoading = true
|
|
||||||
@State private var logClient: LogClient!
|
|
||||||
@State private var importProfile: LibboxProfileContent?
|
@State private var importProfile: LibboxProfileContent?
|
||||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
||||||
@State private var alert: Alert?
|
@State private var alert: Alert?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
viewBuilder {
|
TabView(selection: $selection) {
|
||||||
if profileLoading {
|
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||||
ProgressView().onAppear {
|
NavigationStackCompat {
|
||||||
Task.detached {
|
page.contentView
|
||||||
logClient = LogClient(SharedPreferences.maxLogLines)
|
.navigationTitle(page.title)
|
||||||
await loadProfile()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TabView(selection: $selection) {
|
|
||||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
|
||||||
NavigationStackCompat {
|
|
||||||
page.contentView
|
|
||||||
.navigationTitle(page.title)
|
|
||||||
}
|
|
||||||
.tag(page)
|
|
||||||
.tabItem { page.label }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.tag(page)
|
||||||
|
.tabItem { page.label }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
environments.postReload()
|
||||||
|
}
|
||||||
.alertBinding($alert)
|
.alertBinding($alert)
|
||||||
.onChangeCompat(of: scenePhase) { newValue in
|
.onChangeCompat(of: scenePhase) { newValue in
|
||||||
if newValue == .active {
|
if newValue == .active {
|
||||||
Task.detached {
|
environments.postReload()
|
||||||
await loadProfile()
|
}
|
||||||
}
|
}
|
||||||
|
.onChangeCompat(of: selection) { newValue in
|
||||||
|
if newValue == .logs {
|
||||||
|
environments.connectLog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.selection, $selection)
|
.environment(\.selection, $selection)
|
||||||
.environment(\.extensionProfile, $extensionProfile)
|
|
||||||
.environment(\.logClient, $logClient)
|
|
||||||
.environment(\.importProfile, $importProfile)
|
.environment(\.importProfile, $importProfile)
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||||
@@ -80,33 +71,4 @@ struct MainView: View {
|
|||||||
alert = Alert(errorMessage: "Handled unknown URL \(url.absoluteString)")
|
alert = Alert(errorMessage: "Handled unknown URL \(url.absoluteString)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadProfile() async {
|
|
||||||
defer {
|
|
||||||
profileLoading = false
|
|
||||||
}
|
|
||||||
if ApplicationLibrary.inPreview {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let newProfile = try? await ExtensionProfile.load() {
|
|
||||||
if extensionProfile == nil || extensionProfile?.status == .invalid {
|
|
||||||
newProfile.register()
|
|
||||||
extensionProfile = newProfile
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
extensionProfile = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func connectLog() {
|
|
||||||
guard let profile = extensionProfile else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let logClient else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if profile.status.isConnected, !logClient.isConnected {
|
|
||||||
logClient.reconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
import Foundation
|
||||||
|
import Library
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Application: App {
|
struct Application: App {
|
||||||
@UIApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
|
@UIApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
|
||||||
|
@StateObject private var environments = ExtensionEnvironments()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
MainView()
|
MainView()
|
||||||
|
.environmentObject(environments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-74
@@ -4,90 +4,35 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@State private var selection = NavigationPage.dashboard
|
@State private var selection = NavigationPage.dashboard
|
||||||
@State private var extensionProfile: ExtensionProfile?
|
|
||||||
@State private var profileLoading = true
|
|
||||||
@State private var logClient: LogClient!
|
|
||||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
viewBuilder {
|
TabView(selection: $selection) {
|
||||||
if profileLoading {
|
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||||
ProgressView().onAppear {
|
NavigationStackCompat {
|
||||||
Task.detached {
|
page.contentView
|
||||||
logClient = LogClient(SharedPreferences.maxLogLines)
|
.navigationTitle(page.title)
|
||||||
await loadProfile()
|
.focusSection()
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TabView(selection: $selection) {
|
|
||||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
|
||||||
NavigationStackCompat {
|
|
||||||
page.contentView
|
|
||||||
.navigationTitle(page.title)
|
|
||||||
.focusSection()
|
|
||||||
}
|
|
||||||
.tag(page)
|
|
||||||
.tabItem { page.label }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.tag(page)
|
||||||
|
.tabItem { page.label }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
environments.postReload()
|
||||||
|
}
|
||||||
.onChangeCompat(of: scenePhase) { newValue in
|
.onChangeCompat(of: scenePhase) { newValue in
|
||||||
if newValue == .active {
|
if newValue == .active {
|
||||||
Task.detached {
|
environments.postReload()
|
||||||
await loadProfile()
|
}
|
||||||
}
|
}
|
||||||
|
.onChangeCompat(of: selection) { newValue in
|
||||||
|
if newValue == .logs {
|
||||||
|
environments.connectLog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.selection, $selection)
|
.environment(\.selection, $selection)
|
||||||
.environment(\.extensionProfile, $extensionProfile)
|
|
||||||
.environment(\.logClient, $logClient)
|
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
|
||||||
.onOpenURL(perform: openURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func openURL(url: URL) {
|
|
||||||
if url.host == "import-remote-profile" {
|
|
||||||
var error: NSError?
|
|
||||||
importRemoteProfile = LibboxParseRemoteProfileImportLink(url.absoluteString, &error)
|
|
||||||
if error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if selection != .profiles {
|
|
||||||
selection = .profiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadProfile() async {
|
|
||||||
defer {
|
|
||||||
profileLoading = false
|
|
||||||
}
|
|
||||||
if ApplicationLibrary.inPreview {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let newProfile = try? await ExtensionProfile.load() {
|
|
||||||
if extensionProfile == nil || extensionProfile?.status == .invalid {
|
|
||||||
newProfile.register()
|
|
||||||
extensionProfile = newProfile
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
extensionProfile = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func connectLog() {
|
|
||||||
guard let profile = extensionProfile else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let logClient else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if profile.status.isConnected, !logClient.isConnected {
|
|
||||||
logClient.reconnect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
3A2223582A6E1CC700C50B23 /* MacApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2223572A6E1CC700C50B23 /* MacApplication.swift */; };
|
3A2223582A6E1CC700C50B23 /* MacApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2223572A6E1CC700C50B23 /* MacApplication.swift */; };
|
||||||
3A22235A2A6E212A00C50B23 /* SystemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2223592A6E212A00C50B23 /* SystemExtension.swift */; };
|
3A22235A2A6E212A00C50B23 /* SystemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2223592A6E212A00C50B23 /* SystemExtension.swift */; };
|
||||||
3A251C122A52D09700651082 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AAB5E7A2A4C1446009757F1 /* Assets.xcassets */; };
|
3A251C122A52D09700651082 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AAB5E7A2A4C1446009757F1 /* Assets.xcassets */; };
|
||||||
|
3A27D9002A89BE230031EBCC /* CommandClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A27D8FF2A89BE230031EBCC /* CommandClient.swift */; };
|
||||||
|
3A27D9022A89C6870031EBCC /* ExtensionEnvironments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A27D9012A89C6870031EBCC /* ExtensionEnvironments.swift */; };
|
||||||
3A2EAEED2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */; };
|
3A2EAEED2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */; };
|
||||||
3A3AA7FC2A4EFDAE002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
3A3AA7FC2A4EFDAE002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||||
3A3AA7FF2A4EFDB3002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
3A3AA7FF2A4EFDB3002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||||
@@ -44,7 +46,6 @@
|
|||||||
3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DF82A4AFCB400900CC8 /* NewProfileView.swift */; };
|
3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADF8DF82A4AFCB400900CC8 /* NewProfileView.swift */; };
|
||||||
3A4EAD312A4FEB7B005435B3 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E712A4BF6F6009757F1 /* SettingView.swift */; };
|
3A4EAD312A4FEB7B005435B3 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E712A4BF6F6009757F1 /* SettingView.swift */; };
|
||||||
3A4EAD322A4FEB7B005435B3 /* ServiceLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */; };
|
3A4EAD322A4FEB7B005435B3 /* ServiceLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */; };
|
||||||
3A4EAD332A4FEB7F005435B3 /* LogClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA1ABBB2A4C4107000FD4BA /* LogClient.swift */; };
|
|
||||||
3A4EAD342A4FEB7F005435B3 /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA1ABB92A4C4054000FD4BA /* LogView.swift */; };
|
3A4EAD342A4FEB7F005435B3 /* LogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA1ABB92A4C4054000FD4BA /* LogView.swift */; };
|
||||||
3A4EAD352A4FEB9C005435B3 /* UIProfileUpdateTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A55F9572A4D137E003C4EF4 /* UIProfileUpdateTask.swift */; };
|
3A4EAD352A4FEB9C005435B3 /* UIProfileUpdateTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A55F9572A4D137E003C4EF4 /* UIProfileUpdateTask.swift */; };
|
||||||
3A4EAD362A4FEB9C005435B3 /* ProfileUpdateTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A55F9592A4D1554003C4EF4 /* ProfileUpdateTask.swift */; };
|
3A4EAD362A4FEB9C005435B3 /* ProfileUpdateTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A55F9592A4D1554003C4EF4 /* ProfileUpdateTask.swift */; };
|
||||||
@@ -435,6 +436,8 @@
|
|||||||
3A2223552A6E1BDE00C50B23 /* Variant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variant.swift; sourceTree = "<group>"; };
|
3A2223552A6E1BDE00C50B23 /* Variant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variant.swift; sourceTree = "<group>"; };
|
||||||
3A2223572A6E1CC700C50B23 /* MacApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacApplication.swift; sourceTree = "<group>"; };
|
3A2223572A6E1CC700C50B23 /* MacApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacApplication.swift; sourceTree = "<group>"; };
|
||||||
3A2223592A6E212A00C50B23 /* SystemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemExtension.swift; sourceTree = "<group>"; };
|
3A2223592A6E212A00C50B23 /* SystemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemExtension.swift; sourceTree = "<group>"; };
|
||||||
|
3A27D8FF2A89BE230031EBCC /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = "<group>"; };
|
||||||
|
3A27D9012A89C6870031EBCC /* ExtensionEnvironments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionEnvironments.swift; sourceTree = "<group>"; };
|
||||||
3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = "<group>"; };
|
3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = "<group>"; };
|
||||||
3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExtensionFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/ExtensionFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExtensionFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/ExtensionFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppIntents.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/AppIntents.framework; sourceTree = DEVELOPER_DIR; };
|
3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppIntents.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/AppIntents.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
@@ -467,7 +470,6 @@
|
|||||||
3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCompat.swift; sourceTree = "<group>"; };
|
3A99B42B2A75288C0010D4B0 /* ViewCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCompat.swift; sourceTree = "<group>"; };
|
||||||
3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationDestinationCompat.swift; sourceTree = "<group>"; };
|
3A99B42D2A752ABB0010D4B0 /* NavigationDestinationCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationDestinationCompat.swift; sourceTree = "<group>"; };
|
||||||
3AA1ABB92A4C4054000FD4BA /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogView.swift; sourceTree = "<group>"; };
|
3AA1ABB92A4C4054000FD4BA /* LogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogView.swift; sourceTree = "<group>"; };
|
||||||
3AA1ABBB2A4C4107000FD4BA /* LogClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogClient.swift; sourceTree = "<group>"; };
|
|
||||||
3AAB5E712A4BF6F6009757F1 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = "<group>"; };
|
3AAB5E712A4BF6F6009757F1 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = "<group>"; };
|
||||||
3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLogView.swift; sourceTree = "<group>"; };
|
3AAB5E732A4BF90B009757F1 /* ServiceLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLogView.swift; sourceTree = "<group>"; };
|
||||||
3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileContentView.swift; sourceTree = "<group>"; };
|
3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileContentView.swift; sourceTree = "<group>"; };
|
||||||
@@ -711,7 +713,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3AA1ABB92A4C4054000FD4BA /* LogView.swift */,
|
3AA1ABB92A4C4054000FD4BA /* LogView.swift */,
|
||||||
3AA1ABBB2A4C4107000FD4BA /* LogClient.swift */,
|
|
||||||
);
|
);
|
||||||
path = Log;
|
path = Log;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -883,6 +884,8 @@
|
|||||||
3A4EAD3B2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift */,
|
3A4EAD3B2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift */,
|
||||||
3A2223592A6E212A00C50B23 /* SystemExtension.swift */,
|
3A2223592A6E212A00C50B23 /* SystemExtension.swift */,
|
||||||
3AF342A62A4AA0FF002B34AC /* ExtensionPlatformInterface.swift */,
|
3AF342A62A4AA0FF002B34AC /* ExtensionPlatformInterface.swift */,
|
||||||
|
3A27D8FF2A89BE230031EBCC /* CommandClient.swift */,
|
||||||
|
3A27D9012A89C6870031EBCC /* ExtensionEnvironments.swift */,
|
||||||
);
|
);
|
||||||
path = Network;
|
path = Network;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1446,7 +1449,6 @@
|
|||||||
3A4EAD2C2A4FEB77005435B3 /* EditProfileWindowView.swift in Sources */,
|
3A4EAD2C2A4FEB77005435B3 /* EditProfileWindowView.swift in Sources */,
|
||||||
3A1CF2FA2A50F0BD000A8289 /* OutboundGroupItem.swift in Sources */,
|
3A1CF2FA2A50F0BD000A8289 /* OutboundGroupItem.swift in Sources */,
|
||||||
3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */,
|
3A99B42E2A752ABB0010D4B0 /* NavigationDestinationCompat.swift in Sources */,
|
||||||
3A4EAD332A4FEB7F005435B3 /* LogClient.swift in Sources */,
|
|
||||||
3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */,
|
3AC729F22A76088E00FE8EC1 /* ShareButton.swift in Sources */,
|
||||||
3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */,
|
3A0C6D3C2A79D46500A4DF2B /* OverviewView.swift in Sources */,
|
||||||
3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */,
|
3A4EAD262A4FEB65005435B3 /* ExtensionStatusView.swift in Sources */,
|
||||||
@@ -1506,6 +1508,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
3A27D9022A89C6870031EBCC /* ExtensionEnvironments.swift in Sources */,
|
||||||
3AE4D0B52A6E2BAC009FEA9E /* ExtensionProvider.swift in Sources */,
|
3AE4D0B52A6E2BAC009FEA9E /* ExtensionProvider.swift in Sources */,
|
||||||
3A2223562A6E1BDE00C50B23 /* Variant.swift in Sources */,
|
3A2223562A6E1BDE00C50B23 /* Variant.swift in Sources */,
|
||||||
3AEC214A2A45AA5600A63465 /* Profile+RW.swift in Sources */,
|
3AEC214A2A45AA5600A63465 /* Profile+RW.swift in Sources */,
|
||||||
@@ -1530,6 +1533,7 @@
|
|||||||
3AE4D0B22A6E2B6A009FEA9E /* ExtensionPlatformInterface.swift in Sources */,
|
3AE4D0B22A6E2B6A009FEA9E /* ExtensionPlatformInterface.swift in Sources */,
|
||||||
3AEC21402A45A28F00A63465 /* ProfileManager.swift in Sources */,
|
3AEC21402A45A28F00A63465 /* ProfileManager.swift in Sources */,
|
||||||
3A9144D92A46AE370036E9AD /* ShadredPreferences+Database.swift in Sources */,
|
3A9144D92A46AE370036E9AD /* ShadredPreferences+Database.swift in Sources */,
|
||||||
|
3A27D9002A89BE230031EBCC /* CommandClient.swift in Sources */,
|
||||||
3AF342A02A4A9916002B34AC /* ExtensionProfile.swift in Sources */,
|
3AF342A02A4A9916002B34AC /* ExtensionProfile.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user