diff options
| author | Dustin Mierau <dustin@mierau.me> | 2023-12-08 16:12:47 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2023-12-08 16:13:03 -0800 |
| commit | 84aaa9ef0a1be6986e38d7e4f58e07d3cb84979e (patch) | |
| tree | c0b3a36f14295a921413b7db0f1b37c9ec10e715 /Hotline | |
| parent | 537ed932a4c8d639089f276e322de4e946b0d26b (diff) | |
Move tracker client to async/await. TrackerView uses Model now. Fix string encodings for MacRoman and japanese.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Hotline/HotlineProtocol.swift | 5 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineTrackerClient.swift | 134 | ||||
| -rw-r--r-- | Hotline/HotlineApp.swift | 4 | ||||
| -rw-r--r-- | Hotline/Models/Hotline.swift | 16 | ||||
| -rw-r--r-- | Hotline/Models/Server.swift | 16 | ||||
| -rw-r--r-- | Hotline/Models/Tracker.swift | 10 | ||||
| -rw-r--r-- | Hotline/Utility/FoundationExtensions.swift | 51 | ||||
| -rw-r--r-- | Hotline/Views/ChatView.swift | 25 | ||||
| -rw-r--r-- | Hotline/Views/FilesView.swift | 157 | ||||
| -rw-r--r-- | Hotline/Views/HotlineView.swift | 17 | ||||
| -rw-r--r-- | Hotline/Views/TrackerView.swift | 48 |
11 files changed, 187 insertions, 296 deletions
diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift index e3fcd12..c126d03 100644 --- a/Hotline/Hotline/HotlineProtocol.swift +++ b/Hotline/Hotline/HotlineProtocol.swift @@ -143,6 +143,7 @@ class HotlineFile: Identifiable, Hashable { // data.readUInt32(at: 12)! // reserved // let nameScript = data.readUInt16(at: 16)! // name script +// print("NAME SCRIPT: \(nameScript)") let nameLength = data.readUInt16(at: 18)! self.name = data.readString(at: 20, length: Int(nameLength))! @@ -269,7 +270,7 @@ struct HotlineTransactionField { for name in pathComponents { pathData.appendUInt16(0) - var nameData = name.data(using: .ascii, allowLossyConversion: true) + var nameData = name.data(using: .macOSRoman, allowLossyConversion: true) if nameData == nil { nameData = Data() } @@ -315,7 +316,7 @@ struct HotlineTransactionField { } func getString() -> String? { - return String(data: self.data, encoding: .utf8) ?? String(data: self.data, encoding: .ascii) + return self.data.readString(at: 0, length: self.data.count) } func getUser() -> HotlineUser { diff --git a/Hotline/Hotline/HotlineTrackerClient.swift b/Hotline/Hotline/HotlineTrackerClient.swift index f93949e..9bcf631 100644 --- a/Hotline/Hotline/HotlineTrackerClient.swift +++ b/Hotline/Hotline/HotlineTrackerClient.swift @@ -7,37 +7,37 @@ enum HotlineTrackerStatus: Int { case connected } -struct HotlineTracker: Identifiable { +struct HotlineTracker: Identifiable, Equatable { + let id: UUID = UUID() var address: String var port: UInt16 - var servers: [HotlineServer] = [] - var expanded: Bool = false - - var id: String { get { return self.address } } - + init(_ address: String, port: UInt16 = 5498) { self.address = address self.port = port } + + static func == (lhs: HotlineTracker, rhs: HotlineTracker) -> Bool { + return lhs.address == rhs.address && lhs.port == rhs.port + } } -@Observable class HotlineTrackerClient { static let magicPacket = Data([ 0x48, 0x54, 0x52, 0x4B, // 'HTRK' 0x00, 0x01 // Version ]) - @ObservationIgnored private var serverAddress: NWEndpoint.Host - @ObservationIgnored private var serverPort: NWEndpoint.Port - @ObservationIgnored private var connection: NWConnection? - @ObservationIgnored private var bytes = Data() - @ObservationIgnored private var maxDataLength: Int = 0 - @ObservationIgnored private var serverCount: Int = 0 + private var tracker: HotlineTracker + private var connectionStatus: HotlineTrackerStatus = .disconnected + private var servers: [HotlineServer] = [] - var tracker: HotlineTracker - var connectionStatus: HotlineTrackerStatus = .disconnected - var servers: [HotlineServer] = [] + private var serverAddress: NWEndpoint.Host + private var serverPort: NWEndpoint.Port + private var connection: NWConnection? + private var bytes = Data() + private var maxDataLength: Int = 0 + private var serverCount: Int = 0 init() { let t = HotlineTracker("hltracker.com") @@ -51,29 +51,16 @@ class HotlineTrackerClient { self.serverAddress = NWEndpoint.Host(tracker.address) self.serverPort = NWEndpoint.Port(rawValue: tracker.port)! } - - func fetch(callback: (() -> Void)? = nil) { - self.reset() - self.connect(callback) - } - - func fetch2(address: String, port: Int, callback: (([Server]) -> Void)? = nil) { + + func fetchServers(address: String, port: Int, callback: (([HotlineServer]) -> Void)? = nil) async -> [HotlineServer] { self.serverAddress = NWEndpoint.Host(address) self.serverPort = NWEndpoint.Port(rawValue: UInt16(port))! self.reset() - self.connect { [weak self] in - var allServers: [Server] = [] - - if let servers = self?.servers { - for server in servers { - let s = Server(name: server.name!, description: server.description, address: server.address, port: Int(server.port)) - allServers.append(s) - } - } - - DispatchQueue.main.async { - callback?(allServers) + + return await withCheckedContinuation { [weak self] continuation in + self?.connect { [weak self] in + continuation.resume(returning: self?.servers ?? []) } } } @@ -81,6 +68,7 @@ class HotlineTrackerClient { private func reset() { self.maxDataLength = 0 self.serverCount = 0 + self.servers = [] } private func connect(_ callback: (() -> Void)? = nil) { @@ -88,37 +76,30 @@ class HotlineTrackerClient { self.connection?.stateUpdateHandler = { [weak self] (newState: NWConnection.State) in switch newState { case .ready: - print("READY TO SEND AND RECEIVE DATA") - DispatchQueue.main.async { - self?.connectionStatus = .connected - } + self?.connectionStatus = .connected self?.sendMagic() case .cancelled: - print("CONNECTION CANCELLED") + self?.connectionStatus = .disconnected DispatchQueue.main.async { - self?.connectionStatus = .disconnected callback?() } case .failed(let err): - print("CONNECTION ERROR \(err)") + print("HotlineTrackerClient: Connection error \(err)") + self?.connectionStatus = .disconnected DispatchQueue.main.async { - self?.connectionStatus = .disconnected callback?() } default: - print("CONNECTION OTHER THING") + return } } - DispatchQueue.main.async { - self.connectionStatus = .connecting - } + self.connectionStatus = .connecting self.connection?.start(queue: .global()) } private func disconnect() { guard let c = connection else { - print("HotlineTracker: already disconnected") return } @@ -128,7 +109,7 @@ class HotlineTrackerClient { private func sendMagic() { guard let c = connection else { - print("HotlineTracker: invalid connection to send magic.") + print("HotlineTrackerClient: invalid connection to send magic.") return } @@ -136,39 +117,33 @@ class HotlineTrackerClient { c.send(content: HotlineTrackerClient.magicPacket, completion: .contentProcessed { [weak self] (error) in if let err = error { - print("HotlineTracker: sending magic failed \(err)") + print("HotlineTrackerClient: sending magic failed \(err)") return } - print("HotlineTracker: sent magic!") - self?.receiveMagic() }) } private func receiveMagic() { guard let c = connection else { - print("HotlineTracker: invalid connection to receive magic.") + print("HotlineTrackerClient: invalid connection to receive magic.") return } - print("HotlineTracker: receiving...") c.receive(minimumIncompleteLength: 6, maximumLength: 6) { [weak self] (data, context, isComplete, error) in guard let self = self, let data = data else { return } if data.isEmpty || !data.elementsEqual(HotlineTrackerClient.magicPacket) { - print("HotlineTracker: invalid magic response") + print("HotlineTrackerClient: invalid magic response") self.disconnect() return } - // if let data = data, !data.isEmpty { - print("HotlineTracker: received magic response!") - // } if let error = error { - print("HotlineTracker: receive error \(error)") + print("HotlineTrackerClient: receive error \(error)") } else { self.receiveHeader() @@ -178,35 +153,29 @@ class HotlineTrackerClient { private func receiveHeader() { guard let c = connection else { - print("HotlineTracker: invalid connection to receive header.") + print("HotlineTrackerClient: invalid connection to receive header.") return } - print("HotlineTracker: receiving...") c.receive(minimumIncompleteLength: 8, maximumLength: 8) { [weak self] (data, context, isComplete, error) in guard let self = self else { return } if let error = error { - print("HotlineTracker: receive error \(error)") + print("HotlineTrackerClient: receive error \(error)") self.disconnect() return } if let data = data, !data.isEmpty { - print("HotlineTracker: received \(data.count) header bytes") - self.maxDataLength = Int(data[2]) * 0xFF + Int(data[3]) self.maxDataLength -= 4 - print("HotlineTracker: message size = \(self.maxDataLength)") - self.serverCount = Int(data[4]) * 256 + Int(data[5]) - print("HotlineTracker: server count = \(self.serverCount)") } if let error = error { - print("HotlineTracker: receive error \(error)") + print("HotlineTrackerClient: receive error \(error)") } else { self.receiveListing() @@ -216,33 +185,32 @@ class HotlineTrackerClient { private func receiveListing() { guard let c = connection else { - print("HotlineTracker: invalid connection to receive data.") + print("HotlineTrackerClient: invalid connection to receive data.") return } - print("HotlineTracker: receiving...") c.receive(minimumIncompleteLength: 1, maximumLength: 64 * 1024) { [weak self] (data, context, isComplete, error) in guard let self = self else { return } if let data = data, !data.isEmpty { - print("HotlineTracker: received \(data.count) bytes") self.bytes.append(contentsOf: data) if bytes.count >= maxDataLength { - self.disconnect() self.parseListing() + print("HotlineTrackerClient: Found \(self.servers.count) servers on tracker \(self.serverAddress):\(self.serverPort)") + self.disconnect() return } } if let error = error { - print("HotlineTracker: receive error \(error)") + print("HotlineTrackerClient: receive error \(error)") self.disconnect() } else { - print("HotlineTracker: not complete") + print("HotlineTrackerClient: not complete") self.receiveListing() } } @@ -258,12 +226,13 @@ class HotlineTrackerClient { // Description size (1 byte) // Description (description size) + let trackerSeparatorRegex = /^[-]+$/ var foundServers: [HotlineServer] = [] var cursor = 0 for _ in 1...self.serverCount { if self.bytes.count < cursor + 12 { - print("HotlineTracker: Data isn't long enough for next server") + print("HotlineTrackerClient: Data isn't long enough for next server") break } @@ -281,19 +250,18 @@ class HotlineTrackerClient { if let name = serverName, let desc = serverDescription { - let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) - foundServers.append(server) + let validName = try? trackerSeparatorRegex.prefixMatch(in: name) + if validName == nil { +// if name.range(of: regex, options: .regularExpression) == nil { + let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) + foundServers.append(server) + } } cursor += 10 + nameByteCount + descByteCount } } - DispatchQueue.main.async { - self.tracker.servers = foundServers -// print("CALLING CALLBACK") - self.servers = foundServers - } - + self.servers = foundServers } } diff --git a/Hotline/HotlineApp.swift b/Hotline/HotlineApp.swift index 82fa7d4..67397a2 100644 --- a/Hotline/HotlineApp.swift +++ b/Hotline/HotlineApp.swift @@ -18,7 +18,6 @@ struct HotlineApp: App { @State private var appState = HotlineState() @State private var hotline = HotlineClient() - @State private var tracker = HotlineTrackerClient(tracker: HotlineTracker("hltracker.com")) private var model = Hotline(trackerClient: HotlineTrackerClient()) @@ -28,7 +27,8 @@ struct HotlineApp: App { TrackerView() .environment(appState) .environment(hotline) - .environment(tracker) +// .environment(tracker) + .environment(model) } // .modelContainer(sharedModelContainer) } diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift index 70f68a9..b83dc80 100644 --- a/Hotline/Models/Hotline.swift +++ b/Hotline/Models/Hotline.swift @@ -3,19 +3,21 @@ import SwiftUI @Observable final class Hotline { let trackerClient: HotlineTrackerClient - - init(trackerClient: HotlineTrackerClient) { self.trackerClient = trackerClient } - @MainActor func getServers(address: String, port: Int) async { - let fetchedServers = await withCheckedContinuation { [weak self] continuation in - self?.trackerClient.fetch() { - continuation.resume(returning: []) + @MainActor func getServers(address: String, port: Int = Tracker.defaultPort) async -> [Server] { + let fetchedServers: [HotlineServer] = await self.trackerClient.fetchServers(address: address, port: port) + + var servers: [Server] = [] + + for s in fetchedServers { + if let serverName = s.name { + servers.append(Server(name: serverName, description: s.description, address: s.address, port: Int(s.port), users: Int(s.users))) } } - + return servers } } diff --git a/Hotline/Models/Server.swift b/Hotline/Models/Server.swift index c2063ea..5a6ba80 100644 --- a/Hotline/Models/Server.swift +++ b/Hotline/Models/Server.swift @@ -1,16 +1,26 @@ import SwiftUI -@Observable final class Server { +@Observable final class Server: Identifiable, Equatable { + let id: UUID = UUID() let name: String let description: String? - let users: Int = 0 + let users: Int let address: String let port: Int - init(name: String, description: String?, address: String, port: Int) { + init(name: String, description: String?, address: String, port: Int, users: Int = 0) { self.name = name self.description = description self.address = address self.port = port + self.users = users + } + + static func == (lhs: Server, rhs: Server) -> Bool { + return lhs.id == rhs.id + } + + static func == (lhs: HotlineServer, rhs: Server) -> Bool { + return lhs.name == rhs.name && lhs.address == rhs.address && lhs.port == rhs.port } } diff --git a/Hotline/Models/Tracker.swift b/Hotline/Models/Tracker.swift index c9dfb5c..66d7976 100644 --- a/Hotline/Models/Tracker.swift +++ b/Hotline/Models/Tracker.swift @@ -1,20 +1,22 @@ import SwiftUI @Observable final class Tracker { + static let defaultPort: Int = 5498 + let service: HotlineTrackerClient let address: String let port: Int var servers: [Server] = [] - init(address: String, port: Int = 5498, service: HotlineTrackerClient) { + init(address: String, port: Int = defaultPort, service: HotlineTrackerClient) { self.address = address self.port = port self.service = service } func fetchServers() { - self.service.fetch2(address: self.address, port: self.port) { servers in - self.servers = servers - } +// self.service.fetch2(address: self.address, port: self.port) { hotlineServers in +// self.servers = servers +// } } } diff --git a/Hotline/Utility/FoundationExtensions.swift b/Hotline/Utility/FoundationExtensions.swift index 000314e..719df40 100644 --- a/Hotline/Utility/FoundationExtensions.swift +++ b/Hotline/Utility/FoundationExtensions.swift @@ -5,6 +5,24 @@ enum Endianness { case little } +func detectStringEncoding(of data: Data) -> String.Encoding { +// var nsString: NSString? = nil +// guard case let rawValue = NSString.stringEncoding(for: data, encodingOptions: [.allowLossyKey: false], convertedString: nil, usedLossyConversion: nil) else { +// print("NO ENCODING") +// return nil +// } + + let rawValue = NSString.stringEncoding(for: data, encodingOptions: [.allowLossyKey: false], convertedString: nil, usedLossyConversion: nil) + +// let cfEnc: CFStringEncoding = CFStringConvertNSStringEncodingToEncoding(rawValue); + +// let encodingString: CFString? = CFStringGetNameOfEncoding(cfEnc) + + print("DETECTED ENCODING \(rawValue)") + + return String.Encoding(rawValue: rawValue) +} + extension Data { init(_ val: UInt8) { self.init() @@ -50,16 +68,37 @@ extension Data { } return self.subdata(in: offset..<(offset + length)) } - + func readString(at offset: Int, length: Int) -> String? { - var str: String? + let subdata = self[offset..<(offset + length)] + if subdata.count == 0 { + return "" + } + + let allowedEncodings = [ + NSUTF8StringEncoding, + NSShiftJISStringEncoding, + NSUnicodeStringEncoding, + NSWindowsCP1251StringEncoding + ] + + var decodedNSString: NSString? + let rawValue = NSString.stringEncoding(for: subdata, encodingOptions: [.allowLossyKey: false], convertedString: &decodedNSString, usedLossyConversion: nil) + + if allowedEncodings.contains(rawValue) { + return decodedNSString as? String + } + + else if rawValue > 1 { + print("ENCODING FOUND \(rawValue)") + } - str = String(data: self[offset..<(offset + length)], encoding: .utf8) - if str == nil { - str = String(data: self[offset..<(offset + length)], encoding: .ascii) + var macStr = String(data: subdata, encoding: .macOSRoman) + if macStr == nil { + macStr = String(data: subdata, encoding: .nonLossyASCII) } - return str + return macStr } func readPString(at offset: Int) -> (String?, Int) { diff --git a/Hotline/Views/ChatView.swift b/Hotline/Views/ChatView.swift index 28e548d..1b3ba52 100644 --- a/Hotline/Views/ChatView.swift +++ b/Hotline/Views/ChatView.swift @@ -25,12 +25,27 @@ struct ChatView: View { ForEach(hotline.chatMessages) { msg in if msg.type == .agreement { VStack(alignment: .leading) { - Text(msg.text) - .font(.system(size: 11, weight: .regular, design: .monospaced)) + VStack(alignment: .leading, spacing: 0) { + Text(msg.text) + .textSelection(.enabled) + .padding() + .opacity(0.75) + HStack { + Spacer() + Text((hotline.server?.name ?? "") + " Server Agreement") + .font(.caption) + .fontWeight(.medium) + .opacity(0.4) + .lineLimit(1) + .truncationMode(.middle) + Spacer() + } .padding() - .opacity(0.75) - .background(colorScheme == .dark ? Color(white: 0.1) : Color(white: 0.96)) - .cornerRadius(16) + .background(colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.9)) + } + .background(colorScheme == .dark ? Color(white: 0.1) : Color(white: 0.96)) + .cornerRadius(16) + .frame(maxWidth: .infinity) } .padding() } diff --git a/Hotline/Views/FilesView.swift b/Hotline/Views/FilesView.swift index 091d718..23cd914 100644 --- a/Hotline/Views/FilesView.swift +++ b/Hotline/Views/FilesView.swift @@ -4,144 +4,6 @@ import UniformTypeIdentifiers struct FileView: View { @Environment(HotlineClient.self) private var hotline - @State var file: HotlineFile - @State var loaded = false - @State var fileList: [HotlineFile] = [] - - let depth: Int - let path: [String] - - static let byteFormatter = ByteCountFormatter() - - private func formattedFileSize(_ fileSize: UInt32) -> String { - // let bcf = ByteCountFormatter() - FileView.byteFormatter.allowedUnits = [.useAll] - FileView.byteFormatter.countStyle = .file - return FileView.byteFormatter.string(fromByteCount: Int64(fileSize)) - } - - private func fileIcon(name: String) -> UIImage { - // func utTypeForFilename(_ filename: String) -> UTType? { - let fileExtension = (name as NSString).pathExtension - if let fileType = UTType(filenameExtension: fileExtension) { - print("\(name) \(fileExtension) = \(fileType)") - - if fileType.isSubtype(of: .movie) { - return UIImage(systemName: "play.rectangle")! - } - else if fileType.isSubtype(of: .image) { - return UIImage(systemName: "photo")! - } - else if fileType.isSubtype(of: .archive) { - return UIImage(systemName: "doc.zipper")! - } - else if fileType.isSubtype(of: .text) { - return UIImage(systemName: "doc.text")! - } - else { - return UIImage(systemName: "doc")! - } - } - - return UIImage(systemName: "doc")! - } - - var body: some View { - if file.isFolder { - DisclosureGroup { - if !loaded { - Text("Loading...") - .task { - if !loaded { - hotline.sendGetFileList(path: path) { - print("FETCHED!") - } reply: { newFiles in - print("GOT FILES REPLY?", newFiles) - - fileList = newFiles - loaded = true - } - } - } - } - else { - FileListView(fileList: fileList, depth: depth + 1, path: path + [file.name]) - } - } label: { - VStack { - HStack { - HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { - Image(systemName: "folder.fill") - } - .frame(minWidth: 25) - Text(file.name).fontWeight(.medium) - Spacer() - Text("\(file.fileSize)").foregroundStyle(.secondary) - } - } - .frame(height: 44) - .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - - // Divider() - // .padding(EdgeInsets(top: 0, leading: 16 + 25 + 8, bottom: 0, trailing: 0)) - } - .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: depth == 0 ? 16 : 0)) - .background(Color(uiColor: UIColor.systemBackground)) - } - else { - VStack { - HStack { - HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { - Image(uiImage: fileIcon(name: file.name)) - .renderingMode(.template) - .foregroundColor(.accentColor) - } - .frame(minWidth: 25) - Text(file.name) - Spacer() - Text(formattedFileSize(file.fileSize)).foregroundStyle(.gray) - } - .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 0)) - } - .frame(height: 44) - .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: depth == 0 ? 16 : 0)) - .background(Color(uiColor: UIColor.systemBackground)) - - Divider() - .padding(EdgeInsets(top: 0, leading: 16 + 25 + 8, bottom: 0, trailing: 0)) - } - } -} - -struct FileListView: View { - @Environment(HotlineClient.self) private var hotline - - @State private var fetched = false - @State private var loaded = false - @State var fileList: [HotlineFile] = [] - @State var expanded = false - - var depth = 0 - - var path: [String] = [] - - var body: some View { - LazyVStack(alignment: .leading, spacing: 0) { - Section { - ForEach(fileList, id: \.self) { file in - FileView(file: file, depth: depth, path: path + [file.name]) - } - } - Spacer() - } - .listStyle(.plain) - .animation(.default, value: fileList) - } -} - -struct BestFileView: View { - @Environment(HotlineClient.self) private var hotline - @State var expanded = false var file: HotlineFile @@ -150,7 +12,7 @@ struct BestFileView: View { if file.isFolder { DisclosureGroup(isExpanded: $expanded) { ForEach(file.files!) { childFile in - BestFileView(file: childFile) + FileView(file: childFile) .frame(height: 44) } } label: { @@ -159,9 +21,9 @@ struct BestFileView: View { Image(systemName: "folder.fill") } .frame(minWidth: 25) - Text(file.name).fontWeight(.medium) + Text(file.name).fontWeight(.medium).lineLimit(1).truncationMode(.tail) Spacer() - Text("\(file.fileSize)").foregroundStyle(.secondary) + Text("\(file.fileSize)").foregroundStyle(.secondary).lineLimit(1) } } .onChange(of: expanded) { @@ -174,11 +36,12 @@ struct BestFileView: View { HStack { HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { Image(uiImage: fileIcon(name: file.name)) + .renderingMode(.template) } .frame(minWidth: 25) - Text(file.name).fontWeight(.medium) + Text(file.name).lineLimit(1).truncationMode(.tail) Spacer() - Text(formattedFileSize(file.fileSize)).foregroundStyle(.secondary) + Text(formattedFileSize(file.fileSize)).foregroundStyle(.secondary).lineLimit(1) } } } @@ -187,9 +50,9 @@ struct BestFileView: View { private func formattedFileSize(_ fileSize: UInt32) -> String { // let bcf = ByteCountFormatter() - BestFileView.byteFormatter.allowedUnits = [.useAll] - BestFileView.byteFormatter.countStyle = .file - return BestFileView.byteFormatter.string(fromByteCount: Int64(fileSize)) + FileView.byteFormatter.allowedUnits = [.useAll] + FileView.byteFormatter.countStyle = .file + return FileView.byteFormatter.string(fromByteCount: Int64(fileSize)) } private func fileIcon(name: String) -> UIImage { @@ -228,7 +91,7 @@ struct FilesView: View { NavigationStack { List(hotline.fileList) { file in // OutlineGroup(hotline.fileList, children: \.files, expanded: $expandedFolders) { file in - BestFileView(file: file) + FileView(file: file) .frame(height: 44) // } } diff --git a/Hotline/Views/HotlineView.swift b/Hotline/Views/HotlineView.swift deleted file mode 100644 index f950dd7..0000000 --- a/Hotline/Views/HotlineView.swift +++ /dev/null @@ -1,17 +0,0 @@ -import SwiftUI - -struct HotlineView: View { - @Environment(HotlineState.self) private var appState - @Environment(HotlineClient.self) private var hotline - @Environment(HotlineTrackerClient.self) private var tracker - - var body: some View { - TrackerView() - } -} - -#Preview { - HotlineView() - .environment(HotlineClient()) - .environment(HotlineTrackerClient(tracker: HotlineTracker("hltracker.com"))) -} diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift index 1c61cf4..2d2032e 100644 --- a/Hotline/Views/TrackerView.swift +++ b/Hotline/Views/TrackerView.swift @@ -5,23 +5,23 @@ struct TrackerView: View { // @Environment(\.modelContext) private var modelContext // @Query private var items: [Item] - @Environment(HotlineState.self) private var appState @Environment(HotlineClient.self) private var hotline - @Environment(HotlineTrackerClient.self) private var trackerClient @Environment(Hotline.self) private var model: Hotline @Environment(\.colorScheme) var colorScheme // @State private var tracker = Tracker(address: "hltracker.com", service: trackerService) - @State private var selectedServer: HotlineServer? + @State private var servers: [Server] = [] + @State private var selectedServer: Server? @State var scrollOffset: CGFloat = CGFloat.zero + @State private var initialLoadComplete = false - func shouldDisplayDescription(server: HotlineServer) -> Bool { - guard let name = server.name, let desc = server.description else { + func shouldDisplayDescription(server: Server) -> Bool { + guard let desc = server.description else { return false } - return desc.count > 0 && desc != name && !desc.contains(/^-+/) + return desc.count > 0 && desc != server.name } func connectionStatusToProgress(status: HotlineClientStatus) -> Double { @@ -43,10 +43,11 @@ struct TrackerView: View { return (v - lower) / (upper - lower) } + func updateServers() async { + self.servers = await model.getServers(address: "tracker.preterhuman.net") + } + var body: some View { - @Bindable var config = appState - @Bindable var client = hotline - ZStack(alignment: .center) { VStack(alignment: .center) { ZStack(alignment: .top) { @@ -80,18 +81,18 @@ struct TrackerView: View { } ObservableScrollView(scrollOffset: $scrollOffset) { LazyVStack(alignment: .leading) { - ForEach(tracker.servers) { server in + ForEach(self.servers) { server in VStack(alignment: .leading) { HStack(alignment: .firstTextBaseline) { Image(systemName: "globe.americas.fill").font(.title3) VStack(alignment: .leading) { - Text(server.name!).font(.title3).fontWeight(.medium) + Text(server.name).font(.title3).fontWeight(.medium) if shouldDisplayDescription(server: server) { Spacer() Text(server.description!).opacity(0.5).font(.system(size: 16)) } Spacer() - Text("\(server.address)").opacity(0.3).font(.system(size: 13)) + Text("\(server.address):" + String(format: "%i", server.port)).opacity(0.3).font(.system(size: 13)) } Spacer() if server.users > 0 { @@ -101,14 +102,14 @@ struct TrackerView: View { if server == selectedServer { Spacer(minLength: 16) - if hotline.server == server && hotline.connectionStatus != .disconnected { + if hotline.connectionStatus != .disconnected && hotline.server! == server { ProgressView(value: connectionStatusToProgress(status: hotline.connectionStatus)) .frame(minHeight: 10) .accentColor(colorScheme == .dark ? .white : .black) } else { Button("Connect") { - hotline.connect(to: server) + hotline.connect(to: HotlineServer(address: server.address, port: UInt16(server.port), users: UInt16(server.users), name: server.name, description: server.description)) } .bold() .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24)) @@ -142,13 +143,19 @@ struct TrackerView: View { } .padding(EdgeInsets(top: 75, leading: 0, bottom: 0, trailing: 0)) } - .refreshable { - await withCheckedContinuation { continuation in - tracker.fetch() { - continuation.resume() + .overlay { + if !initialLoadComplete { + VStack { + ProgressView() + .controlSize(.large) } + .frame(maxWidth: .infinity) } } + .refreshable { + initialLoadComplete = true + await updateServers() + } } .fullScreenCover(isPresented: Binding(get: { return hotline.connectionStatus == .loggedIn }, set: { _ in }), onDismiss: { @@ -159,7 +166,9 @@ struct TrackerView: View { .background(Color(uiColor: UIColor.systemGroupedBackground)) .frame(maxWidth: .infinity) .task { - tracker.fetch() + await updateServers() + initialLoadComplete = true +// tracker.fetch() } } } @@ -167,7 +176,6 @@ struct TrackerView: View { #Preview { TrackerView() .environment(HotlineClient()) - .environment(HotlineTrackerClient(tracker: HotlineTracker("hltracker.com"))) .environment(HotlineState()) // .modelContainer(for: Item.self, inMemory: true) } |