Init commit

This commit is contained in:
世界
2023-07-15 15:03:45 +08:00
commit f441b89efb
122 changed files with 7355 additions and 0 deletions
@@ -0,0 +1,92 @@
import Libbox
import Library
import SwiftUI
public struct GroupItemView: View {
private let _group: Binding<OutboundGroup>
private var group: OutboundGroup {
_group.wrappedValue
}
private let item: OutboundGroupItem
public init(_ group: Binding<OutboundGroup>, _ item: OutboundGroupItem) {
_group = group
self.item = item
}
@State private var errorPresented = false
@State private var errorMessage = ""
public var body: some View {
HStack {
if group.selected == item.tag {
Rectangle()
.fill(Color.accentColor)
.frame(width: 6)
} else {
Rectangle()
.fill(.clear)
.frame(width: 6)
}
VStack {
HStack {
Text(item.tag)
.truncationMode(.tail)
.lineLimit(1)
.font(.system(size: 14))
Spacer(minLength: 6)
}
Spacer(minLength: 6)
HStack(alignment: .center) {
Text(item.type)
.foregroundColor(.secondary)
.font(.system(size: 12))
Spacer(minLength: 6)
if item.urlTestDelay > 0 {
Text(item.delayString)
.foregroundColor(item.delayColor)
.font(.system(size: 11))
}
}
}
.frame(height: 36)
.padding([.top, .bottom, .trailing], 12)
}
.background(backgroundColor)
.onTapGesture {
if group.selectable, group.selected != item.tag {
Task.detached {
selectOutbound()
}
}
}
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
private func selectOutbound() {
do {
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)!.selectOutbound(group.tag, outboundTag: item.tag)
var newGroup = group
newGroup.selected = item.tag
_group.wrappedValue = newGroup
} catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
}
private var backgroundColor: Color {
#if os(iOS)
return Color(uiColor: .secondarySystemGroupedBackground)
#elseif os(macOS)
return Color(nsColor: .textBackgroundColor)
#endif
}
}
@@ -0,0 +1,119 @@
import Libbox
import Library
import SwiftUI
public struct GroupListView: View {
@State private var isLoading = true
@State private var connectTask: Task<Void, Error>?
@State private var commandClient: LibboxCommandClient?
@State private var groups: [OutboundGroup] = []
@State private var groupExpand: [String: Bool] = [:]
public init() {}
public var body: some View {
VStack {
if isLoading {
Text("Loading...")
} else if !groups.isEmpty {
ScrollView {
VStack {
ForEach(groups, id: \.hashValue) { it in
GroupView(it, Binding(get: {
groupExpand[it.tag] ?? it.selectable
}, set: { newValue in
groupExpand[it.tag] = newValue
}))
Spacer()
}
}.padding()
}
} else {
Text("Empty groups")
}
}
.onAppear(perform: doReload)
.onDisappear {
connectTask?.cancel()
if let commandClient {
try? commandClient.disconnect()
}
commandClient = nil
}
.navigationTitle("Groups")
}
private func doReload() {
connectTask?.cancel()
connectTask = Task.detached {
await connect()
}
}
private func connect() async {
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]()
for goGroup in goGroups {
var items = [OutboundGroupItem]()
let itemIterator = goGroup.getItems()!
while itemIterator.hasNext() {
let goItem = itemIterator.next()!
items.append(OutboundGroupItem(tag: goItem.tag, type: goItem.type, urlTestTime: Date(timeIntervalSince1970: Double(goItem.urlTestTime)), urlTestDelay: UInt16(goItem.urlTestDelay)))
}
groups.append(OutboundGroup(tag: goGroup.tag, type: goGroup.type, selected: goGroup.selected, selectable: goGroup.selectable, items: items))
}
self.groups = groups
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!)
}
}
}
@@ -0,0 +1,141 @@
import Libbox
import Library
import SwiftUI
public struct GroupView: View {
private var expland: Binding<Bool>
@State private var group: OutboundGroup
@State private var geometryWidth: CGFloat = 300
@State private var errorPresented = false
@State private var errorMessage = ""
public init(_ group: OutboundGroup, _ expland: Binding<Bool>) {
self.group = group
self.expland = expland
}
private var title: some View {
HStack {
Text(group.tag)
.font(.system(size: 17))
Text(group.displayType)
.font(.system(size: 13))
.foregroundColor(.secondary)
Text("\(group.items.count)")
.font(.system(size: 11))
.padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4))
.background(Color.gray.opacity(0.5))
.cornerRadius(4)
Button {
expland.wrappedValue = !expland.wrappedValue
} label: {
if expland.wrappedValue {
Image(systemName: "arrow.down.to.line")
} else {
Image(systemName: "arrow.up.to.line")
}
}
#if os(macOS)
.buttonStyle(.plain)
#endif
Button {
Task.detached {
doURLTest()
}
} label: {
Image(systemName: "bolt.fill")
}
#if os(macOS)
.buttonStyle(.plain)
#endif
Spacer(minLength: 6)
}
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
public var body: some View {
Section {
if expland.wrappedValue {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()),
count: explandColumnCount()))
{
ForEach(group.items, id: \.tag) { it in
GroupItemView($group, it)
}
}
} else {
VStack {
ForEach(Array(itemGroups.enumerated()), id: \.offset) { items in
HStack {
ForEach(items.element, id: \.tag) { it in
Rectangle()
.fill(it.delayColor)
.frame(width: 10, height: 10)
}
}.frame(maxWidth: .infinity, alignment: .topLeading)
}
}
}
} header: {
title
.frame(maxWidth: .infinity, alignment: .topLeading)
}
.background {
GeometryReader { geometry in
Rectangle()
.fill(.clear)
.frame(height: 1)
.onChange(of: geometry.size.width) { newValue in
geometryWidth = newValue
}
.onAppear {
geometryWidth = geometry.size.width
}
}.padding()
}
}
private var itemGroups: [[OutboundGroupItem]] {
let count = Int(Int(geometryWidth) / 20)
if count == 0 {
return [group.items]
} else {
return group.items.chunked(
into: count
)
}
}
private func explandColumnCount() -> Int {
let count = Int(Int(geometryWidth) / 180)
#if os(iOS)
return count < 2 ? 2 : count
#else
return count < 1 ? 1 : count
#endif
}
private func doURLTest() {
do {
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)!.urlTest(group.tag)
} catch {
errorMessage = error.localizedDescription
errorPresented = true
}
}
}
private extension Array {
func chunked(into size: Int) -> [[Element]] {
stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}
@@ -0,0 +1,32 @@
import Foundation
import SwiftUI
public struct OutboundGroup: Codable {
let tag: String
let type: String
var selected: String
let selectable: Bool
let items: [OutboundGroupItem]
var hashValue: Int {
var value = tag.hashValue
(value, _) = value.addingReportingOverflow(selected.hashValue)
for item in items {
(value, _) = value.addingReportingOverflow(item.urlTestTime.hashValue)
}
return value
}
}
public extension OutboundGroup {
var displayType: String {
switch type {
case "selector":
return "Selector"
case "urltest":
return "URLTest"
default:
return "Unknown"
}
}
}
@@ -0,0 +1,29 @@
import Foundation
import SwiftUI
public struct OutboundGroupItem: Codable {
public let tag: String
public let type: String
public let urlTestTime: Date
public let urlTestDelay: UInt16
}
public extension OutboundGroupItem {
var delayString: String {
"\(urlTestDelay)ms"
}
var delayColor: Color {
switch urlTestDelay {
case 0:
return .gray
case ..<800:
return .green
case 800 ..< 1500:
return .yellow
default:
return .orange
}
}
}