aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-01-03 23:55:39 -0800
committerDustin Mierau <dustin@mierau.me>2024-01-03 23:55:39 -0800
commitcc6e0e35cf6e662ffc3ad24e2510265caa5cf52a (patch)
tree908b99f7995edf9dc911209be656fb731c47c5ad /Hotline
parent2286040715ff8c08c4114c60e81f087f92d53800 (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.swift26
-rw-r--r--Hotline/Hotline/HotlineClient.swift29
-rw-r--r--Hotline/Models/Server.swift7
-rw-r--r--Hotline/macOS/ChatView.swift1
-rw-r--r--Hotline/macOS/ServerView.swift266
-rw-r--r--Hotline/macOS/TrackerView.swift8
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) {