diff --git a/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift b/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift index f66b6295..1d577e3f 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift @@ -19,7 +19,6 @@ public enum NetworkStatus: String { case notConnected = "Not Connected" case connecting = "Connecting" case connected = "Connected" - case connectionFailed = "Connection Failed" } // Anticipating supporting more robust configuration options where we allow multiple url/user/pass options for users @@ -31,23 +30,23 @@ public struct ConnectionObject: Equatable { self.url = url self.priority = priority } - - public static func == (lhs: ConnectionObject, rhs: ConnectionObject) -> Bool { - lhs.url == rhs.url && lhs.priority == rhs.priority - } } public final class NetworkTracker: ObservableObject { public static let shared = NetworkTracker() @Published public private(set) var activeServer: ConnectionObject? - @Published public private(set) var status: NetworkStatus = .notConnected + @Published public private(set) var status: NetworkStatus = .connecting - private var monitor: NWPathMonitor - private var monitorQueue = DispatchQueue.global(qos: .background) + private let monitor: NWPathMonitor + private let monitorQueue = DispatchQueue.global(qos: .background) private var connectionObjects: [ConnectionObject] = [] private var retryTimer: DispatchSourceTimer? + private let timerQueue = DispatchQueue(label: "com.openhab.networktracker.timerQueue") + + private let connectedRetryInterval: TimeInterval = 60 // amount of time we scan for better connections when connected + private let disconnectedRetryInterval: TimeInterval = 30 // amount of time we scan when not connected private init() { monitor = NWPathMonitor() @@ -57,7 +56,7 @@ public final class NetworkTracker: ObservableObject { self?.checkActiveServer() } else { os_log("Network status: Disconnected", log: OSLog.default, type: .info) - self?.updateStatus(.notConnected) + self?.setActiveServer(nil) self?.startRetryTimer(10) // try every 10 seconds connect } } @@ -69,19 +68,18 @@ public final class NetworkTracker: ObservableObject { attemptConnection() } + // This gets called periodically when we have an active server to make sure its still the best choice private func checkActiveServer() { guard let activeServer, activeServer.priority == 0 else { // No primary active server, proceed with the normal connection attempt attemptConnection() return } - // Check if the last active server is reachable + // Check if the primary (priority = 0) active server is reachable if thats what is currenty connected. NetworkConnection.tracker(openHABRootUrl: activeServer.url) { [weak self] response in switch response.result { case .success: os_log("Network status: Active server is reachable: %{PUBLIC}@", log: OSLog.default, type: .info, activeServer.url) - self?.updateStatus(.connected) // If reachable, we're done - self?.cancelRetryTimer() case .failure: os_log("Network status: Active server is not reachable: %{PUBLIC}@", log: OSLog.default, type: .error, activeServer.url) self?.attemptConnection() // If not reachable, run the connection logic @@ -92,7 +90,7 @@ public final class NetworkTracker: ObservableObject { private func attemptConnection() { guard !connectionObjects.isEmpty else { os_log("Network status: No connection objects available.", log: OSLog.default, type: .error) - updateStatus(.notConnected) + setActiveServer(nil) return } os_log("Network status: checking available servers....", log: OSLog.default, type: .error) @@ -114,7 +112,7 @@ public final class NetworkTracker: ObservableObject { os_log("Network status: No server responded in 2 seconds, waiting for checks to finish.", log: OSLog.default, type: .info) } else { os_log("Network status: No server responded in 2 seconds and no checks are outstanding.", log: OSLog.default, type: .error) - updateStatus(.connectionFailed) + setActiveServer(nil) } } @@ -176,7 +174,7 @@ public final class NetworkTracker: ObservableObject { os_log("Network status: First available connection established with %{PUBLIC}@", log: OSLog.default, type: .info, firstAvailableConnection.url) } else { os_log("Network status: No server responded, connection failed.", log: OSLog.default, type: .error) - updateStatus(.connectionFailed) + setActiveServer(nil) } } } @@ -184,28 +182,35 @@ public final class NetworkTracker: ObservableObject { // Start the retry timer to attempt connection every N seconds private func startRetryTimer(_ retryInterval: TimeInterval) { cancelRetryTimer() - retryTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) - retryTimer?.schedule(deadline: .now() + retryInterval, repeating: retryInterval) - retryTimer?.setEventHandler { [weak self] in - os_log("Network status: Retry timer firing", log: OSLog.default, type: .info) - self?.attemptConnection() + timerQueue.sync { + retryTimer = DispatchSource.makeTimerSource(queue: timerQueue) + retryTimer?.schedule(deadline: .now() + retryInterval, repeating: retryInterval) + retryTimer?.setEventHandler { [weak self] in + os_log("Network status: Retry timer firing", log: OSLog.default, type: .info) + self?.attemptConnection() + } + retryTimer?.resume() } - retryTimer?.resume() } - // Cancel the retry timer private func cancelRetryTimer() { - retryTimer?.cancel() - retryTimer = nil + timerQueue.sync { + retryTimer?.cancel() + retryTimer = nil + } } - private func setActiveServer(_ server: ConnectionObject) { - os_log("Network status: setActiveServer: %{PUBLIC}@", log: OSLog.default, type: .info, server.url) - if activeServer != server { - activeServer = server + private func setActiveServer(_ server: ConnectionObject?) { + os_log("Network status: setActiveServer: %{PUBLIC}@", log: OSLog.default, type: .info, server?.url ?? "no server") + guard activeServer != server else { return } + activeServer = server + if activeServer != nil { + updateStatus(.connected) + startRetryTimer(connectedRetryInterval) + } else { + updateStatus(.notConnected) + startRetryTimer(disconnectedRetryInterval) } - updateStatus(.connected) - startRetryTimer(60) // check every 60 seconds to see if a better server is available. } private func updateStatus(_ newStatus: NetworkStatus) { diff --git a/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift b/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift index ede1f291..f45298ae 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift @@ -9,34 +9,37 @@ // // SPDX-License-Identifier: EPL-2.0 +import Combine import Foundation import os.log public class OpenHABItemCache { public static let instance = OpenHABItemCache() - - static let URL_NONE = 0 - static let URL_LOCAL = 1 - static let URL_REMOTE = 2 - static let URL_DEMO = 3 - public var items: [OpenHABItem]? - - var timeout: Double { lastUrlConnected == OpenHABItemCache.URL_LOCAL ? 10.0 : 20.0 } - var url = "" - var localUrlFailed = false - var lastUrlConnected = URL_NONE + var cancellables = Set() + var timeout: Double = 20 var lastLoad = Date().timeIntervalSince1970 + private init() { + if NetworkConnection.shared == nil { + NetworkConnection.initialize(ignoreSSL: Preferences.ignoreSSL, interceptor: nil) + } + let connection1 = ConnectionObject( + url: Preferences.localUrl, + priority: 0 + ) + let connection2 = ConnectionObject( + url: Preferences.remoteUrl, + priority: 1 + ) + NetworkTracker.shared.startTracking(connectionObjects: [connection1, connection2]) + } + public func getItemNames(searchTerm: String?, types: [OpenHABItem.ItemType]?, completion: @escaping ([NSString]) -> Void) { var ret = [NSString]() guard let items else { - if #available(iOS 12.0, *) { - reload(searchTerm: searchTerm, types: types, completion: completion) - } else { - // Fallback on earlier versions - } + reload(searchTerm: searchTerm, types: types, completion: completion) return } @@ -72,114 +75,72 @@ public class OpenHABItemCache { @available(iOS 12.0, *) public func reload(searchTerm: String?, types: [OpenHABItem.ItemType]?, completion: @escaping ([NSString]) -> Void) { - lastLoad = Date().timeIntervalSince1970 - - guard let uurl = getURL() else { return } - - os_log("Loading items from %{PUBLIC}@", log: .default, type: .info, url) - - if NetworkConnection.shared == nil { - NetworkConnection.initialize(ignoreSSL: Preferences.ignoreSSL, interceptor: nil) - } - - NetworkConnection.load(from: uurl, timeout: timeout) { response in - switch response.result { - case let .success(data): - do { - try self.decodeItemsData(data) - - let ret = self.items?.filter { (searchTerm == nil || $0.name.contains(searchTerm.orEmpty)) && (types == nil || ($0.type != nil && types!.contains($0.type!))) }.sorted(by: \.name).map { NSString(string: $0.name) } ?? [] - - completion(ret) - } catch { - os_log("%{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) - } - case let .failure(error): - if self.lastUrlConnected == OpenHABItemCache.URL_LOCAL { - self.localUrlFailed = true - os_log("%{PUBLIC}@ ", log: .default, type: .info, error.localizedDescription) - self.reload(searchTerm: searchTerm, types: types, completion: completion) // try remote - - } else { - os_log("%{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + NetworkTracker.shared.$activeServer + .filter { $0 != nil } // Only proceed if activeServer is not nil + .first() // Automatically cancels after the first non-nil value + .receive(on: DispatchQueue.main) + .sink { activeServer in + if let urlString = activeServer?.url, let url = Endpoint.items(openHABRootUrl: urlString).url { + os_log("OpenHABItemCache Loading items from %{PUBLIC}@", log: .default, type: .info, urlString) + self.lastLoad = Date().timeIntervalSince1970 + NetworkConnection.load(from: url, timeout: self.timeout) { response in + switch response.result { + case let .success(data): + do { + try self.decodeItemsData(data) + let ret = self.items?.filter { (searchTerm == nil || $0.name.contains(searchTerm.orEmpty)) && (types == nil || ($0.type != nil && types!.contains($0.type!))) }.sorted(by: \.name).map { NSString(string: $0.name) } ?? [] + completion(ret) + } catch { + print(error) + os_log("OpenHABItemCache %{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + } + case let .failure(error): + os_log("OpenHABItemCache %{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + } + } } } - } + .store(in: &cancellables) } @available(iOS 12.0, *) public func reload(name: String, completion: @escaping (OpenHABItem?) -> Void) { - lastLoad = Date().timeIntervalSince1970 - - guard let uurl = getURL() else { return } - - os_log("Loading items from %{PUBLIC}@", log: .default, type: .info, url) - - if NetworkConnection.shared == nil { - NetworkConnection.initialize(ignoreSSL: Preferences.ignoreSSL, interceptor: nil) - } - - NetworkConnection.load(from: uurl, timeout: timeout) { response in - switch response.result { - case let .success(data): - do { - try self.decodeItemsData(data) - - let item = self.getItem(name) - - completion(item) - - } catch { - os_log("%{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + NetworkTracker.shared.$activeServer + .filter { $0 != nil } // Only proceed if activeServer is not nil + .first() // Automatically cancels after the first non-nil value + .receive(on: DispatchQueue.main) + .sink { activeServer in + if let urlString = activeServer?.url, let url = Endpoint.items(openHABRootUrl: urlString).url { + os_log("OpenHABItemCache Loading items from %{PUBLIC}@", log: .default, type: .info, urlString) + self.lastLoad = Date().timeIntervalSince1970 + NetworkConnection.load(from: url, timeout: self.timeout) { response in + switch response.result { + case let .success(data): + do { + try self.decodeItemsData(data) + let item = self.getItem(name) + completion(item) + } catch { + os_log("OpenHABItemCache %{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + } + case let .failure(error): + print(error) + os_log("OpenHABItemCache %{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + } + } } - case let .failure(error): - if self.lastUrlConnected == OpenHABItemCache.URL_LOCAL { - self.localUrlFailed = true - os_log("%{PUBLIC}@ ", log: .default, type: .info, error.localizedDescription) - self.reload(name: name, completion: completion) // try remote - - } else { - os_log("%{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) - } - } - } - } - - func getURL() -> URL? { - var uurl: URL? - - if Preferences.demomode { - uurl = Endpoint.items(openHABRootUrl: "https://demo.openhab.org").url - url = uurl?.absoluteString ?? "unknown" - lastUrlConnected = OpenHABItemCache.URL_DEMO - - } else { - if localUrlFailed { - uurl = Endpoint.items(openHABRootUrl: Preferences.remoteUrl).url - url = uurl?.absoluteString ?? "unknown" - lastUrlConnected = OpenHABItemCache.URL_REMOTE - - } else { - uurl = Endpoint.items(openHABRootUrl: Preferences.localUrl).url - url = uurl?.absoluteString ?? "unknown" - lastUrlConnected = OpenHABItemCache.URL_LOCAL } - } - - return uurl + .store(in: &cancellables) } private func decodeItemsData(_ data: Data) throws { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full) let codingDatas = try data.decoded(as: [OpenHABItem.CodingData].self, using: decoder) - items = [OpenHABItem]() - for codingDatum in codingDatas where codingDatum.openHABItem.type != OpenHABItem.ItemType.group { self.items?.append(codingDatum.openHABItem) } - os_log("Loaded items to cache: %{PUBLIC}d", log: .default, type: .info, items?.count ?? 0) } } diff --git a/OpenHABCore/Tests/OpenHABCoreTests/ItemCacheTests.swift b/OpenHABCore/Tests/OpenHABCoreTests/ItemCacheTests.swift deleted file mode 100644 index 644f8e42..00000000 --- a/OpenHABCore/Tests/OpenHABCoreTests/ItemCacheTests.swift +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -@testable import OpenHABCore - -import XCTest - -final class ItemCacheTests: XCTestCase { - private static let ITEMS_URL = "/rest/items?" - private let instance = OpenHABItemCache.instance - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testAppendingPathComponent() throws { - let url = URL(string: "http://demo.openhab.org:8080/rest/items/DemoSwitch")!.appendingPathComponent("/state") - XCTAssertEqual(url.absoluteString, "http://demo.openhab.org:8080/rest/items/DemoSwitch/state") - } - - func testGetURL() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - Preferences.localUrl = "http://192.168.0.1:8080" - Preferences.remoteUrl = "http://myopenhab.org:8080" - Preferences.demomode = false - - var url = instance.getURL() - XCTAssert(instance.lastUrlConnected == OpenHABItemCache.URL_LOCAL) - var expected = Preferences.localUrl + ItemCacheTests.ITEMS_URL - var result = url?.absoluteString ?? "" - XCTAssert(expected == result) - - instance.localUrlFailed = true - - url = instance.getURL() - XCTAssert(instance.lastUrlConnected == OpenHABItemCache.URL_REMOTE) - expected = Preferences.remoteUrl + ItemCacheTests.ITEMS_URL - result = url?.absoluteString ?? "" - XCTAssert(expected == result) - - Preferences.demomode = true - - url = instance.getURL() - XCTAssert(instance.lastUrlConnected == OpenHABItemCache.URL_DEMO) - expected = "https://demo.openhab.org" + ItemCacheTests.ITEMS_URL - result = url?.absoluteString ?? "" - XCTAssert(expected == result) - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } - } -} diff --git a/openHAB/DrawerView.swift b/openHAB/DrawerView.swift index 291428a1..05699ded 100644 --- a/openHAB/DrawerView.swift +++ b/openHAB/DrawerView.swift @@ -9,6 +9,7 @@ // // SPDX-License-Identifier: EPL-2.0 +import Combine import Kingfisher import OpenHABCore import os.log @@ -72,6 +73,7 @@ struct DrawerView: View { @State private var sitemaps: [OpenHABSitemap] = [] @State private var uiTiles: [OpenHABUiTile] = [] @State private var selectedSection: Int? + @State private var connectedUrl: String = "Not connected" // Default label text var openHABUsername = "" var openHABPassword = "" @@ -88,98 +90,126 @@ struct DrawerView: View { @ScaledMetric var tilesIconwidth = 20.0 @ScaledMetric var sitemapIconwidth = 20.0 - var body: some View { - List { - Section(header: Text("Main")) { - HStack { - Image("openHABIcon") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: openHABIconwidth) - Text("Home") - } - .onTapGesture { - dismiss() - onDismiss(.webview) - } - } + // Combine cancellable + @State private var trackerCancellable: AnyCancellable? - Section(header: Text("Tiles")) { - ForEach(uiTiles, id: \.url) { tile in + var body: some View { + VStack { + List { + Section(header: Text("Main")) { HStack { - ImageView(url: tile.imageUrl) + Image("openHABIcon") + .resizable() .aspectRatio(contentMode: .fit) - .frame(width: tilesIconwidth) - Text(tile.name) + .frame(width: openHABIconwidth) + Text("Home") } .onTapGesture { dismiss() - onDismiss(.tile(tile.url)) + onDismiss(.webview) } } - } - Section(header: Text("Sitemaps")) { - ForEach(sitemaps, id: \.name) { sitemap in - HStack { - let url = Endpoint.iconForDrawer(rootUrl: appData?.openHABRootUrl ?? "", icon: sitemap.icon).url - KFImage(url).placeholder { Image("openHABIcon").resizable() } - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: sitemapIconwidth) - Text(sitemap.label) - } - .onTapGesture { - dismiss() - onDismiss(.sitemap(sitemap.name)) + Section(header: Text("Tiles")) { + ForEach(uiTiles, id: \.url) { tile in + HStack { + ImageView(url: tile.imageUrl) + .aspectRatio(contentMode: .fit) + .frame(width: tilesIconwidth) + Text(tile.name) + } + .onTapGesture { + dismiss() + onDismiss(.tile(tile.url)) + } } } - } - Section(header: Text("System")) { - HStack { - Image(systemSymbol: .gear) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: openHABIconwidth) - Text(LocalizedStringKey("settings")) - } - .onTapGesture { - dismiss() - onDismiss(.settings) + Section(header: Text("Sitemaps")) { + ForEach(sitemaps, id: \.name) { sitemap in + HStack { + let url = Endpoint.iconForDrawer(rootUrl: appData?.openHABRootUrl ?? "", icon: sitemap.icon).url + KFImage(url).placeholder { Image("openHABIcon").resizable() } + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: sitemapIconwidth) + Text(sitemap.label) + } + .onTapGesture { + dismiss() + onDismiss(.sitemap(sitemap.name)) + } + } } - // check if we are using my.openHAB, add notifications menu item then - // Actually this should better test whether the host of the remoteUrl is on openhab.org - if Preferences.remoteUrl.contains("openhab.org"), !Preferences.demomode { + Section(header: Text("System")) { HStack { - Image(systemSymbol: .bell) + Image(systemSymbol: .gear) .resizable() .aspectRatio(contentMode: .fit) .frame(width: openHABIconwidth) - Text(LocalizedStringKey("notifications")) + Text(LocalizedStringKey("settings")) } .onTapGesture { dismiss() - onDismiss(.notifications) + onDismiss(.settings) + } + + if Preferences.remoteUrl.contains("openhab.org"), !Preferences.demomode { + HStack { + Image(systemSymbol: .bell) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: openHABIconwidth) + Text(LocalizedStringKey("notifications")) + } + .onTapGesture { + dismiss() + onDismiss(.notifications) + } } } } + .listStyle(.inset) + .onAppear(perform: loadData) + + Spacer() + + // Display the connected URL + HStack { + Image(systemName: "cloud.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + Text(connectedUrl) + .font(.footnote) + } + .padding(.bottom, 5) + .onAppear(perform: trackActiveServer) + .onDisappear { + trackerCancellable?.cancel() + } } - .listStyle(.inset) - .onAppear(perform: loadData) + } + + private func trackActiveServer() { + trackerCancellable = NetworkTracker.shared.$activeServer + .receive(on: DispatchQueue.main) + .sink { activeServer in + if let activeServer { + connectedUrl = activeServer.url + } else { + connectedUrl = NSLocalizedString("connecting", comment: "") + } + } } private func loadData() { - // TODO: Replace network calls with appropriate @EnvironmentObject or other state management loadSitemaps() loadUiTiles() } private func loadSitemaps() { - // Perform network call to load sitemaps and decode - // Update the sitemaps state - NetworkConnection.sitemaps(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in switch response.result { case let .success(data): @@ -191,7 +221,6 @@ struct DrawerView: View { sitemaps = Array(sitemaps.dropLast()) } - // Sort the sitemaps according to Settings selection. switch SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label { case .label: sitemaps.sort { $0.label < $1.label } case .name: sitemaps.sort { $0.name < $1.name } @@ -203,8 +232,6 @@ struct DrawerView: View { } private func loadUiTiles() { - // Perform network call to load UI Tiles and decode - // Update the uiTiles state NetworkConnection.uiTiles(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in switch response.result { case .success: @@ -223,11 +250,6 @@ struct DrawerView: View { } } } - - mutating func loadSettings() { - openHABUsername = Preferences.username - openHABPassword = Preferences.password - } } #Preview { diff --git a/openHAB/OpenHABRootViewController.swift b/openHAB/OpenHABRootViewController.swift index 8412fe23..30a83f1a 100644 --- a/openHAB/OpenHABRootViewController.swift +++ b/openHAB/OpenHABRootViewController.swift @@ -120,20 +120,30 @@ class OpenHABRootViewController: UIViewController { } fileprivate func setupTracker() { - Publishers.CombineLatest( + Publishers.CombineLatest3( Preferences.$localUrl, - Preferences.$remoteUrl + Preferences.$remoteUrl, + Preferences.$demomode ) - .sink { (localUrl, remoteUrl) in - let connection1 = ConnectionObject( - url: localUrl, - priority: 0 - ) - let connection2 = ConnectionObject( - url: remoteUrl, - priority: 1 - ) - NetworkTracker.shared.startTracking(connectionObjects: [connection1, connection2]) + .sink { (localUrl, remoteUrl, demomode) in + if demomode { + NetworkTracker.shared.startTracking(connectionObjects: [ + ConnectionObject( + url: "https://demo.openhab.org", + priority: 0 + ) + ]) + } else { + let connection1 = ConnectionObject( + url: localUrl, + priority: 0 + ) + let connection2 = ConnectionObject( + url: remoteUrl, + priority: 1 + ) + NetworkTracker.shared.startTracking(connectionObjects: [connection1, connection2]) + } } .store(in: &cancellables) diff --git a/openHAB/OpenHABSitemapViewController.swift b/openHAB/OpenHABSitemapViewController.swift index 33129bf2..bc84d17d 100644 --- a/openHAB/OpenHABSitemapViewController.swift +++ b/openHAB/OpenHABSitemapViewController.swift @@ -176,7 +176,7 @@ class OpenHABSitemapViewController: OpenHABViewController, GenericUITableViewCel switch status { case .connecting: self.showPopupMessage(seconds: 1.5, title: NSLocalizedString("connecting", comment: ""), message: "", theme: .info) - case .connectionFailed: + case .notConnected: os_log("Tracking error", log: .viewCycle, type: .info) self.showPopupMessage(seconds: 60, title: NSLocalizedString("error", comment: ""), message: NSLocalizedString("network_not_available", comment: ""), theme: .error) case _: @@ -479,6 +479,8 @@ class OpenHABSitemapViewController: OpenHABViewController, GenericUITableViewCel // this will be called imediately after connecting for the initial state, otherwise it will wait for the state to change // since we do not reference the sink cancelable, this will only fire once _ = NetworkTracker.shared.$activeServer + .filter { $0 != nil } // Only proceed if activeServer is not nil + .first() // Automatically cancels after the first non-nil value .receive(on: DispatchQueue.main) .sink { [weak self] activeServer in if let openHABUrl = activeServer?.url, let self { diff --git a/openHAB/OpenHABWebViewController.swift b/openHAB/OpenHABWebViewController.swift index 39406d06..18e1a49d 100644 --- a/openHAB/OpenHABWebViewController.swift +++ b/openHAB/OpenHABWebViewController.swift @@ -90,7 +90,7 @@ class OpenHABWebViewController: OpenHABViewController { switch status { case .connecting: self.showPopupMessage(seconds: 60, title: NSLocalizedString("connecting", comment: ""), message: "", theme: .info) - case .connectionFailed: + case .notConnected: self.pageLoadError(message: NSLocalizedString("network_not_available", comment: "")) case _: break @@ -304,7 +304,7 @@ extension OpenHABWebViewController: WKScriptMessageHandler { os_log("WKScriptMessage sseConnected is false", log: OSLog.remoteAccess, type: .info) sseTimer?.invalidate() sseTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { _ in - self.showPopupMessage(seconds: 20, title: NSLocalizedString("connecting", comment: ""), message: "", theme: .error) + self.showPopupMessage(seconds: 20, title: NSLocalizedString("connecting", comment: ""), message: "", theme: .info) self.acceptsCommands = false } default: break