From a28604cc91a5177cba968a2b691922cd90f1998a Mon Sep 17 00:00:00 2001 From: SandiyosDev <25407129+SandiyosDev@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:04:53 -0500 Subject: [PATCH 1/5] refrsesh host list UX --- OpenParsec/MainView.swift | 82 ++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/OpenParsec/MainView.swift b/OpenParsec/MainView.swift index 87d2b36..7782eaf 100644 --- a/OpenParsec/MainView.swift +++ b/OpenParsec/MainView.swift @@ -30,6 +30,7 @@ struct MainView: View { @State var pollTimer: Timer? @State var isRefreshing: Bool = false + @State var refreshError: String? @State var inSettings: Bool = false @State var sessionExpired: Bool = false @@ -37,7 +38,7 @@ struct MainView: View { @State private var lastHostShown: String? var busy: Bool { - isConnecting || isRefreshing || inSettings + isConnecting || inSettings } init(_ controller: ContentView?) { @@ -88,6 +89,8 @@ struct MainView: View { .font(.system(size: 20, weight: .medium)) Button(action: refreshHosts, label: { Image(systemName: "arrow.clockwise") }) .padding(4) + .disabled(isRefreshing) + .opacity(isRefreshing ? 0.4 : 1) } else if page == .friends { Text(friendCountStr) .multilineTextAlignment(.center) @@ -117,7 +120,7 @@ struct MainView: View { .foregroundColor(Color("Foreground")) Spacer() Button(action: logout) { - Text("Sign in") + Text("Sign In") .fontWeight(.semibold) .foregroundColor(Color("AccentColor")) .frame(minWidth: 44, minHeight: 44) @@ -131,9 +134,38 @@ struct MainView: View { .frame(maxWidth: 400) .padding(.horizontal) } - Text(refreshTime) - .multilineTextAlignment(.center) - .opacity(0.5) + if let refreshError = refreshError { + HStack { + Text(refreshError) + .foregroundColor(Color("Foreground")) + Spacer() + Button(action: refreshHosts) { + Text("Retry") + .fontWeight(.semibold) + .foregroundColor(Color("AccentColor")) + .frame(minWidth: 44, minHeight: 44) + .contentShape(Rectangle()) + } + } + .padding(.horizontal, 12) + .padding(.vertical, 2) + .background(Rectangle().fill(Color("BackgroundPrompt"))) + .cornerRadius(8) + .frame(maxWidth: 400) + .padding(.horizontal) + } + if isRefreshing { + HStack(spacing: 8) { + ActivityIndicator(isAnimating: $isRefreshing, style: .medium, tint: UIColor(named: "Foreground") ?? .white) + .frame(width: 20, height: 20) + Text("Refreshing hosts...") + .foregroundColor(Color("Foreground")) + } + } else { + Text(refreshTime) + .multilineTextAlignment(.center) + .opacity(0.5) + } if let lastHost = lastHostShown { VStack(spacing: 0) { HStack { @@ -394,24 +426,6 @@ struct MainView: View { .padding() } } - if isRefreshing { - ZStack { - Rectangle() // Darken background - .fill(Color.black) - .opacity(0.5) - .edgesIgnoringSafeArea(.all) - VStack { - ActivityIndicator(isAnimating: $isRefreshing, style: .large, tint: .white) - .padding() - Text("Refreshing hosts...") - .multilineTextAlignment(.center) - } - .padding() - .background(Rectangle().fill(Color("BackgroundPrompt"))) - .cornerRadius(8) - .padding() - } - } } .foregroundColor(Color("Foreground")) } @@ -449,14 +463,14 @@ struct MainView: View { } func refreshHosts() { + guard !isRefreshing else { return } withAnimation { isRefreshing = true let clinfo = NetworkHandler.clinfo if clinfo == nil { isRefreshing = false - baseAlertText = "Error gathering hosts: Invalid session" - showBaseAlert = true + sessionExpired = true return } @@ -475,6 +489,7 @@ struct MainView: View { if let data = data { guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { return } if statusCode == 401 { + refreshError = nil sessionExpired = true return } @@ -482,7 +497,11 @@ struct MainView: View { if statusCode == 200 { // 200 OK sessionExpired = false - guard let info: HostInfoList = try? decoder.decode(HostInfoList.self, from: data) else { return } + guard let info: HostInfoList = try? decoder.decode(HostInfoList.self, from: data) else { + refreshError = "Couldn't refresh hosts: bad response" + return + } + refreshError = nil hosts.removeAll() if let datas = info.data { datas.forEach { h in @@ -500,12 +519,13 @@ struct MainView: View { let formatter = DateFormatter() formatter.dateFormat = "M/d/yyyy h:mm a" refreshTime = "Last refreshed at \(formatter.string(from: Date()))" - } else if statusCode == 403 { // 403 Forbidden - guard let info: ErrorInfo = try? decoder.decode(ErrorInfo.self, from: data) else { return } - - baseAlertText = "Error gathering hosts: \(info.error)" - showBaseAlert = true + } else if statusCode == 403, let info: ErrorInfo = try? decoder.decode(ErrorInfo.self, from: data) { // 403 Forbidden + refreshError = "Couldn't refresh hosts: \(info.error)" + } else { + refreshError = "Couldn't refresh hosts (HTTP \(statusCode))" } + } else { + refreshError = "Can't reach Parsec" } } } From 48fda96b34f1acc632a72197888ac25fc02fbb30 Mon Sep 17 00:00:00 2001 From: SandiyosDev <25407129+SandiyosDev@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:21:08 -0500 Subject: [PATCH 2/5] hide 1970 refresh time --- OpenParsec/MainView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenParsec/MainView.swift b/OpenParsec/MainView.swift index 7782eaf..757c601 100644 --- a/OpenParsec/MainView.swift +++ b/OpenParsec/MainView.swift @@ -8,7 +8,7 @@ struct MainView: View { // Host page vars @State var hostCountStr: String = "0 hosts" - @State var refreshTime: String = "Last refreshed at 1/1/1970 12:00 AM" + @State var refreshTime: String = "" @State var hosts: [IdentifiableHostInfo] = [] @@ -161,7 +161,7 @@ struct MainView: View { Text("Refreshing hosts...") .foregroundColor(Color("Foreground")) } - } else { + } else if !refreshTime.isEmpty { Text(refreshTime) .multilineTextAlignment(.center) .opacity(0.5) From a17739906e911251b3747c9d45f3dd6169e33b03 Mon Sep 17 00:00:00 2001 From: SandiyosDev <25407129+SandiyosDev@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:03:47 -0500 Subject: [PATCH 3/5] show refresh time local --- OpenParsec/MainView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenParsec/MainView.swift b/OpenParsec/MainView.swift index 757c601..adf9421 100644 --- a/OpenParsec/MainView.swift +++ b/OpenParsec/MainView.swift @@ -517,7 +517,8 @@ struct MainView: View { hostCountStr = "\(hosts.count) \(grammar)" let formatter = DateFormatter() - formatter.dateFormat = "M/d/yyyy h:mm a" + formatter.dateStyle = .short + formatter.timeStyle = .short refreshTime = "Last refreshed at \(formatter.string(from: Date()))" } else if statusCode == 403, let info: ErrorInfo = try? decoder.decode(ErrorInfo.self, from: data) { // 403 Forbidden refreshError = "Couldn't refresh hosts: \(info.error)" From bb6056bf75784d7d395f99ed1ce82ec29f18f2f7 Mon Sep 17 00:00:00 2001 From: SandiyosDev <25407129+SandiyosDev@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:27:31 -0500 Subject: [PATCH 4/5] long press host name copy --- OpenParsec/MainView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OpenParsec/MainView.swift b/OpenParsec/MainView.swift index adf9421..b83e614 100644 --- a/OpenParsec/MainView.swift +++ b/OpenParsec/MainView.swift @@ -265,6 +265,11 @@ struct MainView: View { .frame(maxWidth: 400) .background(Rectangle().fill(Color("BackgroundCard"))) .cornerRadius(8) + .contextMenu { + Button(action: { UIPasteboard.general.string = i.hostname }) { + Label("Copy Name", systemImage: "doc.on.doc") + } + } } } .padding() From cc2e14595ee73a7a1ac78caf9925c2d761424a6b Mon Sep 17 00:00:00 2001 From: SandiyosDev <25407129+SandiyosDev@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:38:56 -0500 Subject: [PATCH 5/5] hide last host card during connect --- OpenParsec/MainView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenParsec/MainView.swift b/OpenParsec/MainView.swift index b83e614..c7aab56 100644 --- a/OpenParsec/MainView.swift +++ b/OpenParsec/MainView.swift @@ -166,7 +166,7 @@ struct MainView: View { .multilineTextAlignment(.center) .opacity(0.5) } - if let lastHost = lastHostShown { + if let lastHost = lastHostShown, !isConnecting { VStack(spacing: 0) { HStack { VStack(alignment: .leading, spacing: 4) {