diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-01-03 23:55:39 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-01-03 23:55:39 -0800 |
| commit | cc6e0e35cf6e662ffc3ad24e2510265caa5cf52a (patch) | |
| tree | 908b99f7995edf9dc911209be656fb731c47c5ad /Hotline | |
| parent | 2286040715ff8c08c4114c60e81f087f92d53800 (diff) | |
Server windows restore with app session now. Implemented new login view so you can connect to specific IP servers no listed by tracker, login/pass supported (I think?), and a nicer connect progress display.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Application.swift | 26 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineClient.swift | 29 | ||||
| -rw-r--r-- | Hotline/Models/Server.swift | 7 | ||||
| -rw-r--r-- | Hotline/macOS/ChatView.swift | 1 | ||||
| -rw-r--r-- | Hotline/macOS/ServerView.swift | 266 | ||||
| -rw-r--r-- | Hotline/macOS/TrackerView.swift | 8 |
6 files changed, 239 insertions, 98 deletions
diff --git a/Hotline/Application.swift b/Hotline/Application.swift index 9c966d0..8e1b4d9 100644 --- a/Hotline/Application.swift +++ b/Hotline/Application.swift @@ -1,13 +1,18 @@ import SwiftUI import SwiftData +enum ServerWindowDestination: Hashable, Codable { + case server(server: Server) + case none +} + @main struct Application: App { #if os(iOS) private var model = Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient()) #endif - private var preferences = Prefs() + @State private var preferences = Prefs() var body: some Scene { #if os(iOS) @@ -24,20 +29,9 @@ struct Application: App { .defaultPosition(.center) WindowGroup(id: "server", for: Server.self) { $server in - if let s = server { - ServerView(server: s) - .frame(minWidth: 400, minHeight: 300) - .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())) - .environment(preferences) - .toolbar { - ToolbarItem(placement: .navigation) { - Image(systemName: "globe.americas.fill") - .resizable() - .scaledToFit() - .frame(width: 18) - } - } - } + ServerView(server: $server) + .frame(minWidth: 400, minHeight: 300) + .environment(preferences) } .defaultSize(width: 700, height: 800) .defaultPosition(.center) @@ -50,12 +44,10 @@ struct Application: App { // } // } -#if os(macOS) Settings { SettingsView() .environment(preferences) } -#endif #endif } diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift index 6193025..78597e6 100644 --- a/Hotline/Hotline/HotlineClient.swift +++ b/Hotline/Hotline/HotlineClient.swift @@ -119,7 +119,9 @@ class HotlineClient { let tcpOptions = NWProtocolTCP.Options() tcpOptions.enableKeepalive = true + tcpOptions.enableFastOpen = true tcpOptions.keepaliveInterval = 30 + tcpOptions.connectionTimeout = 30 let connectionParameters: NWParameters connectionParameters = NWParameters(tls: nil, tcp: tcpOptions) @@ -140,20 +142,25 @@ class HotlineClient { print("HotlineClient: connection waiting \(err)...") switch err { case .posix(let errCode): - switch errCode { - case .ETIMEDOUT: - self.disconnect() - case .ECONNREFUSED: - self.disconnect() - default: - self.disconnect() - break - } - print("HotlineClient: posix error code \(errCode)") + self.disconnect() +// switch errCode { +// case .ETIMEDOUT: +// self.disconnect() +// case .ECONNREFUSED: +// self.disconnect() +// default: +// self.disconnect() +// break +// } + case .tls(let errStatus): + print("HotlineClient: tls error code \(errStatus)") + self.disconnect() + case .dns(let errType): + print("HotlineClient: DNS Error code \(errType)") + self.disconnect() default: print("HotlineClient: error code \(err)") - } case .ready: print("HotlineClient: connection ready!") diff --git a/Hotline/Models/Server.swift b/Hotline/Models/Server.swift index da4f01f..62c6867 100644 --- a/Hotline/Models/Server.swift +++ b/Hotline/Models/Server.swift @@ -14,6 +14,13 @@ struct Server: Codable { self.port = port self.users = users } + + static func parseServerAddressAndPort(_ address: String) -> (String, Int) { + let url = URL(string: "hotline://\(address)") + let port = url?.port ?? HotlinePorts.DefaultServerPort + let host = url?.host(percentEncoded: false) ?? address + return (host, port) + } } extension Server: Identifiable { diff --git a/Hotline/macOS/ChatView.swift b/Hotline/macOS/ChatView.swift index d9e1f54..2c154d3 100644 --- a/Hotline/macOS/ChatView.swift +++ b/Hotline/macOS/ChatView.swift @@ -170,7 +170,6 @@ struct ChatView: View { } } } - .navigationTitle(self.model.serverTitle) .background(Color(nsColor: .textBackgroundColor)) .toolbar { ToolbarItem(placement: .primaryAction) { diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift index ddf6878..7589744 100644 --- a/Hotline/macOS/ServerView.swift +++ b/Hotline/macOS/ServerView.swift @@ -157,32 +157,22 @@ struct TransferItemView: View { } } -private func connectionStatusToProgress(status: HotlineClientStatus) -> Double { - switch status { - case .disconnected: - return 0.0 - case .connecting: - return 0.1 - case .connected: - return 0.25 - case .loggingIn: - return 0.5 - case .loggedIn: - return 1.0 - } -} - struct ServerView: View { - @Environment(Hotline.self) private var model: Hotline @Environment(Prefs.self) private var preferences: Prefs @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) private var colorScheme @Environment(\.controlActiveState) private var controlActiveState + @State private var model: Hotline = Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient()) @State private var agreementShown: Bool = false @State private var selection: MenuItem? = ServerView.menuItems.first - let server: Server? + @State private var connectAddress: String = "" + @State private var connectLogin: String = "" + @State private var connectPassword: String = "" + @Binding var server: Server? + static var menuItems = [ MenuItem(name: "Chat", image: "bubble", type: .chat), MenuItem(name: "News", image: "newspaper", type: .news, serverVersion: 150), @@ -190,26 +180,64 @@ struct ServerView: View { MenuItem(name: "Files", image: "folder", type: .files), ] - @MainActor func sendPreferences() { - if self.model.status == .loggedIn { - var options: HotlineUserOptions = HotlineUserOptions() - - if preferences.refusePrivateMessages { - options.update(with: .refusePrivateMessages) - } - - if preferences.refusePrivateChat { - options.update(with: .refusePrivateChat) - } - - if preferences.enableAutomaticMessage { - options.update(with: .automaticResponse) + var connectForm: some View { + GroupBox { + Form { + Group { + TextField(text: $connectAddress) { + Text("Address:") + } + TextField(text: $connectLogin, prompt: Text("optional")) { + Text("Login:") + } + SecureField(text: $connectPassword, prompt: Text("optional")) { + Text("Password:") + } + } + .textFieldStyle(.roundedBorder) + .controlSize(.regular) + + HStack { + Button { + print("SAVE BOOKMARK... SOMEHOW") + } label: { + Text("Save...") + } + .controlSize(.regular) + .buttonStyle(.automatic) + .help("Save server as bookmark") + + Spacer() + + Button { + dismiss() + } label: { + Text("Cancel") + } + .controlSize(.regular) + .buttonStyle(.automatic) + .keyboardShortcut(.cancelAction) + + Button { + let (a, p) = Server.parseServerAddressAndPort(connectAddress) + server = Server(name: nil, description: nil, address: a, port: p, users: 0) + Task { + await connectToServer() + } + } label: { + Text("Connect") + } + .controlSize(.regular) + .buttonStyle(.automatic) + .keyboardShortcut(.defaultAction) + } + .padding(.top, 8) + } - - print("Updating preferences with server") - - self.model.sendUserInfo(username: preferences.username, iconID: preferences.userIconID, options: options, autoresponse: preferences.automaticMessage) + .padding() } + .frame(maxWidth: 350) + .padding() } var navigationList: some View { @@ -283,13 +311,13 @@ struct ServerView: View { } } - var body: some View { + var serverView: some View { NavigationSplitView { self.navigationList .frame(maxWidth: .infinity) .navigationSplitViewColumnWidth(min: 150, ideal: 200, max: 500) } detail: { - if let selection = self.selection { + if let selection = selection { switch selection.type { case .banner: EmptyView() @@ -297,61 +325,171 @@ struct ServerView: View { EmptyView() case .chat: ChatView() - .navigationTitle(self.model.serverTitle) + .navigationTitle(model.serverTitle) .navigationSplitViewColumnWidth(min: 250, ideal: 500) case .news: NewsView() - .navigationTitle(self.model.serverTitle) + .navigationTitle(model.serverTitle) .navigationSplitViewColumnWidth(min: 250, ideal: 500) case .messageBoard: MessageBoardView() - .navigationTitle(self.model.serverTitle) + .navigationTitle(model.serverTitle) .navigationSplitViewColumnWidth(min: 250, ideal: 500) case .files: FilesView() - .navigationTitle(self.model.serverTitle) + .navigationTitle(model.serverTitle) .navigationSplitViewColumnWidth(min: 250, ideal: 500) case .tasks: EmptyView() case .user: if let selectionUserID = selection.userID { MessageView(userID: selectionUserID) - .navigationTitle(self.model.serverTitle) + .navigationTitle(model.serverTitle) .navigationSplitViewColumnWidth(min: 250, ideal: 500) } } } } - .navigationTitle("") - .onAppear { - if let s = self.server { - self.model.login(server: s, login: "", password: "", username: preferences.username, iconID: preferences.userIconID) { success in - if !success { - print("FAILED LOGIN??") - } - else { - print("GETTING USER LIST????!") - self.sendPreferences() - self.model.getUserList() - self.model.downloadBanner() + } + + var body: some View { + Group { + if model.status == .disconnected { + connectForm + .navigationTitle("Connect to Server") + } + else if model.status != .loggedIn { + HStack { + Image("Hotline") + .resizable() + .renderingMode(.template) + .scaledToFit() + .foregroundColor(Color(hex: 0xE10000)) + .frame(width: 18) + .opacity(controlActiveState == .inactive ? 0.5 : 1.0) + .padding(.trailing, 4) + + ProgressView(value: connectionStatusToProgress(status: model.status)) { + Text(connectionStatusToLabel(status: model.status)) } + .accentColor(colorScheme == .dark ? .white : .black) } + .frame(maxWidth: 300) + .padding() + .navigationTitle("Connecting to Server") + } + else { + serverView + .environment(model) + .onChange(of: preferences.userIconID) { sendPreferences() } + .onChange(of: preferences.username) { sendPreferences() } + .onChange(of: preferences.refusePrivateMessages) { sendPreferences() } + .onChange(of: preferences.refusePrivateChat) { sendPreferences() } + .onChange(of: preferences.enableAutomaticMessage) { sendPreferences() } + .onChange(of: preferences.automaticMessage) { sendPreferences() } + .toolbar { + ToolbarItem(placement: .navigation) { + Image(systemName: "globe.americas.fill") + .resizable() + .scaledToFit() + .frame(width: 18) + } + } } } .onDisappear { - self.model.disconnect() + model.disconnect() } - .onChange(of: model.status) { - if model.status == .disconnected { - dismiss() + .task { + if server != nil { + connectToServer() + } + } + } + + // MARK: - + + @MainActor func connectToServer(login: String = "", password: String = "") { + model.login(server: server!, login: login, password: password, username: preferences.username, iconID: preferences.userIconID) { success in + if !success { + print("FAILED LOGIN??") + } + else { + print("GETTING USER LIST????!") + sendPreferences() + model.getUserList() + model.downloadBanner() + } + } + } + + private func connectionStatusToProgress(status: HotlineClientStatus) -> Double { + switch status { + case .disconnected: + return 0.0 + case .connecting: + return 0.4 + case .connected: + return 0.75 + case .loggingIn: + return 0.9 + case .loggedIn: + return 1.0 + } + } + + private func connectionStatusToLabel(status: HotlineClientStatus) -> String { + if let s = self.server { + let n = s.name ?? s.address + switch status { + case .disconnected: + return "Disconnected" + case .connecting: + return "Connecting to \(n)..." + case .connected: + return "Connected to \(n)" + case .loggingIn: + return "Logging in to \(n)..." + case .loggedIn: + return "Logged in to \(n)" + } + } + else { + switch status { + case .disconnected: + return "Disconnected" + case .connecting: + return "Connecting..." + case .connected: + return "Connected" + case .loggingIn: + return "Logging in..." + case .loggedIn: + return "Logged in" } } - .onChange(of: preferences.userIconID) { self.sendPreferences() } - .onChange(of: preferences.username) { self.sendPreferences() } - .onChange(of: preferences.refusePrivateMessages) { self.sendPreferences() } - .onChange(of: preferences.refusePrivateChat) { self.sendPreferences() } - .onChange(of: preferences.enableAutomaticMessage) { self.sendPreferences() } - .onChange(of: preferences.automaticMessage) { self.sendPreferences() } + } + + @MainActor func sendPreferences() { + if self.model.status == .loggedIn { + var options: HotlineUserOptions = HotlineUserOptions() + + if preferences.refusePrivateMessages { + options.update(with: .refusePrivateMessages) + } + + if preferences.refusePrivateChat { + options.update(with: .refusePrivateChat) + } + + if preferences.enableAutomaticMessage { + options.update(with: .automaticResponse) + } + + print("Updating preferences with server") + + self.model.sendUserInfo(username: preferences.username, iconID: preferences.userIconID, options: options, autoresponse: preferences.automaticMessage) + } } } diff --git a/Hotline/macOS/TrackerView.swift b/Hotline/macOS/TrackerView.swift index 3aed49f..2c9c839 100644 --- a/Hotline/macOS/TrackerView.swift +++ b/Hotline/macOS/TrackerView.swift @@ -303,13 +303,11 @@ struct TrackerView: View { clickedItem.expanded.toggle() } else if let server = clickedItem.server { - openWindow(value: server) + openWindow(id: "server", value: server) } - else if - let bookmark = clickedItem.bookmark, - bookmark.type == .server { + else if let bookmark = clickedItem.bookmark, bookmark.type == .server { let server = Server(name: bookmark.name, description: nil, address: bookmark.address, port: HotlinePorts.DefaultServerPort) - openWindow(value: server) + openWindow(id: "server", value: server) } } .onKeyPress(.rightArrow) { |