diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9197feca..1c0d720f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,14 +15,14 @@ on: jobs: build_and_deploy: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.ACCESS_TOKEN }} fetch-depth: 0 @@ -36,7 +36,7 @@ jobs: echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config - name: SSH Keys - uses: webfactory/ssh-agent@v0.7.0 + uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: | ${{ secrets.MATCH_GIT_PRIVATE_KEY }} diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index e87a5f25..706941d4 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -7,14 +7,14 @@ on: jobs: build_and_test: - runs-on: macos-13 + runs-on: macos-14 if: ${{ github.event.pull_request.draft == false }} steps: - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: | @@ -25,6 +25,6 @@ jobs: LANG: en_US.UTF-8 LC_ALL: en_US.UTF-8 FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 60 - uses: maierj/fastlane-action@v2.3.0 + uses: maierj/fastlane-action@v3.1.0 with: lane: unittests diff --git a/Gemfile.lock b/Gemfile.lock index 22b2a1e0..a7dd3d1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,32 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.15) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.880.0) - aws-sdk-core (3.190.2) + aws-partitions (1.952.0) + aws-sdk-core (3.201.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.76.0) - aws-sdk-core (~> 3, >= 3.188.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.142.0) - aws-sdk-core (~> 3, >= 3.189.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) + aws-sigv4 (~> 1.5) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) + base64 (0.2.0) claide (1.1.0) colored (1.2) colored2 (3.1.2) @@ -35,7 +38,7 @@ GEM domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.109.0) + excon (0.110.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -64,15 +67,15 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.219.0) + fastimage (2.3.1) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -93,10 +96,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -105,13 +108,13 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-changelog (0.16.0) fastlane-plugin-versioning (0.5.2) gh_inspector (1.1.3) google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.2) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -119,19 +122,18 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick google-apis-iamcredentials_v1 (0.17.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-playcustomapp_v1 (0.13.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.1) + google-cloud-core (1.7.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) + google-cloud-errors (1.4.0) google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -147,34 +149,37 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.7.1) - jwt (2.7.1) - mini_magick (4.12.0) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.1) mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.1) nanaimo (0.3.0) naturally (2.2.1) - optparse (0.4.0) + nkf (0.2.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) - public_suffix (5.0.4) - rake (13.1.0) + public_suffix (6.0.0) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.2.9) + strscan rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) - signet (0.18.0) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -182,6 +187,7 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -192,9 +198,8 @@ GEM tty-cursor (~> 0.7) uber (0.1.0) unicode-display_width (2.5.0) - webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -215,4 +220,4 @@ DEPENDENCIES fastlane-plugin-versioning BUNDLED WITH - 2.5.4 + 2.5.7 diff --git a/README.md b/README.md index b0235a9d..1e4a8ae1 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,159 @@ ## Introduction -This app is a native client for openHAB which allows easy access to your sitemaps. +This is the IOS native client for openHAB. -Download on the App Store +

+Main UI +Sitemap +

+ +## Download + +### openHAB (Current) + +This is the primary openHAB app which contains the latest features and is updated regularly. This includes Apple Watch support, enhanced notifications, widgets and more. +Requires at least iOS 16 and openHAB 2.x and later. + +(Official App Store Link Coming Soon) + +Beta releases are available on [TestFlight](https://testflight.apple.com/join/0uFYONeF). + +Download on TestFlight + +### openHAB V1 (Legacy) + +This is the legacy app for users on iOS 15 or earlier as well as openHAB system 1.x and later (tested to at least openHAB 4). +This app only receives security updates and minor fixes and is not intended for most users. + +Download on the App Store Beta releases are available on [TestFlight](https://testflight.apple.com/join/563WBakc). ## Features -* Control your openHAB server and [openHAB Cloud instance](https://github.com/openhab/openhab-cloud) -* Receive notifications from openHAB Cloud -* Supports wall mounted tablets +* Control your openHAB server directly and through a [openHAB Cloud instance](https://github.com/openhab/openhab-cloud) +* [Enhanced push notification](#push-notifications) from openHAB Cloud and the openHAB cloud binding +* [Apple Watch](#apple-watch-configuration) companion app +* [Widgets](#widgets) (coming soon!) + +## App Configuration +

+Logo +Logo +

-## Setting up development environment +### Connection Settings +The app will try and connect using the Local URL as the primary connection, and if that fails or is not reachable, falls back to the local URL. + +### Demo Mode +This sets up the app to use the openHAB demo server and can be used to experience the app without needing to install openHAB. + +#### Local URL +This is the primary connection to your openHAB instance, a fully qualified URL with a IP or host is required. + +Example: +`https://openhab.local:8443` + +#### Remote URL +This is the secondary connection to your openHAB instance, a fully qualified URL with a IP or host is required. +If using the openHAB cloud service, leave this as the default setting of `https://myopenhab.org`. +When set to the public cloud, the app will also register for push notifications (as long as credentials are correct) + +Example: +`https://myopenhab.org` + +### Username / Password +This will be sent if the local or remote server challenges for authentication, or if "Always Send Credentials" is checked on. +If using the openHAB cloud, these should be set to those login credentials. + +### Application Settings + +### Certificates +Allows the installation of p12 formatted certificates for use in client side authentication setups. + +To install a client certificate, rename the certificate with the extension `.ohp12`, then send it to your iOS device (airdrop, icloud, dropbox, etc..), then open/save and select `openHAB` from the "Open In" menu (you may need to select "More..." to see all apps). + +To delete a certificate, swipe left on the certificate name in the certificate menu + +If using openssl v3 to generate certificates, make sure to add `-legacy -certpbe pbeWithSHA1And40BitRC2-CBC` to the pk12 export command. +See https://github.com/openssl/openssl/issues/19871 for more information on V3 compatibility with Apple products. + +### Idle Timeout +Useful for wall or fixed installations, will disable the Idle screen timeout. + +### Crash Reporting +Sends crash reports to Google / Firebase. + +### Main UI Settings +#### WebRTC +Allows audio and video communications in the Main UI for views and widgets that require it. + +#### Default Path +Allows the user to enter a path to act as the starting point when the Main UI is loaded. +Clicking the "+" button will prompt to enter the current path the of Main UI view. + +#### Clear Cache +Clears the Main UI web cache. + +### Sitemap Settings + +#### Realtime Sliders +Allows sitemap sliders to send changes as the control is sliding in realtime. + +### Image Cache +Clears the sitemap cached images. + +### Icon Type +Select which type of icon the sitemap view will attempt to load for icons. + +### Sitemap Sorting +Sort order when presenting multiple sitemaps for selection. + +## Apple Watch Configuration +The Apple watch requires a sitemap with the name `watch.sitemap`. +Note that some advanced sitemap features may not be supported on the Apple watch and its recommended to keep this sitemap simple and appropriate for interaction on a small display. + +When using the Watch app, slide left to bring up the configuration view and select "sync" to ensure the local, remote and username/password configurations are synced to the watch. + +## Main UI and Sitemap Usage +Logo + +Clicking "Home" will navigate to the Main UI from the user's openHAB system. Clicking this when the Main UI is already visible will force a reload the Main UI. + +Tiles are the alternative UIs installed on a user's system and will be opened in an embedded browser. + +Sitemaps show the available sitemaps on the users system. Selecting a sitemap will present the native sitemap renderer view. + +Notifications is a list of push notification retrieved from the openHAB cloud (if configured). + +Settings opens the application settings view. + +The app will persist the last primary view opened (Main UI or Sitemaps) when the app is opened or restarted. + +## Push Notifications +The [openHAB Cloud Connector](https://next.openhab.org/addons/integrations/openhabcloud/) allows users to send push notifications mobile devices registered with an [openHAB Cloud instance](https://github.com/openhab/openhab-cloud) such as [myopenHAB.org](https://www.myopenhab.org). + +

+Logo +

+ +Push Notifications on iOS support: +- Title and message text +- Image and video attachments +- Up to 3 action buttons (long press notification) +- Collapsible / updated notifications +- Removing notifications + +## Widgets + +Coming soon ! + +## Setting up development environment If you want to contribute to the iOS application we are here to help you to set up -development environment. openHAB iOS app is developed using Xcode and the standard iOS SDK from Apple. -The iOS application is based on the iOS 12 and watchOS 7 SDK and makes uses of several Swift packages. +development environment. +openHAB iOS app is developed using Xcode and the standard iOS SDK from Apple. +The iOS application is based on the iOS 16 and watchOS 8 SDK and makes uses of several Swift packages. To start developing you need an [Apple Developer](https://developer.apple.com/devcenter/ios/index.action) account. @@ -39,7 +176,6 @@ And also please support with the localization of openhab-ios: ## Trademark Disclaimer - Product names, logos, brands and other trademarks referred to within the openHAB website are the property of their respective trademark holders. These trademark holders are not affiliated with openHAB or our website. They do not sponsor or endorse our materials. diff --git a/doc/mainui.png b/doc/mainui.png new file mode 100644 index 00000000..169ac87b Binary files /dev/null and b/doc/mainui.png differ diff --git a/doc/notifications.png b/doc/notifications.png new file mode 100644 index 00000000..9b25198c Binary files /dev/null and b/doc/notifications.png differ diff --git a/doc/settings1.jpeg b/doc/settings1.jpeg new file mode 100644 index 00000000..9b58180b Binary files /dev/null and b/doc/settings1.jpeg differ diff --git a/doc/settings2.jpeg b/doc/settings2.jpeg new file mode 100644 index 00000000..f6ea3f16 Binary files /dev/null and b/doc/settings2.jpeg differ diff --git a/doc/sidemenu.jpeg b/doc/sidemenu.jpeg new file mode 100644 index 00000000..3d0ac9a6 Binary files /dev/null and b/doc/sidemenu.jpeg differ diff --git a/doc/sitemap.png b/doc/sitemap.png new file mode 100644 index 00000000..fd051dd7 Binary files /dev/null and b/doc/sitemap.png differ diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f7b5db50..6a4e1918 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -57,7 +57,7 @@ platform :ios do scheme: 'openHABTestsSwift', xcargs: '-skipPackagePluginValidation', testplan: 'openHABTests', - devices: ['iPhone 14 Pro'], + devices: ['iPhone 15 Pro'], clean: true ) end @@ -71,7 +71,7 @@ platform :ios do end desc 'Build beta' - lane :beta do |options| + lane :beta do |options| options[:bump] = options[:bump] != nil ? options[:bump] : "patch" if is_ci? @@ -107,7 +107,7 @@ platform :ios do scheme: 'openHAB' ) end - increment_version_if_required + # increment_version_if_required # NOTE: this resolves packages of the project so need to clean sh("cd .. && git clean -fd") diff --git a/fastlane/Matchfile b/fastlane/Matchfile index e377c182..f0afd003 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -4,7 +4,7 @@ storage_mode("git") type("appstore") # The default type, can be: appstore, adhoc, enterprise or development -app_identifier(["org.openhab.app", "org.openhab.app.openHABIntents"]) +app_identifier(["org.openhab.app", "org.openhab.app.openHABIntents", "org.openhab.app.watchkitapp", "org.openhab.app.NotificationService"]) # username("user@fastlane.tools") # Your Apple Developer Portal username # For all available options run `fastlane match --help` diff --git a/fastlane/Snapfile b/fastlane/Snapfile index 6029f593..45128f72 100644 --- a/fastlane/Snapfile +++ b/fastlane/Snapfile @@ -2,10 +2,10 @@ #A list of devices you want to take the screenshots from devices([ - "iPhone 13 Pro Max", + "iPhone 15 Pro Max", "iPhone 8 Plus", - "iPad Pro (12.9-inch) (4th generation)", - "iPad Pro (11-inch) (3rd generation)" + "iPad Pro 13-inch (M4)", + "iPad Pro (12.9-inch) (6th generation)" ]) #languages([ @@ -31,7 +31,7 @@ skip_open_summary true override_status_bar(true) # Do not check for most recent SnapshotHelper code -skip_helper_version_check true +skip_helper_version_check false # Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments # launch_arguments(["-favColor red"]) diff --git a/fastlane/SnapshotHelper.swift b/fastlane/SnapshotHelper.swift index e97caeee..6dec1302 100644 --- a/fastlane/SnapshotHelper.swift +++ b/fastlane/SnapshotHelper.swift @@ -1,3 +1,10 @@ +// +// SnapshotHelper.swift +// Example +// +// Created by Felix Krause on 10/8/15. +// + // ----------------------------------------------------- // IMPORTANT: When modifying this file, make sure to // increment the version number at the very @@ -8,13 +15,12 @@ import Foundation import XCTest -var deviceLanguage = "" -var locale = "" - +@MainActor func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations) } +@MainActor func snapshot(_ name: String, waitForLoadingIndicator: Bool) { if waitForLoadingIndicator { Snapshot.snapshot(name) @@ -26,27 +32,19 @@ func snapshot(_ name: String, waitForLoadingIndicator: Bool) { /// - Parameters: /// - name: The name of the snapshot /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait. +@MainActor func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { Snapshot.snapshot(name, timeWaitingForIdle: timeout) } enum SnapshotError: Error, CustomDebugStringConvertible { - case cannotDetectUser - case cannotFindHomeDirectory case cannotFindSimulatorHomeDirectory - case cannotAccessSimulatorHomeDirectory(String) case cannotRunOnPhysicalDevice var debugDescription: String { switch self { - case .cannotDetectUser: - return "Couldn't find Snapshot configuration files - can't detect current user " - case .cannotFindHomeDirectory: - return "Couldn't find Snapshot configuration files - can't detect `Users` dir" case .cannotFindSimulatorHomeDirectory: return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." - case let .cannotAccessSimulatorHomeDirectory(simulatorHostHome): - return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?" case .cannotRunOnPhysicalDevice: return "Can't use Snapshot on a physical device." } @@ -54,25 +52,29 @@ enum SnapshotError: Error, CustomDebugStringConvertible { } @objcMembers +@MainActor open class Snapshot: NSObject { static var app: XCUIApplication? static var waitForAnimations = true static var cacheDirectory: URL? static var screenshotsDirectory: URL? { - cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true) + return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true) } + static var deviceLanguage = "" + static var currentLocale = "" open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { + Snapshot.app = app Snapshot.waitForAnimations = waitForAnimations do { - let cacheDir = try pathPrefix() + let cacheDir = try getCacheDirectory() Snapshot.cacheDirectory = cacheDir setLanguage(app) setLocale(app) setLaunchArguments(app) - } catch { + } catch let error { NSLog(error.localizedDescription) } } @@ -104,17 +106,17 @@ open class Snapshot: NSObject { do { let trimCharacterSet = CharacterSet.whitespacesAndNewlines - locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) + currentLocale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) } catch { NSLog("Couldn't detect/set locale...") } - if locale.isEmpty, !deviceLanguage.isEmpty { - locale = Locale(identifier: deviceLanguage).identifier + if currentLocale.isEmpty && !deviceLanguage.isEmpty { + currentLocale = Locale(identifier: deviceLanguage).identifier } - if !locale.isEmpty { - app.launchArguments += ["-AppleLocale", "\"\(locale)\""] + if !currentLocale.isEmpty { + app.launchArguments += ["-AppleLocale", "\"\(currentLocale)\""] } } @@ -152,40 +154,67 @@ open class Snapshot: NSObject { } #if os(OSX) - guard let app = self.app else { - NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") - return - } + guard let app = self.app else { + NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + return + } - app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) + app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) #else - guard self.app != nil else { - NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") - return - } - - let screenshot = XCUIScreen.main.screenshot() - guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } + guard self.app != nil else { + NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + return + } - do { - // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices - let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") - let range = NSRange(location: 0, length: simulator.count) - simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") + let screenshot = XCUIScreen.main.screenshot() + #if os(iOS) && !targetEnvironment(macCatalyst) + let image = XCUIDevice.shared.orientation.isLandscape ? fixLandscapeOrientation(image: screenshot.image) : screenshot.image + #else + let image = screenshot.image + #endif + + guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } + + do { + // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices + let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") + let range = NSRange(location: 0, length: simulator.count) + simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") + + let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") + #if swift(<5.0) + try UIImagePNGRepresentation(image)?.write(to: path, options: .atomic) + #else + try image.pngData()?.write(to: path, options: .atomic) + #endif + } catch let error { + NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") + NSLog(error.localizedDescription) + } + #endif + } - let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") - try screenshot.pngRepresentation.write(to: path) - } catch { - NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") - NSLog(error.localizedDescription) - } + class func fixLandscapeOrientation(image: UIImage) -> UIImage { + #if os(watchOS) + return image + #else + if #available(iOS 10.0, *) { + let format = UIGraphicsImageRendererFormat() + format.scale = image.scale + let renderer = UIGraphicsImageRenderer(size: image.size, format: format) + return renderer.image { context in + image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) + } + } else { + return image + } #endif } class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) { #if os(tvOS) - return + return #endif guard let app = self.app else { @@ -198,40 +227,28 @@ open class Snapshot: NSObject { _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout) } - class func pathPrefix() throws -> URL? { - let homeDir: URL + class func getCacheDirectory() throws -> URL { + let cachePath = "Library/Caches/tools.fastlane" // on OSX config is stored in /Users//Library // and on iOS/tvOS/WatchOS it's in simulator's home dir #if os(OSX) - guard let user = ProcessInfo().environment["USER"] else { - throw SnapshotError.cannotDetectUser - } - - guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else { - throw SnapshotError.cannotFindHomeDirectory - } - - homeDir = usersDir.appendingPathComponent(user) - #else - #if arch(i386) || arch(x86_64) - guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { - throw SnapshotError.cannotFindSimulatorHomeDirectory - } - guard let homeDirUrl = URL(string: simulatorHostHome) else { - throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome) - } - homeDir = URL(fileURLWithPath: homeDirUrl.path) + let homeDir = URL(fileURLWithPath: NSHomeDirectory()) + return homeDir.appendingPathComponent(cachePath) + #elseif arch(i386) || arch(x86_64) || arch(arm64) + guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { + throw SnapshotError.cannotFindSimulatorHomeDirectory + } + let homeDir = URL(fileURLWithPath: simulatorHostHome) + return homeDir.appendingPathComponent(cachePath) #else - throw SnapshotError.cannotRunOnPhysicalDevice - #endif + throw SnapshotError.cannotRunOnPhysicalDevice #endif - return homeDir.appendingPathComponent("Library/Caches/tools.fastlane") } } private extension XCUIElementAttributes { var isNetworkLoadingIndicator: Bool { - if hasWhiteListedIdentifier { return false } + if hasAllowListedIdentifier { return false } let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20) let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3) @@ -239,10 +256,10 @@ private extension XCUIElementAttributes { return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize } - var hasWhiteListedIdentifier: Bool { - let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] + var hasAllowListedIdentifier: Bool { + let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] - return whiteListedIdentifiers.contains(identifier) + return allowListedIdentifiers.contains(identifier) } func isStatusBar(_ deviceWidth: CGFloat) -> Bool { @@ -258,15 +275,16 @@ private extension XCUIElementAttributes { private extension XCUIElementQuery { var networkLoadingIndicators: XCUIElementQuery { - let isNetworkLoadingIndicator = NSPredicate { evaluatedObject, _ in + let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in guard let element = evaluatedObject as? XCUIElementAttributes else { return false } return element.isNetworkLoadingIndicator } - return containing(isNetworkLoadingIndicator) + return self.containing(isNetworkLoadingIndicator) } + @MainActor var deviceStatusBars: XCUIElementQuery { guard let app = Snapshot.app else { fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") @@ -274,22 +292,22 @@ private extension XCUIElementQuery { let deviceWidth = app.windows.firstMatch.frame.width - let isStatusBar = NSPredicate { evaluatedObject, _ in + let isStatusBar = NSPredicate { (evaluatedObject, _) in guard let element = evaluatedObject as? XCUIElementAttributes else { return false } return element.isStatusBar(deviceWidth) } - return containing(isStatusBar) + return self.containing(isStatusBar) } } private extension CGFloat { func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool { - numberA ... numberB ~= self + return numberA...numberB ~= self } } // Please don't remove the lines below // They are used to detect outdated configuration files -// SnapshotHelperVersion [1.21] +// SnapshotHelperVersion [1.30] diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj index 77423c95..70188299 100644 --- a/openHAB.xcodeproj/project.pbxproj +++ b/openHAB.xcodeproj/project.pbxproj @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1665,10 +1665,12 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; @@ -1685,6 +1687,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.openHABIntents; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app.openHABIntents"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; @@ -1705,7 +1708,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -1750,10 +1753,12 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -1774,6 +1779,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app.NotificationService"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1795,7 +1801,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1811,6 +1817,10 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = es.spaphone.openHABUITests; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1837,7 +1847,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1853,6 +1863,10 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = es.spaphone.openHABUITests; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1878,7 +1892,7 @@ CODE_SIGN_ENTITLEMENTS = "openHABWatch Extension/openHABWatch Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = "compiler-default"; @@ -1921,11 +1935,13 @@ 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[sdk=watchos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; + "DEVELOPMENT_TEAM[sdk=watchos*]" = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = openHABWatch/Info.plist; @@ -1940,6 +1956,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match AppStore org.openhab.app.watchkitapp"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -1965,7 +1982,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2008,7 +2025,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2167,7 +2184,7 @@ CODE_SIGN_ENTITLEMENTS = openHAB/openHAB.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = PBAPXHRAM9; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "openHAB/openHAB-Prefix.pch"; @@ -2209,9 +2226,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = openHAB/openHAB.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = PBAPXHRAM9; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "openHAB/openHAB-Prefix.pch"; INFOPLIST_FILE = "openHAB/openHAB-Info.plist"; @@ -2230,6 +2249,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + "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/openHABUITests/OpenHABUITests.swift b/openHABUITests/OpenHABUITests.swift index ed0d4d04..ee432f07 100644 --- a/openHABUITests/OpenHABUITests.swift +++ b/openHABUITests/OpenHABUITests.swift @@ -13,6 +13,7 @@ import os.log import XCTest class OpenHABUITests: XCTestCase { + @MainActor override func setUp() { let app = XCUIApplication() app.launchEnvironment = ["UITest": "1"] @@ -21,10 +22,12 @@ class OpenHABUITests: XCTestCase { app.launch() } + @MainActor override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } + @MainActor func testShots() { let app = XCUIApplication() let hamburgerButton = app.navigationBars.buttons["HamburgerButton"]