import Foundation /// Which side of the room the setup screen is preparing. Once a room is /// running the distinction only changes labels: every peer can send and /// receive, just like the CLI. enum RoomMode: String, CaseIterable, Identifiable { case send case receive var id: String { rawValue } var title: String { switch self { case .send: return "Send" case .receive: return "Receive" } } var icon: String { switch self { case .send: return "arrow.up.circle.fill" case .receive: return "arrow.down.circle.fill" } } } /// One file's progress as reported by the Go engine. The JSON field names /// match `swarm.FileStatus`; no conversion layer is needed. struct FileProgress: Codable, Identifiable, Hashable { let name: String let size: Int64 let haveChunks: Int64 let totalChunks: Int64 let complete: Bool var id: String { name } var fraction: Double { guard totalChunks > 0 else { return 0 } return min(1, max(0, Double(haveChunks) / Double(totalChunks))) } var formattedSize: String { ByteCountFormatter.string(fromByteCount: size, countStyle: .file) } } /// The whole room snapshot decoded from `ShareSession.statusJSON()`. The /// Go bridge keeps the same shape across launches so the UI never has to /// know about gomobile objects beyond the session itself. struct RoomStatus: Codable { var code: String var relay: String var outDir: String var running: Bool var peers: Int var files: [FileProgress] var error: String? var allComplete: Bool { !files.isEmpty && files.allSatisfy(\.complete) } var receivedFiles: [FileProgress] { files.filter(\.complete) } } /// Result of the relay's stateless ping/pong probe. `ok` is true only when /// the configured address accepted a connection and answered with pong, /// not merely when the TCP port was open. struct RelayCheck: Codable, Equatable { let ok: Bool let relay: String let latencyMs: Int64 let error: String? } /// A file that has already been copied into the app's seed directory. /// Holding the copied URL (rather than the security-scoped picker URL) /// means the Go engine can keep reading it after the picker goes away. struct PickedFile: Identifiable, Hashable { let id = UUID() let url: URL let name: String let size: Int64 var formattedSize: String { ByteCountFormatter.string(fromByteCount: size, countStyle: .file) } }