fix(apple): show "Loading Resources..." instead of "No Resources" while loading (#6358)

Closes #6356

---------

Signed-off-by: Reactor Scram <ReactorScram@users.noreply.github.com>
Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
Reactor Scram
2024-08-20 11:44:00 -05:00
committed by GitHub
parent 6665526b13
commit d43b501b48
5 changed files with 39 additions and 25 deletions

View File

@@ -82,7 +82,7 @@ public class TunnelManager {
// Cache resources on this side of the IPC barrier so we can
// return them to callers when they haven't changed.
private var resourcesListCache: [Resource] = []
private var resourcesListCache: ResourceList = ResourceList.loading
// Persists our tunnel settings
private var manager: NETunnelProviderManager?
@@ -272,7 +272,7 @@ public class TunnelManager {
updateDisabledResources()
}
func fetchResources(callback: @escaping ([Resource]) -> Void) {
func fetchResources(callback: @escaping (ResourceList) -> Void) {
guard session().status == .connected else { return }
do {
@@ -281,7 +281,7 @@ public class TunnelManager {
self.resourceListHash = Data(SHA256.hash(data: data))
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
self.resourcesListCache = (try? decoder.decode([Resource].self, from: data)) ?? []
self.resourcesListCache = ResourceList.loaded(try! decoder.decode([Resource].self, from: data))
}
callback(self.resourcesListCache)
@@ -323,7 +323,7 @@ public class TunnelManager {
if session.status == .disconnected {
// Reset resource list on disconnect
resourceListHash = Data()
resourcesListCache = []
resourcesListCache = ResourceList.loading
}
await statusChangeHandler?(session.status)

View File

@@ -8,6 +8,20 @@
import Foundation
public enum ResourceList {
case loading
case loaded([Resource])
public func asArray() -> [Resource] {
switch self {
case .loading:
[]
case .loaded(let x):
x
}
}
}
public struct Resource: Decodable, Identifiable, Equatable {
public let id: String
public var name: String

View File

@@ -142,7 +142,7 @@ public final class Store: ObservableObject {
// Network Extensions don't have a 2-way binding up to the GUI process,
// so we need to periodically ask the tunnel process for them.
func beginUpdatingResources(callback: @escaping ([Resource]) -> Void) {
func beginUpdatingResources(callback: @escaping (ResourceList) -> Void) {
Log.app.log("\(#function)")
tunnelManager.fetchResources(callback: callback)

View File

@@ -18,7 +18,6 @@ import SwiftUI
// https://developer.apple.com/documentation/swiftui/menubarextra
public final class MenuBar: NSObject, ObservableObject {
private var statusItem: NSStatusItem
private var resources: [Resource] = []
// Wish these could be `[String]` but diffing between different types is tricky
private var lastShownFavorites: [Resource] = []
@@ -73,20 +72,18 @@ public final class MenuBar: NSObject, ObservableObject {
if status == .connected {
model.store.beginUpdatingResources { newResources in
// Handle resource changes
self.populateResourceMenus(newResources)
self.handleTunnelStatusOrResourcesChanged(status: status, resources: newResources)
self.resources = newResources
// Handle resource changes
self.populateResourceMenus(newResources.asArray())
self.handleTunnelStatusOrResourcesChanged(status: status, resources: newResources)
}
} else {
model.store.endUpdatingResources()
populateResourceMenus([])
resources = []
}
// Handle status changes
self.updateStatusItemIcon(status: status)
self.handleTunnelStatusOrResourcesChanged(status: status, resources: resources)
self.handleTunnelStatusOrResourcesChanged(status: status, resources: model.resources)
}).store(in: &cancellables)
}
@@ -325,7 +322,7 @@ public final class MenuBar: NSObject, ObservableObject {
(connectingAnimationImageIndex + 1) % connectingAnimationImages.count
}
private func handleTunnelStatusOrResourcesChanged(status: NEVPNStatus, resources: [Resource]?) {
private func handleTunnelStatusOrResourcesChanged(status: NEVPNStatus, resources: ResourceList) {
// Update "Sign In" / "Sign Out" menu items
switch status {
case .invalid:
@@ -405,13 +402,16 @@ public final class MenuBar: NSObject, ObservableObject {
}()
}
private func resourceMenuTitle(_ resources: [Resource]?) -> String {
guard let resources = resources else { return "Loading Resources..." }
if resources.isEmpty {
return "No Resources"
} else {
return "Resources"
private func resourceMenuTitle(_ resources: ResourceList) -> String {
switch resources {
case .loading:
return "Loading Resources..."
case .loaded(let x):
if x.isEmpty {
return "No Resources"
} else {
return "Resources"
}
}
}
@@ -654,7 +654,7 @@ public final class MenuBar: NSObject, ObservableObject {
// When the user clicks to add or remove a favorite, the menu will close anyway, so just recreate the whole menu.
// This avoids complex logic when changing in and out of the "nothing is favorited" special case
self.populateResourceMenus([])
self.populateResourceMenus(resources)
self.populateResourceMenus(model.resources.asArray())
}
private func copyToClipboard(_ string: String) {

View File

@@ -12,7 +12,7 @@ import SwiftUI
@MainActor
public final class SessionViewModel: ObservableObject {
@Published private(set) var actorName: String? = nil
@Published private(set) var resources: [Resource]? = nil
@Published private(set) var resources: ResourceList = ResourceList.loading
@Published private(set) var status: NEVPNStatus? = nil
@@ -67,7 +67,8 @@ struct SessionView: View {
var body: some View {
switch model.status {
case .connected:
if let resources = model.resources {
switch model.resources {
case .loaded(let resources):
if resources.isEmpty {
Text("No Resources. Contact your admin to be granted access.")
} else {
@@ -91,10 +92,9 @@ struct SessionView: View {
.navigationTitle("All Resources")
}
}
.listStyle(GroupedListStyle())
}
} else {
case .loading:
Text("Loading Resources...")
}
case .connecting: