diff --git a/.github/workflows/deployTest.yml b/.github/workflows/deployTest.yml deleted file mode 100644 index 7aea33e0..00000000 --- a/.github/workflows/deployTest.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Deploy on TestFlight ===Test=== - -on: - schedule: - - cron: "0 6 1 * *" - workflow_dispatch: - inputs: - bump_type: - type: choice - description: 'Select version bump type:' - options: - - none - - patch - - minor - - major - -jobs: - build_and_deploy: - runs-on: macos-14 - - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - - uses: actions/checkout@v4 - with: - token: ${{ secrets.ACCESS_TOKEN }} - fetch-depth: 0 - - - name: Install dependencies - run: | - bundle install --redownload - - - name: SSH Config - run: | - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config - - - name: SSH Keys - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.MATCH_GIT_PRIVATE_KEY }} - - - name: Deploy on TestFlight - env: - OH_CROWDIN_PROJECT_ID: openhab-ios - OH_CROWDIN_USERNAME: ${{ secrets.OH_CROWDIN_USERNAME }} - OH_CROWDIN_ACCOUNT_KEY: ${{ secrets.OH_CROWDIN_ACCOUNT_KEY }} - BRANCH_NAME: ${{ github.ref_name }} - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} - ASC_API_KEY_ISSUER_ID: ${{ secrets.ASC_API_KEY_ISSUER_ID }} - ASC_API_KEY: ${{ secrets.ASC_API_KEY }} - LANG: en_US.UTF-8 - LC_ALL: en_US.UTF-8 - FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 60 - FASTLANE_GITHUB_RELEASE_API_BEARER: ${{ secrets.ACCESS_TOKEN }} - uses: maierj/fastlane-action@v3.1.0 - with: - lane: beta - options: '{ "bump": "${{ github.event.inputs.bump_type }}" }' - skip-tracking: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 336b229e..2243a93c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '15.4' - uses: actions/checkout@v4 with: 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/fastlane/Fastfile b/fastlane/Fastfile index 2782273f..cb852d96 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -88,6 +88,32 @@ platform :ios do # ensure branch is clean ensure_git_status_clean + + # Set up manual signing, independently of what is set in "Signing" with Xcode frontend + update_code_signing_settings( + use_automatic_signing: false, + code_sign_identity: 'Apple Distribution', + ) + + update_code_signing_settings( + targets: 'openHAB', + profile_name: 'match AppStore org.openhab.app', + ) + + update_code_signing_settings( + targets: 'openHABWatch', + profile_name: 'match AppStore org.openhab.app.watchkitapp', + ) + + update_code_signing_settings( + targets: 'openHABIntents', + profile_name: 'match AppStore org.openhab.app.openHABIntents', + ) + + update_code_signing_settings( + targets: 'NotificationService', + profile_name: 'match AppStore org.openhab.app.NotificationService', + ) # setup code signing setup_keychain @@ -132,7 +158,7 @@ platform :ios do end clean_build_artifacts - + # build and upload to TestFlight build_app( scheme: 'openHAB', diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj index 5852d3c3..50da1010 100644 --- a/openHAB.xcodeproj/project.pbxproj +++ b/openHAB.xcodeproj/project.pbxproj @@ -453,9 +453,6 @@ DFB2624518830A3600D3244D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; DFB2624C18830A3600D3244D /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; DFDA3CE9193CADB200888039 /* ping.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ping.wav; sourceTree = ""; }; - DFDEE4161883C6A5008B26AC /* OpenHABTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABTracker.swift; sourceTree = ""; }; - DFDEE3FC18831099008B26AC /* OpenHABSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABSettingsViewController.swift; sourceTree = ""; }; - DFDF452E1932032B00A6E581 /* OpenHABLegalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABLegalViewController.swift; sourceTree = ""; }; DFDF45301932042B00A6E581 /* legal.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = legal.rtf; sourceTree = ""; }; DFE10413197415F900D94943 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; DFFD8FD018EDBD4F003B502A /* UICircleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICircleButton.swift; sourceTree = ""; }; @@ -842,8 +839,6 @@ isa = PBXGroup; children = ( DFB2624518830A3600D3244D /* Images.xcassets */, - DACB636127D3FC6500041931 /* error.png */, - DF4B84061885AE0E00F34902 /* blankicon.png */, ); name = Images; sourceTree = ""; @@ -933,7 +928,6 @@ DFDEE3FE1883228C008B26AC /* Models */, DF4B84051885AD4600F34902 /* Images */, DF4B83FD18857FA100F34902 /* UI */, - DFDEE3FF18832293008B26AC /* Util */, 1224F78B228A89E300750965 /* Watch */, DFB2623118830A3600D3244D /* Supporting Files */, 938BF9C724EFCCC000E6B52F /* Resources */, @@ -1092,8 +1086,8 @@ 1224F7B7228A8AE600750965 /* Embed Watch Content */, 93F38C4C23803499001B1451 /* Embed Frameworks */, 4D6470DE2561F935007B03FC /* Embed Foundation Extensions */, + DAF0A2902C56FE9F00A14A6A /* Run swiftformat & swiftlint */, 93F8063627AE76AF0035A6B0 /* Crashlytics Run Script */, - DAF0A2902C56FE9F00A14A6A /* Run Build Tools */, ); buildRules = ( ); @@ -1295,6 +1289,11 @@ inputFileListPaths = ( ); inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist", + "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist", + "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", ); name = "Crashlytics Run Script"; outputFileListPaths = ( @@ -1305,7 +1304,7 @@ shellPath = /bin/sh; shellScript = "${BUILD_DIR%Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\n"; }; - DAF0A2902C56FE9F00A14A6A /* Run Build Tools */ = { + DAF0A2902C56FE9F00A14A6A /* Run swiftformat & swiftlint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1314,7 +1313,7 @@ ); inputPaths = ( ); - name = "Run Build Tools"; + name = "Run swiftformat & swiftlint"; outputFileListPaths = ( ); outputPaths = ( @@ -1452,8 +1451,6 @@ 938BF9D324EFD0B700E6B52F /* UIViewController+Localization.swift in Sources */, DAA42BA821DC97E000244B2A /* NotificationTableViewCell.swift in Sources */, DAF0A28F2C56F1EE00A14A6A /* ColorPickerCell.swift in Sources */, - 6595667E28E0BE8E00E8A53B /* MulticastDelegate.swift in Sources */, - DFDEE3FD18831099008B26AC /* OpenHABSettingsViewController.swift in Sources */, 938EDCE122C4FEB800661CA1 /* ScaleAspectFitImageView.swift in Sources */, DAEAA89F21E6B16600267EA3 /* UITableView.swift in Sources */, DFB2624418830A3600D3244D /* OpenHABSitemapViewController.swift in Sources */, @@ -1575,9 +1572,9 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1596,7 +1593,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.openHABIntents; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.openHABIntents"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1617,9 +1614,9 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1637,7 +1634,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.openHABIntents; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.openHABIntents"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; @@ -1656,11 +1653,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = PBAPXHRAM9; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -1682,6 +1681,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.NotificationService"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app.NotificationService"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1702,11 +1703,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PBAPXHRAM9; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -1727,7 +1730,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.NotificationService"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app.NotificationService"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1746,10 +1750,10 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1791,11 +1795,11 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1838,11 +1842,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "openHABWatch Extension/openHABWatch Extension.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = PBAPXHRAM9; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=watchos*]" = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = openHABWatch/Info.plist; @@ -1856,7 +1862,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.watchkitapp"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match AppStore org.openhab.app.watchkitapp"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -1882,12 +1889,14 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "openHABWatch Extension/openHABWatch Extension.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = PBAPXHRAM9; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=watchos*]" = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = openHABWatch/Info.plist; @@ -1901,7 +1910,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.watchkitapp"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match AppStore org.openhab.app.watchkitapp"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -1924,10 +1934,10 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1966,11 +1976,11 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 19; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2127,13 +2137,16 @@ CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = openHAB/openHAB.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; - DEVELOPMENT_TEAM = PBAPXHRAM9; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "openHAB/openHAB-Prefix.pch"; INFOPLIST_FILE = "openHAB/openHAB-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = openHAB; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2149,7 +2162,8 @@ PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app"; SUPPORTS_MACCATALYST = NO; SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -2170,13 +2184,16 @@ CLANG_CXX_LANGUAGE_STANDARD = "$(inherited)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = openHAB/openHAB.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 12; - DEVELOPMENT_TEAM = PBAPXHRAM9; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 19; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "openHAB/openHAB-Prefix.pch"; INFOPLIST_FILE = "openHAB/openHAB-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = openHAB; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2191,7 +2208,8 @@ PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app"; SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_INSTALL_OBJC_HEADER = NO; diff --git a/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved b/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8fe2c82b..6496c563 100644 --- a/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "748c7837511d0e6a507737353af268484e1745e2", + "version" : "1.2024011601.1" + } + }, { "identity" : "alamofire", "kind" : "remoteSourceControl", @@ -9,6 +18,114 @@ "version" : "5.9.1" } }, + { + "identity" : "alamofirenetworkactivityindicator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/AlamofireNetworkActivityIndicator.git", + "state" : { + "revision" : "392bed083e8d193aca16bfa684ee24e4bcff0510", + "version" : "3.1.0" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "076b241a625e25eac22f8849be256dfb960fcdfe", + "version" : "10.19.1" + } + }, + { + "identity" : "cocoalumberjack", + "kind" : "remoteSourceControl", + "location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git", + "state" : { + "revision" : "4b8714a7fb84d42393314ce897127b3939885ec3", + "version" : "3.8.5" + } + }, + { + "identity" : "devicekit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devicekit/DeviceKit.git", + "state" : { + "revision" : "d37e70cb2646666dcf276d7d3d4a9760a41ff8a6", + "version" : "4.9.0" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "9d17b500cd98d9a7009751ad62f802e152e97021", + "version" : "10.26.0" + } + }, + { + "identity" : "flexcolorpicker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/RastislavMirek/FlexColorPicker.git", + "state" : { + "revision" : "72a5c2c5e28074e6c5f13efe3c98eb780ae2f906", + "version" : "1.4.4" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "16244d177c4e989f87b25e9db1012b382cfedc55", + "version" : "10.25.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", + "version" : "9.4.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6", + "version" : "7.13.3" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359", + "version" : "1.62.2" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "0382ca27f22fb3494cf657d8dc356dc282cd1193", + "version" : "3.4.1" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", + "version" : "100.0.0" + } + }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -17,6 +134,87 @@ "revision" : "5b92f029fab2cce44386d28588098b5be0824ef5", "version" : "7.11.0" } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", + "version" : "2.30910.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version" : "2.4.0" + } + }, + { + "identity" : "sfsafesymbols", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", + "state" : { + "revision" : "e2e28f4e56e1769c2ec3c61c9355fc64eb7a535a", + "version" : "5.3.0" + } + }, + { + "identity" : "sidemenu", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jonkykong/SideMenu.git", + "state" : { + "revision" : "8bd4fd128923cf5494fa726839af8afe12908ad9", + "version" : "6.5.0" + } + }, + { + "identity" : "svgkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SVGKit/SVGKit.git", + "state" : { + "branch" : "3.x", + "revision" : "02421928cab787faaffb2403d47c39392936fbc7" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log", + "state" : { + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", + "version" : "1.26.0" + } + }, + { + "identity" : "swiftmessages", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwiftKickMobile/SwiftMessages.git", + "state" : { + "revision" : "62e12e138fc3eedf88c7553dd5d98712aa119f40", + "version" : "9.0.9" + } } ], "version" : 2 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 fdea04ef..ec4a69cc 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 47d26f23..bc84d17d 100644 --- a/openHAB/OpenHABSitemapViewController.swift +++ b/openHAB/OpenHABSitemapViewController.swift @@ -85,6 +85,7 @@ class OpenHABSitemapViewController: OpenHABViewController, GenericUITableViewCel private let search = UISearchController(searchResultsController: nil) private var isUserInteracting = false private var isWaitingToReload = false + private let logger = Logger(subsystem: "org.openhab.app", category: "OpenHABSitemapViewController") var relevantPage: OpenHABSitemapPage? { if isFiltering { @@ -175,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 _: @@ -478,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 { @@ -777,16 +780,13 @@ extension OpenHABSitemapViewController: UITableViewDelegate, UITableViewDataSour newViewController.openHABRootUrl = openHABRootUrl navigationController?.pushViewController(newViewController, animated: true) } else if widget?.type == .selection { - os_log("Selected selection widget", log: .viewCycle, type: .info) selectedWidgetRow = indexPath.row let selectedWidget: OpenHABWidget? = relevantWidget(indexPath: indexPath) + let selectionItemState = selectedWidget?.item?.state + logger.info("Selected selection widget in status: \(selectionItemState ?? "unknown")") let hostingController = UIHostingController(rootView: SelectionView( mappings: selectedWidget?.mappingsOrItemOptions ?? [], - selectionItem: - Binding( - get: { selectedWidget?.item }, - set: { selectedWidget?.item = $0 } - ), + selectionItemState: selectionItemState, onSelection: { selectedMappingIndex in let selectedWidget: OpenHABWidget? = self.relevantPage?.widgets[self.selectedWidgetRow] let selectedMapping: OpenHABWidgetMapping? = selectedWidget?.mappingsOrItemOptions[selectedMappingIndex] 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 diff --git a/openHAB/SelectionView.swift b/openHAB/SelectionView.swift index cd8c2d38..ad31af6d 100644 --- a/openHAB/SelectionView.swift +++ b/openHAB/SelectionView.swift @@ -15,39 +15,42 @@ import SwiftUI struct SelectionView: View { var mappings: [OpenHABWidgetMapping] // List of mappings (instead of AnyHashable, we use a concrete type) - @Binding var selectionItem: OpenHABItem? // Binding to track the selected item state + @State var selectionItemState: String? // To track the selected item state var onSelection: (Int) -> Void // Closure to handle selection + private let logger = Logger(subsystem: "org.openhab.app", category: "SelectionView") + var body: some View { List(0 ..< mappings.count, id: \.self) { index in let mapping = mappings[index] HStack { Text(mapping.label) Spacer() - if selectionItem?.state == mapping.command { + if selectionItemState == mapping.command { Image(systemSymbol: .checkmark) .foregroundColor(.blue) } } - .contentShape(Rectangle()) // Ensures entire row is tappable + .contentShape(.interaction, Rectangle()) // Ensures entire row is tappable .onTapGesture { - os_log("Selected mapping %d", log: .viewCycle, type: .info, index) + selectionItemState = mappings[index].command + logger.info("Selected mapping \(index)") onSelection(index) } + .accessibilityElement(children: .combine) + .accessibilityAddTraits(.isButton) } .navigationTitle("Select Mapping") // Navigation title } } #Preview { - let selectedItem: OpenHABItem? = OpenHABItem(name: "", type: "", state: "command2", link: "", label: nil, groupType: nil, stateDescription: nil, commandDescription: nil, members: [], category: nil, options: nil) - - return SelectionView( + SelectionView( mappings: [ OpenHABWidgetMapping(command: "command1", label: "Option 1"), OpenHABWidgetMapping(command: "command2", label: "Option 2") ], - selectionItem: .constant(selectedItem) + selectionItemState: "command2" ) { selectedMappingIndex in print("Selected mapping at index \(selectedMappingIndex)") }