From 2a521859581a1d179ff56ed5460bf33899e47d39 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 7 Jan 2017 01:28:03 -0800 Subject: [PATCH] SwiftLint part deux --- .swiftlint.yml | 2 + .../NotificationService.swift | 24 +- HomeAssistant/AppDelegate.swift | 141 ++-- .../Components/BinarySensorComponent.swift | 3 +- .../Classes/Components/ClimateComponent.swift | 83 +- .../Components/DeviceTrackerComponent.swift | 6 +- .../Classes/Components/InputSlider.swift | 7 +- .../Classes/Components/LightComponent.swift | 3 +- .../Components/MediaPlayerComponent.swift | 26 +- .../Components/ThermostatComponent.swift | 41 +- .../Classes/Components/ZoneComponent.swift | 9 +- HomeAssistant/Classes/Entity.swift | 43 +- HomeAssistant/HAAPI.swift | 718 ++++++++++-------- .../Utilities/NSURL+QueryDictionary.swift | 28 +- .../Utilities/OpenInChromeController.swift | 10 +- HomeAssistant/Utilities/Utils.swift | 167 ++-- HomeAssistant/Views/AboutViewController.swift | 35 +- .../Views/DevicesMapViewController.swift | 41 +- .../EntityAttributesViewController.swift | 143 ++-- HomeAssistant/Views/EurekaLocationRow.swift | 7 +- HomeAssistant/Views/GroupViewController.swift | 120 +-- .../Views/RootTabBarViewController.swift | 76 +- .../Views/SettingsDetailViewController.swift | 46 +- .../Views/SettingsViewController.swift | 158 ++-- .../MjpegStreamingController.swift | 20 +- .../NotificationViewController.swift | 69 +- 26 files changed, 1252 insertions(+), 774 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index e37dbd05b..dad7ab2de 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,5 @@ +disabled_rules: + - variable_name excluded: - fastlane - HomeAssistantTests diff --git a/APNSAttachmentService/NotificationService.swift b/APNSAttachmentService/NotificationService.swift index d289457ba..f1a817bec 100644 --- a/APNSAttachmentService/NotificationService.swift +++ b/APNSAttachmentService/NotificationService.swift @@ -17,7 +17,10 @@ final class NotificationService: UNNotificationServiceExtension { private var baseURL: String = "" private var apiPassword: String = "" - override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + // swiftlint:disable cyclomatic_complexity + // swiftlint:disable function_body_length + override func didReceive(_ request: UNNotificationRequest, + withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { print("APNSAttachmentService started!") print("Received userInfo", request.content.userInfo) self.contentHandler = contentHandler @@ -101,16 +104,23 @@ final class NotificationService: UNNotificationServiceExtension { attachmentOptions[UNNotificationAttachmentOptionsThumbnailHiddenKey] = attachmentHideThumbnail } guard let attachmentData = NSData(contentsOf:attachmentURL) else { return failEarly() } - guard let attachment = UNNotificationAttachment.create(fileIdentifier: attachmentURL.lastPathComponent, data: attachmentData, options: attachmentOptions) else { return failEarly() } + guard let attachment = UNNotificationAttachment.create(fileIdentifier: attachmentURL.lastPathComponent, + data: attachmentData, + options: attachmentOptions) else { + return failEarly() + } content.attachments.append(attachment) - contentHandler(content.copy() as! UNNotificationContent) + if let copiedContent = content.copy() as? UNNotificationContent { + contentHandler(copiedContent) + } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. - // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + // Use this as an opportunity to deliver your "best attempt" at modified content, + // otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } @@ -121,10 +131,12 @@ final class NotificationService: UNNotificationServiceExtension { extension UNNotificationAttachment { /// Save the attachment URL to disk - static func create(fileIdentifier: String, data: NSData, options: [AnyHashable : Any]?) -> UNNotificationAttachment? { + static func create(fileIdentifier: String, data: NSData, + options: [AnyHashable : Any]?) -> UNNotificationAttachment? { let fileManager = FileManager.default let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString - let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true) + let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, + isDirectory: true) do { try fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil) diff --git a/HomeAssistant/AppDelegate.swift b/HomeAssistant/AppDelegate.swift index 363f5fcc4..99a8953f5 100644 --- a/HomeAssistant/AppDelegate.swift +++ b/HomeAssistant/AppDelegate.swift @@ -14,14 +14,9 @@ import RealmSwift import UserNotifications import AlamofireNetworkActivityIndicator -let realmConfig = Realm.Configuration( - schemaVersion: 2, - - migrationBlock: { _, oldSchemaVersion in - if (oldSchemaVersion < 1) { - } -}) +let realmConfig = Realm.Configuration(schemaVersion: 2, migrationBlock: nil) +// swiftlint:disable:next force_try let realm = try! Realm(configuration: realmConfig) @UIApplicationMain @@ -31,7 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let prefs = UserDefaults(suiteName: "group.io.robbie.homeassistant")! - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { + // swiftlint:disable:next line_length + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { migrateUserDefaultsToAppGroups() Realm.Configuration.defaultConfiguration = realmConfig print("Realm file path", Realm.Configuration.defaultConfiguration.fileURL!.path) @@ -43,9 +40,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UNUserNotificationCenter.current().delegate = self } - Crashlytics.sharedInstance().setObjectValue(prefs.integer(forKey: "lastInstalledVersion"), forKey: "lastInstalledVersion") - let buildNumber = Int(string: Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion")! as! String) - prefs.set(buildNumber, forKey: "lastInstalledVersion") + Crashlytics.sharedInstance().setObjectValue(prefs.integer(forKey: "lastInstalledVersion"), + forKey: "lastInstalledVersion") + if let bundleVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") { + if let stringedBundleVersion = bundleVersion as? String { + prefs.set(stringedBundleVersion, forKey: "lastInstalledVersion") + } + } initAPI() @@ -71,47 +72,35 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return }.catch {err -> Void in print("ERROR", err) - // let settingsView = SettingsViewController() - // settingsView.title = "Settings" - // settingsView.showErrorConnectingMessage = true - // let navController = UINavigationController(rootViewController: settingsView) - // self.window?.makeKeyAndVisible() - // self.window?.rootViewController!.present(navController, animated: true, completion: nil) + let settingsView = SettingsViewController() + settingsView.title = "Settings" + settingsView.showErrorConnectingMessage = true + let navController = UINavigationController(rootViewController: settingsView) + self.window?.makeKeyAndVisible() + self.window?.rootViewController!.present(navController, animated: true, completion: nil) } } } - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } + func applicationWillResignActive(_ application: UIApplication) {} - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } + func applicationDidEnterBackground(_ application: UIApplication) {} - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } + func applicationWillEnterForeground(_ application: UIApplication) {} - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } + func applicationDidBecomeActive(_ application: UIApplication) {} - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } + func applicationWillTerminate(_ application: UIApplication) {} func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) + let tokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) - self.prefs.setValue(deviceTokenString, forKey: "deviceToken") + self.prefs.setValue(tokenString, forKey: "deviceToken") - print("Registering push with deviceTokenString: \(deviceTokenString)") + print("Registering push with tokenString: \(tokenString)") - _ = HomeAssistantAPI.sharedInstance.registerDeviceForPush(deviceToken: deviceTokenString).then { pushId -> Void in + _ = HomeAssistantAPI.sharedInstance.registerDeviceForPush(deviceToken: tokenString).then { pushId -> Void in print("Registered for push. PushID:", pushId) CLSLogv("Registered for push:", getVaList([pushId])) Crashlytics.sharedInstance().setUserIdentifier(pushId) @@ -120,54 +109,69 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } - func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Swift.Error) { + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Swift.Error) { print("Error when trying to register for push", error) Crashlytics.sharedInstance().recordError(error) } - func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { print("Received remote notification in completion handler!") - let userInfoDict = userInfo as! [String:Any] - - if let hadict = userInfoDict["homeassistant"] as? [String:String] { - if let command = hadict["command"] { - switch command { - case "request_location_update": - print("Received remote request to provide a location update") - HomeAssistantAPI.sharedInstance.sendOneshotLocation().then { success -> Void in - print("Did successfully send location when requested via APNS?", success) + if let userInfoDict = userInfo as? [String:Any] { + if let hadict = userInfoDict["homeassistant"] as? [String:String] { + if let command = hadict["command"] { + switch command { + case "request_location_update": + print("Received remote request to provide a location update") + HomeAssistantAPI.sharedInstance.sendOneshotLocation().then { success -> Void in + print("Did successfully send location when requested via APNS?", success) + completionHandler(UIBackgroundFetchResult.noData) + }.catch {error in + print("Error when attempting to submit location update") + Crashlytics.sharedInstance().recordError(error) + completionHandler(UIBackgroundFetchResult.failed) + } + default: + print("Received unknown command via APNS!", userInfo) completionHandler(UIBackgroundFetchResult.noData) - }.catch {error in - print("Error when attempting to submit location update") - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) - completionHandler(UIBackgroundFetchResult.failed) } - default: - print("Received unknown command via APNS!", userInfo) - completionHandler(UIBackgroundFetchResult.noData) } } } } - func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) { + func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, + forRemoteNotification userInfo: [AnyHashable : Any], + withResponseInfo responseInfo: [AnyHashable : Any], + completionHandler: @escaping () -> Void) { var userInput: String? = nil if let userText = responseInfo[UIUserNotificationActionResponseTypedTextKey] as? String { userInput = userText } - let _ = HomeAssistantAPI.sharedInstance.handlePushAction(identifier: identifier!, userInfo: userInfo, userInput: userInput) + let _ = HomeAssistantAPI.sharedInstance.handlePushAction(identifier: identifier!, + userInfo: userInfo, + userInput: userInput) } - func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { + func application(_ app: UIApplication, + open url: URL, + options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { var serviceData: [String:String] = url.queryItems! serviceData["sourceApplication"] = options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String switch url.host! { case "call_service": // homeassistant://call_service/device_tracker.see?entity_id=device_tracker.entity - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: url.pathComponents[1].components(separatedBy: ".")[0], service: url.pathComponents[1].components(separatedBy: ".")[1], serviceData: serviceData) + let domain = url.pathComponents[1].components(separatedBy: ".")[0] + let service = url.pathComponents[1].components(separatedBy: ".")[1] + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: domain, + service: service, + serviceData: serviceData) break case "fire_event": // homeassistant://fire_event/custom_event?entity_id=device_tracker.entity - let _ = HomeAssistantAPI.sharedInstance.CreateEvent(eventType: url.pathComponents[1], eventData: serviceData) + let _ = HomeAssistantAPI.sharedInstance.CreateEvent(eventType: url.pathComponents[1], + eventData: serviceData) break case "send_location": // homeassistant://send_location/ let _ = HomeAssistantAPI.sharedInstance.sendOneshotLocation() @@ -181,13 +185,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { @available(iOS 10, *) extension AppDelegate: UNUserNotificationCenterDelegate { - public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + public func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { var userText: String? = nil if let textInput = response as? UNTextInputNotificationResponse { userText = textInput.userText } - HomeAssistantAPI.sharedInstance.handlePushAction(identifier: response.actionIdentifier, userInfo: response.notification.request.content.userInfo, userInput: userText).then { _ in - completionHandler() + HomeAssistantAPI.sharedInstance.handlePushAction(identifier: response.actionIdentifier, + userInfo: response.notification.request.content.userInfo, + userInput: userText).then { _ in + completionHandler() }.catch { err -> Void in print("Error: \(err)") Crashlytics.sharedInstance().recordError(err) @@ -195,7 +203,10 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } } - public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + public func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + // swiftlint:disable:next line_length + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .badge, .sound]) } } diff --git a/HomeAssistant/Classes/Components/BinarySensorComponent.swift b/HomeAssistant/Classes/Components/BinarySensorComponent.swift index 8b0c160a6..ccd58ef99 100644 --- a/HomeAssistant/Classes/Components/BinarySensorComponent.swift +++ b/HomeAssistant/Classes/Components/BinarySensorComponent.swift @@ -23,13 +23,14 @@ class BinarySensor: SwitchableEntity { return "mdi:radiobox-blank" } + // swiftlint:disable:next cyclomatic_complexity override func StateIcon() -> String { if self.MobileIcon != nil { return self.MobileIcon! } if self.Icon != nil { return self.Icon! } let activated = (self.IsOn == false) if let sensorClass = self.SensorClass { - switch (sensorClass) { + switch sensorClass { case "connectivity": return activated ? "mdi:server-network-off" : "mdi:server-network" case "light": diff --git a/HomeAssistant/Classes/Components/ClimateComponent.swift b/HomeAssistant/Classes/Components/ClimateComponent.swift index 899d9f0cc..58e5c1089 100644 --- a/HomeAssistant/Classes/Components/ClimateComponent.swift +++ b/HomeAssistant/Classes/Components/ClimateComponent.swift @@ -33,18 +33,21 @@ class Climate: Entity { override func mapping(map: Map) { super.mapping(map: map) - AuxHeat <- (map["attributes.aux_heat"], ComponentBoolTransform(trueValue: "on", falseValue: "off")) - AwayMode <- (map["attributes.away_mode"], ComponentBoolTransform(trueValue: "on", falseValue: "off")) + AuxHeat <- (map["attributes.aux_heat"], + ComponentBoolTransform(trueValue: "on", falseValue: "off")) + AwayMode <- (map["attributes.away_mode"], + ComponentBoolTransform(trueValue: "on", falseValue: "off")) CurrentHumidity.value <- map["attributes.current_humidity"] CurrentTemperature.value <- map["attributes.current_temperature"] - FanMode <- map["attributes.fan_mode"] + FanMode <- map["attributes.fan_mode"] Humidity.value <- map["attributes.humidity"] MaximumHumidity.value <- map["attributes.max_humidity"] MaximumTemp.value <- map["attributes.max_temp"] MinimumHumidity.value <- map["attributes.min_humidity"] MinimumTemp.value <- map["attributes.min_temp"] - OperationMode <- map["attributes.operation_mode"] - SwingMode <- (map["attributes.swing_mode"], ComponentBoolTransform(trueValue: "on", falseValue: "off")) + OperationMode <- map["attributes.operation_mode"] + SwingMode <- (map["attributes.swing_mode"], + ComponentBoolTransform(trueValue: "on", falseValue: "off")) Temperature.value <- map["attributes.temperature"] var FanList: [String]? = nil @@ -73,43 +76,93 @@ class Climate: Entity { } func TurnFanOn() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_fan_mode", serviceData: ["entity_id": self.ID as AnyObject, "fan": "on" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_fan_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "fan": "on" as AnyObject + ]) } func TurnFanOff() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_fan_mode", serviceData: ["entity_id": self.ID as AnyObject, "fan": "off" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_fan_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "fan": "off" as AnyObject + ]) } func SetAwayModeOn() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_away_mode", serviceData: ["entity_id": self.ID as AnyObject, "away_mode": "on" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_away_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "away_mode": "on" as AnyObject + ]) } func SetAwayModeOff() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_away_mode", serviceData: ["entity_id": self.ID as AnyObject, "away_mode": "off" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_away_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "away_mode": "off" as AnyObject + ]) } func SetTemperature(_ newTemp: Float) { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_temperature", serviceData: ["entity_id": self.ID as AnyObject, "temperature": newTemp as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_temperature", + serviceData: [ + "entity_id": self.ID as AnyObject, + "temperature": newTemp as AnyObject + ]) } func SetHumidity(_ newHumidity: Int) { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_humidity", serviceData: ["entity_id": self.ID as AnyObject, "humidity": newHumidity as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_humidity", + serviceData: [ + "entity_id": self.ID as AnyObject, + "humidity": newHumidity as AnyObject + ]) } func SetSwingMode(_ newSwingMode: String) { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_swing_mode", serviceData: ["entity_id": self.ID as AnyObject, "swing_mode": newSwingMode as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_swing_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "swing_mode": newSwingMode as AnyObject + ]) } func SetOperationMode(_ newOperationMode: String) { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_operation_mode", serviceData: ["entity_id": self.ID as AnyObject, "operation_mode": newOperationMode as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_operation_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "operation_mode": newOperationMode as AnyObject + ]) } func TurnAuxHeatOn() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_aux_heat", serviceData: ["entity_id": self.ID as AnyObject, "aux_heat": "on" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_aux_heat", + serviceData: [ + "entity_id": self.ID as AnyObject, + "aux_heat": "on" as AnyObject + ]) } func TurnAuxHeatOff() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", service: "set_aux_heat", serviceData: ["entity_id": self.ID as AnyObject, "aux_heat": "off" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "climate", + service: "set_aux_heat", + serviceData: [ + "entity_id": self.ID as AnyObject, + "aux_heat": "off" as AnyObject + ]) } override var ComponentIcon: String { diff --git a/HomeAssistant/Classes/Components/DeviceTrackerComponent.swift b/HomeAssistant/Classes/Components/DeviceTrackerComponent.swift index 8d37d86bc..e35e14be4 100644 --- a/HomeAssistant/Classes/Components/DeviceTrackerComponent.swift +++ b/HomeAssistant/Classes/Components/DeviceTrackerComponent.swift @@ -39,7 +39,11 @@ class DeviceTracker: Entity { func location() -> CLLocation { if let accr = self.GPSAccuracy.value { - return CLLocation(coordinate: self.locationCoordinates(), altitude: 0, horizontalAccuracy: accr, verticalAccuracy: -1, timestamp: Date()) + return CLLocation(coordinate: self.locationCoordinates(), + altitude: 0, + horizontalAccuracy: accr, + verticalAccuracy: -1, + timestamp: Date()) } else { if self.Latitude.value != nil && self.Longitude.value != nil { return CLLocation(latitude: self.Latitude.value!, longitude: self.Longitude.value!) diff --git a/HomeAssistant/Classes/Components/InputSlider.swift b/HomeAssistant/Classes/Components/InputSlider.swift index 2bd9d3cce..06d0f4c00 100644 --- a/HomeAssistant/Classes/Components/InputSlider.swift +++ b/HomeAssistant/Classes/Components/InputSlider.swift @@ -29,6 +29,11 @@ class InputSlider: Entity { } func SelectValue(_ value: Float) { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "input_slider", service: "select_value", serviceData: ["entity_id": self.ID as AnyObject, "value": value as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "input_slider", + service: "select_value", + serviceData: ["entity_id": self.ID as AnyObject, + "value": value as AnyObject + ] + ) } } diff --git a/HomeAssistant/Classes/Components/LightComponent.swift b/HomeAssistant/Classes/Components/LightComponent.swift index 1b99f3272..62e7c4aad 100644 --- a/HomeAssistant/Classes/Components/LightComponent.swift +++ b/HomeAssistant/Classes/Components/LightComponent.swift @@ -67,7 +67,8 @@ class Light: SwitchableEntity { } override class func ignoredProperties() -> [String] { - return ["SupportedFeatures", "SupportsBrightness", "SupportsColorTemp", "SupportsEffect", "SupportsFlash", "SupportsRGBColor", "SupportsTransition", "SupportsXYColor", "RGBColor", "XYColor"] + return ["SupportedFeatures", "SupportsBrightness", "SupportsColorTemp", "SupportsEffect", "SupportsFlash", + "SupportsRGBColor", "SupportsTransition", "SupportsXYColor", "RGBColor", "XYColor"] } } diff --git a/HomeAssistant/Classes/Components/MediaPlayerComponent.swift b/HomeAssistant/Classes/Components/MediaPlayerComponent.swift index 95c4bab8e..dc5734c0f 100644 --- a/HomeAssistant/Classes/Components/MediaPlayerComponent.swift +++ b/HomeAssistant/Classes/Components/MediaPlayerComponent.swift @@ -81,7 +81,10 @@ class MediaPlayer: SwitchableEntity { } override class func ignoredProperties() -> [String] { - return ["SupportedMediaCommands", "SourceList", "SupportsPause", "SupportsSeek", "SupportsVolumeSet", "SupportsVolumeMute", "SupportsPreviousTrack", "SupportsNextTrack", "SupportsTurnOn", "SupportsTurnOff", "SupportsPlayMedia", "SupportsVolumeStep", "SupportsSelectSource", "SupportsStop", "SupportsClearPlaylist"] + return ["SupportedMediaCommands", "SourceList", "SupportsPause", "SupportsSeek", + "SupportsVolumeSet", "SupportsVolumeMute", "SupportsPreviousTrack", + "SupportsNextTrack", "SupportsTurnOn", "SupportsTurnOff", "SupportsPlayMedia", + "SupportsVolumeStep", "SupportsSelectSource", "SupportsStop", "SupportsClearPlaylist"] } func humanReadableMediaDuration() -> String { @@ -96,14 +99,29 @@ class MediaPlayer: SwitchableEntity { } func muteOn() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "media_player", service: "volume_mute", serviceData: ["entity_id": self.ID as AnyObject, "is_volume_muted": "on" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "media_player", + service: "volume_mute", + serviceData: [ + "entity_id": self.ID as AnyObject, + "is_volume_muted": "on" as AnyObject + ]) } func muteOff() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "media_player", service: "volume_mute", serviceData: ["entity_id": self.ID as AnyObject, "is_volume_muted": "off" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "media_player", + service: "volume_mute", + serviceData: [ + "entity_id": self.ID as AnyObject, + "is_volume_muted": "off" as AnyObject + ]) } func setVolume(_ newVolume: Float) { let fixedVolume = newVolume/100 - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "media_player", service: "volume_set", serviceData: ["entity_id": self.ID as AnyObject, "volume_level": fixedVolume as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "media_player", + service: "volume_set", + serviceData: [ + "entity_id": self.ID as AnyObject, + "volume_level": fixedVolume as AnyObject + ]) } override var ComponentIcon: String { diff --git a/HomeAssistant/Classes/Components/ThermostatComponent.swift b/HomeAssistant/Classes/Components/ThermostatComponent.swift index 5f47e5f24..34d6e237e 100644 --- a/HomeAssistant/Classes/Components/ThermostatComponent.swift +++ b/HomeAssistant/Classes/Components/ThermostatComponent.swift @@ -23,9 +23,11 @@ class Thermostat: Entity { override func mapping(map: Map) { super.mapping(map: map) - AwayMode <- (map["attributes.away_mode"], ComponentBoolTransform(trueValue: "on", falseValue: "off")) + AwayMode <- (map["attributes.away_mode"], + ComponentBoolTransform(trueValue: "on", falseValue: "off")) CurrentTemperature <- map["attributes.current_temperature"] - Fan <- (map["attributes.fan"], ComponentBoolTransform(trueValue: "on", falseValue: "off")) + Fan <- (map["attributes.fan"], + ComponentBoolTransform(trueValue: "on", falseValue: "off")) Temperature <- map["attributes.temperature"] MaximumTemperature <- map["attributes.max_temp"] MinimumTemperature <- map["attributes.min_temp"] @@ -33,19 +35,44 @@ class Thermostat: Entity { TargetTemperatureLow <- map["attributes.target_temp_low"] } func turnFanOn() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", service: "set_fan_mode", serviceData: ["entity_id": self.ID as AnyObject, "fan": "on" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", + service: "set_fan_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "fan": "on" as AnyObject + ]) } func turnFanOff() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", service: "set_fan_mode", serviceData: ["entity_id": self.ID as AnyObject, "fan": "off" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", + service: "set_fan_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "fan": "off" as AnyObject + ]) } func setAwayModeOn() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", service: "set_away_mode", serviceData: ["entity_id": self.ID as AnyObject, "away_mode": "on" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", + service: "set_away_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "away_mode": "on" as AnyObject + ]) } func setAwayModeOff() { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", service: "set_away_mode", serviceData: ["entity_id": self.ID as AnyObject, "away_mode": "off" as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", + service: "set_away_mode", + serviceData: [ + "entity_id": self.ID as AnyObject, + "away_mode": "off" as AnyObject + ]) } func setTemperature(_ newTemp: Float) { - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", service: "set_temperature", serviceData: ["entity_id": self.ID as AnyObject, "temperature": newTemp as AnyObject]) + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "thermostat", + service: "set_temperature", + serviceData: [ + "entity_id": self.ID as AnyObject, + "temperature": newTemp as AnyObject + ]) } override var ComponentIcon: String { diff --git a/HomeAssistant/Classes/Components/ZoneComponent.swift b/HomeAssistant/Classes/Components/ZoneComponent.swift index 6ce797fa2..bfed75717 100644 --- a/HomeAssistant/Classes/Components/ZoneComponent.swift +++ b/HomeAssistant/Classes/Components/ZoneComponent.swift @@ -28,10 +28,15 @@ class Zone: Entity { } func locationCoordinates() -> CLLocationCoordinate2D { - return CLLocationCoordinate2D(latitude: CLLocationDegrees(self.Latitude), longitude: CLLocationDegrees(self.Longitude)) + return CLLocationCoordinate2D(latitude: CLLocationDegrees(self.Latitude), + longitude: CLLocationDegrees(self.Longitude)) } func location() -> CLLocation { - return CLLocation(coordinate: self.locationCoordinates(), altitude: 0, horizontalAccuracy: self.Radius, verticalAccuracy: -1, timestamp: Date()) + return CLLocation(coordinate: self.locationCoordinates(), + altitude: 0, + horizontalAccuracy: self.Radius, + verticalAccuracy: -1, + timestamp: Date()) } } diff --git a/HomeAssistant/Classes/Entity.swift b/HomeAssistant/Classes/Entity.swift index 6f1cf6d7c..16cffd8c6 100644 --- a/HomeAssistant/Classes/Entity.swift +++ b/HomeAssistant/Classes/Entity.swift @@ -11,6 +11,7 @@ import ObjectMapper import RealmSwift import Realm +// swiftlint:disable:next type_body_length class Entity: Object, StaticMappable { let DefaultEntityUIColor = colorWithHexString("#44739E", alpha: 1) @@ -55,6 +56,7 @@ class Entity: Object, StaticMappable { dynamic var NodeID: String? = nil var BatteryLevel = RealmOptional() + // swiftlint:disable:next cyclomatic_complexity function_body_length class func objectForMapping(map: Map) -> BaseMappable? { if let entityId: String = map["entity_id"].value() { let entityType = entityId.components(separatedBy: ".")[0] @@ -108,6 +110,7 @@ class Entity: Object, StaticMappable { } return nil } + // swiftlint:enable cyclomatic_complexity function_body_length func mapping(map: Map) { ID <- map["entity_id"] @@ -157,7 +160,7 @@ class Entity: Object, StaticMappable { } var ComponentIcon: String { - switch (self.Domain) { + switch self.Domain { case "alarm_control_panel": return "mdi:bell" case "automation": @@ -232,16 +235,16 @@ class Entity: Object, StaticMappable { if self.MobileIcon != nil { return self.MobileIcon! } if self.Icon != nil { return self.Icon! } switch self { - case is BinarySensor: - return (self as! BinarySensor).StateIcon() - case is Lock: - return (self as! Lock).StateIcon() - case is MediaPlayer: - return (self as! MediaPlayer).StateIcon() + case let binarySensor as BinarySensor: + return binarySensor.StateIcon() + case let lock as Lock: + return lock.StateIcon() + case let mediaPlayer as MediaPlayer: + return mediaPlayer.StateIcon() default: - if (self.UnitOfMeasurement == "°C" || self.UnitOfMeasurement == "°F") { + if self.UnitOfMeasurement == "°C" || self.UnitOfMeasurement == "°F" { return "mdi:thermometer" - } else if (self.UnitOfMeasurement == "Mice") { + } else if self.UnitOfMeasurement == "Mice" { return "mdi:mouse-variant" } return self.ComponentIcon @@ -250,12 +253,12 @@ class Entity: Object, StaticMappable { func EntityColor() -> UIColor { switch self { - case is Light: - return (self as! Light).EntityColor() - case is Sun: - return (self as! Sun).EntityColor() - case is SwitchableEntity: - return (self as! SwitchableEntity).EntityColor() + case let light as Light: + return light.EntityColor() + case let sun as Sun: + return sun.EntityColor() + case let switchableEntity as SwitchableEntity: + return switchableEntity.EntityColor() default: let hexColor = self.State == "unavailable" ? "#bdbdbd" : "#44739E" return colorWithHexString(hexColor, alpha: 1) @@ -274,7 +277,9 @@ class Entity: Object, StaticMappable { if let friendly = self.FriendlyName { return friendly } else { - return self.ID.replacingOccurrences(of: "\(self.Domain).", with: "").replacingOccurrences(of: "_", with: " ").capitalized + return self.ID.replacingOccurrences(of: "\(self.Domain).", + with: "").replacingOccurrences(of: "_", + with: " ").capitalized } } @@ -321,7 +326,11 @@ open class ComponentBoolTransform: TransformType { } public func transformFromJSON(_ value: Any?) -> Bool? { - return ((value! as! String) == self.trueValue) + if let valueString = value as? String { + return valueString == self.trueValue + } else { + return false + } } open func transformToJSON(_ value: Bool?) -> String? { diff --git a/HomeAssistant/HAAPI.swift b/HomeAssistant/HAAPI.swift index f74bb682a..05cd53d93 100644 --- a/HomeAssistant/HAAPI.swift +++ b/HomeAssistant/HAAPI.swift @@ -26,12 +26,13 @@ let prefs = UserDefaults(suiteName: "group.io.robbie.homeassistant")! let APIClientSharedInstance = HomeAssistantAPI() +// swiftlint:disable file_length + +// swiftlint:disable:next type_body_length public class HomeAssistantAPI { class var sharedInstance: HomeAssistantAPI { - get { - return APIClientSharedInstance - } + return APIClientSharedInstance } private var manager: Alamofire.SessionManager? @@ -105,10 +106,14 @@ public class HomeAssistantAPI { prefs.setValue(config.Version, forKey: "version") Crashlytics.sharedInstance().setObjectValue(config.Version, forKey: "hass_version") - Crashlytics.sharedInstance().setObjectValue(self.loadedComponents.joined(separator: ","), forKey: "loadedComponents") - Crashlytics.sharedInstance().setObjectValue(self.enabledPermissions.joined(separator: ","), forKey: "allowedPermissions") + Crashlytics.sharedInstance().setObjectValue(self.loadedComponents.joined(separator: ","), + forKey: "loadedComponents") + Crashlytics.sharedInstance().setObjectValue(self.enabledPermissions.joined(separator: ","), + forKey: "allowedPermissions") - NotificationCenter.default.post(name: NSNotification.Name(rawValue: "connected"), object: nil, userInfo: nil) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "connected"), + object: nil, + userInfo: nil) let _ = self.GetStates() @@ -126,7 +131,7 @@ public class HomeAssistantAPI { fulfill(config) }.catch {error in print("Error at launch!", error) - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) + Crashlytics.sharedInstance().recordError(error) reject(error) } @@ -139,7 +144,8 @@ public class HomeAssistantAPI { eventSource?.onOpen { print("SSE: Connection Opened") self.showMurmur(title: "Connected to Home Assistant") - NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sse.opened"), object: nil, userInfo: nil) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sse.opened"), + object: nil, userInfo: nil) } eventSource?.onError { error in @@ -147,7 +153,11 @@ public class HomeAssistantAPI { Crashlytics.sharedInstance().recordError(err) print("SSE: ", err) self.showMurmur(title: "SSE Error! \(err.localizedDescription)") - NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sse.error"), object: nil, userInfo: ["code": err.code, "description": err.description]) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sse.error"), + object: nil, userInfo: [ + "code": err.code, + "description": err.description + ]) } } @@ -160,7 +170,8 @@ public class HomeAssistantAPI { HomeAssistantAPI.sharedInstance.storeEntities(entities: [newState]) } } - NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sse.\(event.EventType)"), object: nil, userInfo: event.toJSON()) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sse.\(event.EventType)"), + object: nil, userInfo: event.toJSON()) } else { print("Unable to ObjectMap this SSE message", eventName!, eventData) } @@ -170,7 +181,11 @@ public class HomeAssistantAPI { } } - func submitLocation(updateType: LocationUpdateTypes, coordinates: CLLocationCoordinate2D, accuracy: CLLocationAccuracy, zone: Zone? = nil) { + // swiftlint:disable:next function_body_length + func submitLocation(updateType: LocationUpdateTypes, + coordinates: CLLocationCoordinate2D, + accuracy: CLLocationAccuracy, + zone: Zone? = nil) { UIDevice.current.isBatteryMonitoringEnabled = true var batteryState = "Unplugged" @@ -194,8 +209,10 @@ public class HomeAssistantAPI { "dev_id": deviceID ] - self.CallService(domain: "device_tracker", service: "see", serviceData: locationUpdate as [String : Any]).then {_ in - print("Device seen!") + self.CallService(domain: "device_tracker", + service: "see", + serviceData: locationUpdate as [String : Any]).then {_ in + print("Device seen!") }.catch {err in Crashlytics.sharedInstance().recordError(err as NSError) } @@ -231,7 +248,8 @@ public class HomeAssistantAPI { content.body = notificationBody content.sound = UNNotificationSound.default() - UNUserNotificationCenter.current().add(UNNotificationRequest.init(identifier: notificationIdentifer, content: content, trigger: nil)) + UNUserNotificationCenter.current().add(UNNotificationRequest.init(identifier: notificationIdentifer, + content: content, trigger: nil)) } else { let notification = UILocalNotification() notification.alertTitle = notificationTitle @@ -246,13 +264,20 @@ public class HomeAssistantAPI { } func trackLocation() { - let _ = Location.getLocation(withAccuracy: .neighborhood, frequency: .significant, timeout: 50, onSuccess: { (location) in - // You will receive at max one event if desidered accuracy can be achieved; this because you have set .OneShot as frequency. - self.submitLocation(updateType: .Manual, coordinates: location.coordinate, accuracy: location.horizontalAccuracy, zone: nil) + let _ = Location.getLocation(withAccuracy: .neighborhood, + frequency: .significant, + timeout: 50, + onSuccess: { (location) in + // You will receive at max one event if desidered accuracy can be achieved + // because you have set .OneShot as frequency. + self.submitLocation(updateType: .Manual, + coordinates: location.coordinate, + accuracy: location.horizontalAccuracy, + zone: nil) }) { (_, error) in // something went wrong. request will be cancelled automatically print("Something went wrong when trying to get significant location updates! Error was:", error) - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) + Crashlytics.sharedInstance().recordError(error) } for zone in realm.objects(Zone.self) { @@ -261,12 +286,17 @@ public class HomeAssistantAPI { continue } do { - let _ = try Beacons.monitor(geographicRegion: zone.locationCoordinates(), radius: CLLocationDistance(zone.Radius), onStateDidChange: { newState in - var updateType = LocationUpdateTypes.RegionEnter - if newState == RegionState.exited { - updateType = LocationUpdateTypes.RegionExit - } - self.submitLocation(updateType: updateType, coordinates: zone.locationCoordinates(), accuracy: 1, zone: zone) + let _ = try Beacons.monitor(geographicRegion: zone.locationCoordinates(), + radius: CLLocationDistance(zone.Radius), + onStateDidChange: { newState in + var updateType = LocationUpdateTypes.RegionEnter + if newState == RegionState.exited { + updateType = LocationUpdateTypes.RegionExit + } + self.submitLocation(updateType: updateType, + coordinates: zone.locationCoordinates(), + accuracy: 1, + zone: zone) }) { error in CLSLogv("Error in region monitoring: %@", getVaList([error.localizedDescription])) Crashlytics.sharedInstance().recordError(error) @@ -278,12 +308,16 @@ public class HomeAssistantAPI { } // let location = Location() - + // // self.getBeacons().then { beacons -> Void in // for beacon in beacons { // print("Got beacon from HA", beacon.UUID, beacon.Major, beacon.Minor) - // try Beacons.monitorForBeacon(proximityUUID: beacon.UUID!, major: UInt16(beacon.Major!), minor: UInt16(beacon.Minor!), onFound: { beaconsFound in - // // beaconsFound is an array of found beacons ([CLBeacon]) but in this case it contains only one beacon + // try Beacons.monitorForBeacon(proximityUUID: beacon.UUID!, + // major: UInt16(beacon.Major!), + // minor: UInt16(beacon.Minor!), + // onFound: { beaconsFound in + // // beaconsFound is an array of found beacons ([CLBeacon]) but + // // in this case it contains only one beacon // print("beaconsFound", beaconsFound) // }) { error in // // something bad happened @@ -299,12 +333,18 @@ public class HomeAssistantAPI { func sendOneshotLocation() -> Promise { return Promise { fulfill, reject in - let _ = Location.getLocation(withAccuracy: .neighborhood, frequency: .oneShot, timeout: 50, onSuccess: { (location) in - self.submitLocation(updateType: .Manual, coordinates: location.coordinate, accuracy: location.horizontalAccuracy, zone: nil) - fulfill(true) + let _ = Location.getLocation(withAccuracy: .neighborhood, + frequency: .oneShot, + timeout: 50, + onSuccess: { (location) in + self.submitLocation(updateType: .Manual, + coordinates: location.coordinate, + accuracy: location.horizontalAccuracy, + zone: nil) + fulfill(true) }) { (_, error) in print("Error when trying to get a oneshot location!", error) - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) + Crashlytics.sharedInstance().recordError(error) reject(error) } } @@ -313,15 +353,17 @@ public class HomeAssistantAPI { func GetStatus() -> Promise { let queryUrl = baseAPIURL return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseObject { (response: DataResponse) in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error on GetStatus() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + // swiftlint:disable:next line_length + method: .get).validate().responseObject { (response: DataResponse) in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error on GetStatus() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -329,15 +371,17 @@ public class HomeAssistantAPI { func GetConfig() -> Promise { let queryUrl = baseAPIURL+"config" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseObject { (response: DataResponse) in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error on GetConfig() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + // swiftlint:disable:next line_length + method: .get).validate().responseObject { (response: DataResponse) in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error on GetConfig() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -345,15 +389,17 @@ public class HomeAssistantAPI { func GetServices() -> Promise<[ServicesResponse]> { let queryUrl = baseAPIURL+"services" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseArray { (response: DataResponse<[ServicesResponse]>) in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error on GetServices() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + // swiftlint:disable:next line_length + method: .get).validate().responseArray { (response: DataResponse<[ServicesResponse]>) in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error on GetServices() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -377,8 +423,10 @@ public class HomeAssistantAPI { // } // } + // swiftlint:disable:next cyclomatic_complexity function_body_length func storeEntities(entities: [Entity]) { for entity in entities { + // swiftlint:disable:next force_try try! realm.write { switch entity { case is Automation: @@ -393,9 +441,10 @@ public class HomeAssistantAPI { realm.create(GarageDoor.self, value: entity, update: true) case is Group: if let currentObject = realm.object(ofType: Group.self, forPrimaryKey: entity.ID as AnyObject) { - let group = entity as! Group - group.Order = currentObject.Order - realm.create(Group.self, value: entity, update: true) + if let group = entity as? Group { + group.Order = currentObject.Order + realm.create(Group.self, value: entity, update: true) + } } else { realm.create(Group.self, value: entity, update: true) } @@ -438,16 +487,18 @@ public class HomeAssistantAPI { func GetStates() -> Promise<[Entity]> { let queryUrl = baseAPIURL+"states" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseArray { (response: DataResponse<[Entity]>) -> Void in - switch response.result { - case .success: - self.storeEntities(entities: response.result.value!) - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error on GetStates() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + // swiftlint:disable:next line_length + method: .get).validate().responseArray { (response: DataResponse<[Entity]>) -> Void in + switch response.result { + case .success: + self.storeEntities(entities: response.result.value!) + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error on GetStates() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -455,16 +506,18 @@ public class HomeAssistantAPI { func GetStateForEntityIdMapped(entityId: String) -> Promise { let queryUrl = baseAPIURL+"states/"+entityId return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseObject { (response: DataResponse) -> Void in - switch response.result { - case .success: - self.storeEntities(entities: [response.result.value!]) - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error on GetStateForEntityIdMapped() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + // swiftlint:disable:next line_length + method: .get).validate().responseObject { (response: DataResponse) -> Void in + switch response.result { + case .success: + self.storeEntities(entities: [response.result.value!]) + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error on GetStateForEntityIdMapped() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -488,17 +541,21 @@ public class HomeAssistantAPI { func SetState(entityId: String, state: String) -> Promise { let queryUrl = baseAPIURL+"states/"+entityId return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .post, parameters: ["state": state], encoding: JSONEncoding.default).validate().responseObject { (response: DataResponse) in - switch response.result { - case .success: - self.showMurmur(title: response.result.value!.Domain+" state set to "+response.result.value!.State) - self.storeEntities(entities: [response.result.value!]) - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error when attemping to SetState(): %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + method: .post, + parameters: ["state": state], + encoding: JSONEncoding.default + ).validate().responseObject { (response: DataResponse) in + switch response.result { + case .success: + self.showMurmur(title: response.result.value!.Domain+" state set to "+response.result.value!.State) + self.storeEntities(entities: [response.result.value!]) + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error when attemping to SetState(): %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -506,18 +563,21 @@ public class HomeAssistantAPI { func CreateEvent(eventType: String, eventData: [String:Any]) -> Promise { let queryUrl = baseAPIURL+"events/"+eventType return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .post, parameters: eventData, encoding: JSONEncoding.default).validate().responseJSON { response in - switch response.result { - case .success: - if let jsonDict = response.result.value as? [String : String] { - self.showMurmur(title: eventType+" created") - fulfill(jsonDict["message"]!) - } - case .failure(let error): - CLSLogv("Error when attemping to CreateEvent(): %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + method: .post, + parameters: eventData, + encoding: JSONEncoding.default).validate().responseJSON { response in + switch response.result { + case .success: + if let jsonDict = response.result.value as? [String : String] { + self.showMurmur(title: eventType+" created") + fulfill(jsonDict["message"]!) + } + case .failure(let error): + CLSLogv("Error when attemping to CreateEvent(): %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -526,15 +586,19 @@ public class HomeAssistantAPI { // self.showMurmur(title: domain+"/"+service+" called") let queryUrl = baseAPIURL+"services/"+domain+"/"+service return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .post, parameters: serviceData, encoding: JSONEncoding.default).validate().responseArray { (response: DataResponse<[ServicesResponse]>) in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error on CallService() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + method: .post, + parameters: serviceData, + encoding: JSONEncoding.default + ).validate().responseArray { (response: DataResponse<[ServicesResponse]>) in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error on CallService() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -574,9 +638,17 @@ public class HomeAssistantAPI { let deviceKitDevice = Device() let ident = IdentifyRequest() - ident.AppBuildNumber = Int(string: Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion")! as! String) + if let bundleVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") { + if let stringedBundleVersion = bundleVersion as? String { + ident.AppBuildNumber = Int(stringedBundleVersion) + } + } + if let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") { + if let stringedVersionNumber = versionNumber as? String { + ident.AppVersionNumber = Double(stringedVersionNumber) + } + } ident.AppBundleIdentifer = Bundle.main.bundleIdentifier - ident.AppVersionNumber = Double(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String) ident.DeviceID = deviceID ident.DeviceLocalizedModel = deviceKitDevice.localizedModel ident.DeviceModel = deviceKitDevice.model @@ -614,9 +686,22 @@ public class HomeAssistantAPI { let deviceKitDevice = Device() let ident = PushRegistrationRequest() - ident.AppBuildNumber = Int(string: Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion")! as! String) + if let bundleVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") { + if let stringedBundleVersion = bundleVersion as? String { + ident.AppBuildNumber = Int(stringedBundleVersion) + } + } + if let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") { + if let stringedVersionNumber = versionNumber as? String { + ident.AppVersionNumber = Double(stringedVersionNumber) + } + } + if let isSandboxed = Bundle.main.object(forInfoDictionaryKey: "IS_SANDBOXED") { + if let stringedisSandboxed = isSandboxed as? String { + ident.APNSSandbox = (stringedisSandboxed == "true") + } + } ident.AppBundleIdentifer = Bundle.main.bundleIdentifier - ident.AppVersionNumber = Double(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String) ident.DeviceID = deviceID ident.DeviceName = deviceKitDevice.name ident.DevicePermanentID = DeviceUID.uid() @@ -627,7 +712,6 @@ public class HomeAssistantAPI { ident.PushSounds = listAllInstalledPushNotificationSounds() ident.PushToken = deviceToken ident.UserEmail = prefs.string(forKey: "userEmail")! - ident.APNSSandbox = ((Bundle.main.object(forInfoDictionaryKey: "IS_SANDBOXED") as! String) == "true") ident.HomeAssistantVersion = prefs.string(forKey: "version")! ident.HomeAssistantTimezone = prefs.string(forKey: "time_zone")! @@ -638,9 +722,17 @@ public class HomeAssistantAPI { let deviceKitDevice = Device() let ident = IdentifyRequest() - ident.AppBuildNumber = Int(string: Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion")! as! String) + if let bundleVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") { + if let stringedBundleVersion = bundleVersion as? String { + ident.AppBuildNumber = Int(stringedBundleVersion) + } + } + if let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") { + if let stringedVersionNumber = versionNumber as? String { + ident.AppVersionNumber = Double(stringedVersionNumber) + } + } ident.AppBundleIdentifer = Bundle.main.bundleIdentifier - ident.AppVersionNumber = Double(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String) ident.DeviceID = deviceID ident.DeviceLocalizedModel = deviceKitDevice.localizedModel ident.DeviceModel = deviceKitDevice.model @@ -660,15 +752,18 @@ public class HomeAssistantAPI { func identifyDevice() -> Promise { let queryUrl = baseAPIURL+"ios/identify" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .post, parameters: buildIdentifyDict(), encoding: JSONEncoding.default).validate().responseString { response in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error when attemping to identifyDevice(): %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + method: .post, + parameters: buildIdentifyDict(), + encoding: JSONEncoding.default).validate().responseString { response in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error when attemping to identifyDevice(): %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -676,15 +771,18 @@ public class HomeAssistantAPI { func removeDevice() -> Promise { let queryUrl = baseAPIURL+"ios/identify" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .delete, parameters: buildRemovalDict(), encoding: JSONEncoding.default).validate().responseString { response in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error when attemping to identifyDevice(): %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + method: .delete, + parameters: buildRemovalDict(), + encoding: JSONEncoding.default).validate().responseString { response in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error when attemping to identifyDevice(): %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -692,111 +790,143 @@ public class HomeAssistantAPI { func registerDeviceForPush(deviceToken: String) -> Promise { let queryUrl = "https://ios-push.home-assistant.io/registrations" return Promise { fulfill, reject in - Alamofire.request(queryUrl, method: .post, parameters: buildPushRegistrationDict(deviceToken: deviceToken), encoding: JSONEncoding.default).validate().responseObject { (response: DataResponse) in - switch response.result { - case .success: - let json = response.result.value! - fulfill(json.PushId!) - case .failure(let error): - CLSLogv("Error when attemping to registerDeviceForPush(): %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + Alamofire.request(queryUrl, + method: .post, + parameters: buildPushRegistrationDict(deviceToken: deviceToken), + encoding: JSONEncoding.default + ).validate().responseObject {(response: DataResponse) in + switch response.result { + case .success: + let json = response.result.value! + fulfill(json.PushId!) + case .failure(let error): + CLSLogv("Error when attemping to registerDeviceForPush(): %@", + getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } + // swiftlint:disable:next function_body_length func setupPushActions() -> Promise> { let queryUrl = baseAPIURL+"ios/push" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseObject { (response: DataResponse) in - switch response.result { - case .success: - let config = response.result.value! - var allCategories = Set() - if let categories = config.Categories { - for category in categories { - let finalCategory = UIMutableUserNotificationCategory() - finalCategory.identifier = category.Identifier - var categoryActions = [UIMutableUserNotificationAction]() - if let actions = category.Actions { - for action in actions { - let newAction = UIMutableUserNotificationAction() - newAction.title = action.Title - newAction.identifier = action.Identifier - newAction.isAuthenticationRequired = action.AuthenticationRequired - newAction.isDestructive = action.Destructive - newAction.behavior = (action.Behavior == "default") ? UIUserNotificationActionBehavior.default : UIUserNotificationActionBehavior.textInput - newAction.activationMode = (action.ActivationMode == "foreground") ? UIUserNotificationActivationMode.foreground : UIUserNotificationActivationMode.background - if let textInputButtonTitle = action.TextInputButtonTitle { - newAction.parameters[UIUserNotificationTextInputActionButtonTitleKey] = textInputButtonTitle + let _ = self.manager!.request(queryUrl, + // swiftlint:disable:next line_length + method: .get).validate().responseObject { (response: DataResponse) in + switch response.result { + case .success: + let config = response.result.value! + var allCategories = Set() + if let categories = config.Categories { + for category in categories { + let finalCategory = UIMutableUserNotificationCategory() + finalCategory.identifier = category.Identifier + var categoryActions = [UIMutableUserNotificationAction]() + if let actions = category.Actions { + for action in actions { + let newAction = UIMutableUserNotificationAction() + newAction.title = action.Title + newAction.identifier = action.Identifier + newAction.isAuthenticationRequired = action.AuthenticationRequired + newAction.isDestructive = action.Destructive + let defaultBehavior = UIUserNotificationActionBehavior.default + let textInputBehavior = UIUserNotificationActionBehavior.textInput + let behavior = (action.Behavior == "default") ? defaultBehavior : textInputBehavior + newAction.behavior = behavior + let foreground = UIUserNotificationActivationMode.foreground + let background = UIUserNotificationActivationMode.background + let mode = (action.ActivationMode == "foreground") ? foreground : background + newAction.activationMode = mode + if let textInputButtonTitle = action.TextInputButtonTitle { + let titleKey = UIUserNotificationTextInputActionButtonTitleKey + newAction.parameters[titleKey] = textInputButtonTitle + } + categoryActions.append(newAction) } - categoryActions.append(newAction) + finalCategory.setActions(categoryActions, for: UIUserNotificationActionContext.default) + allCategories.insert(finalCategory) + } else { + print("Category has no actions defined, continuing loop") + continue } - finalCategory.setActions(categoryActions, for: UIUserNotificationActionContext.default) - allCategories.insert(finalCategory) - } else { - print("Category has no actions defined, continuing loop") - continue } } + fulfill(allCategories) + case .failure(let error): + CLSLogv("Error on setupPushActions() request: %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) } - fulfill(allCategories) - case .failure(let error): - CLSLogv("Error on setupPushActions() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } } } } @available(iOS 10, *) + // swiftlint:disable:next cyclomatic_complexity function_body_length func setupUserNotificationPushActions() -> Promise> { let queryUrl = baseAPIURL+"ios/push" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseObject { (response: DataResponse) in - switch response.result { - case .success: - let config = response.result.value! - var allCategories = Set() - if let categories = config.Categories { - for category in categories { - var categoryActions = [UNNotificationAction]() - if let actions = category.Actions { - for action in actions { - var actionOptions = UNNotificationActionOptions([]) - if action.AuthenticationRequired { - actionOptions.insert(.authenticationRequired) - } - if action.Destructive { - actionOptions.insert(.destructive) - } - if (action.ActivationMode == "foreground") { - actionOptions.insert(.foreground) - } - if (action.Behavior == "default") { - let newAction = UNNotificationAction(identifier: action.Identifier!, title: action.Title!, options: actionOptions) - categoryActions.append(newAction) - } else if (action.Behavior == "TextInput") { - let newAction = UNTextInputNotificationAction(identifier: action.Identifier!, title: action.Title!, options: actionOptions, textInputButtonTitle: action.TextInputButtonTitle!, textInputPlaceholder: action.TextInputPlaceholder!) - categoryActions.append(newAction) + let _ = self.manager!.request(queryUrl, + method: .get + ).validate().responseObject { (response: DataResponse) in + switch response.result { + case .success: + let config = response.result.value! + var allCategories = Set() + if let categories = config.Categories { + for category in categories { + var categoryActions = [UNNotificationAction]() + if let actions = category.Actions { + for action in actions { + var actionOptions = UNNotificationActionOptions([]) + if action.AuthenticationRequired { + actionOptions.insert(.authenticationRequired) + } + if action.Destructive { + actionOptions.insert(.destructive) + } + if action.ActivationMode == "foreground" { + actionOptions.insert(.foreground) + } + if action.Behavior == "default" { + let newAction = UNNotificationAction(identifier: action.Identifier!, + title: action.Title!, + options: actionOptions) + categoryActions.append(newAction) + } else if action.Behavior == "TextInput" { + if let identifier = action.Identifier, + let btnTitle = action.TextInputButtonTitle, + let plcehold = action.TextInputPlaceholder, let title = action.Title { + let newAction = UNTextInputNotificationAction(identifier: identifier, + title: title, + options: actionOptions, + textInputButtonTitle:btnTitle, + textInputPlaceholder:plcehold) + categoryActions.append(newAction) + } + } } + } else { + print("Category has no actions defined, continuing loop") + continue } - } else { - print("Category has no actions defined, continuing loop") - continue + let finalCategory = UNNotificationCategory.init(identifier: category.Identifier!, + actions: categoryActions, + intentIdentifiers: [], + options: [.customDismissAction]) + allCategories.insert(finalCategory) } - let finalCategory = UNNotificationCategory.init(identifier: category.Identifier!, actions: categoryActions, intentIdentifiers: [], options: [.customDismissAction]) - allCategories.insert(finalCategory) } + fulfill(allCategories) + case .failure(let error): + CLSLogv("Error on setupUserNotificationPushActions() request: %@", + getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) } - fulfill(allCategories) - case .failure(let error): - CLSLogv("Error on setupUserNotificationPushActions() request: %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } } } } @@ -808,7 +938,7 @@ public class HomeAssistantAPI { UNUserNotificationCenter.current().setNotificationCategories(categories) }.catch {error -> Void in print("Error when attempting to setup push actions", error) - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) + Crashlytics.sharedInstance().recordError(error) } } else { self.setupPushActions().then { categories -> Void in @@ -817,7 +947,7 @@ public class HomeAssistantAPI { UIApplication.shared.registerUserNotificationSettings(settings) }.catch {error -> Void in print("Error when attempting to setup push actions", error) - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) + Crashlytics.sharedInstance().recordError(error) } } } @@ -825,17 +955,20 @@ public class HomeAssistantAPI { func handlePushAction(identifier: String, userInfo: [AnyHashable : Any], userInput: String?) -> Promise { return Promise { fulfill, reject in let device = Device() - var eventData: [String:Any] = ["actionName": identifier, "sourceDevicePermanentID": DeviceUID.uid(), "sourceDeviceName": device.name] + var eventData: [String:Any] = ["actionName": identifier, + "sourceDevicePermanentID": DeviceUID.uid(), + "sourceDeviceName": device.name] if let dataDict = userInfo["homeassistant"] { eventData["action_data"] = dataDict } if let textInput = userInput { eventData["response_info"] = textInput } - HomeAssistantAPI.sharedInstance.CreateEvent(eventType: "ios.notification_action_fired", eventData: eventData).then { _ in - fulfill(true) + HomeAssistantAPI.sharedInstance.CreateEvent(eventType: "ios.notification_action_fired", + eventData: eventData).then { _ in + fulfill(true) }.catch {error in - Crashlytics.sharedInstance().recordError((error as Any) as! NSError) + Crashlytics.sharedInstance().recordError(error) reject(error) } } @@ -844,15 +977,16 @@ public class HomeAssistantAPI { func getBeacons() -> Promise<[Beacon]> { let queryUrl = baseAPIURL+"ios/beacons" return Promise { fulfill, reject in - let _ = self.manager!.request(queryUrl, method: .get).validate().responseArray { (response: DataResponse<[Beacon]>) in - switch response.result { - case .success: - fulfill(response.result.value!) - case .failure(let error): - CLSLogv("Error when attemping to getBeacons(): %@", getVaList([error.localizedDescription])) - Crashlytics.sharedInstance().recordError(error) - reject(error) - } + let _ = self.manager!.request(queryUrl, + method: .get).validate().responseArray { (response: DataResponse<[Beacon]>) in + switch response.result { + case .success: + fulfill(response.result.value!) + case .failure(let error): + CLSLogv("Error when attemping to getBeacons(): %@", getVaList([error.localizedDescription])) + Crashlytics.sharedInstance().recordError(error) + reject(error) + } } } } @@ -860,7 +994,9 @@ public class HomeAssistantAPI { func getImage(imageUrl: String) -> Promise { var url = imageUrl if url.hasPrefix("/local/") || url.hasPrefix("/api/") { - url = baseAPIURL+url.replacingOccurrences(of: "/api/", with: "").replacingOccurrences(of: "/local/", with: "") + url = baseAPIURL+url.replacingOccurrences(of: "/api/", + with: "").replacingOccurrences(of: "/local/", + with: "") } return Promise { fulfill, reject in let _ = self.manager!.request(url, method: .get).validate().responseImage { response in @@ -881,7 +1017,6 @@ public class HomeAssistantAPI { } var locationEnabled: Bool { - // return PermissionScope().statusLocationAlways() == .authorized && self.loadedComponents.contains("device_tracker") return PermissionScope().statusLocationAlways() == .authorized } @@ -914,10 +1049,11 @@ public class HomeAssistantAPI { var enabledPermissions: [String] { var permissionsContainer: [String] = [] - for status in PermissionScope().permissionStatuses([NotificationsPermission().type, LocationAlwaysPermission().type]) { - if status.1 == .authorized { - permissionsContainer.append(status.0.prettyDescription.lowercased()) - } + for status in PermissionScope().permissionStatuses([NotificationsPermission().type, + LocationAlwaysPermission().type]) { + if status.1 == .authorized { + permissionsContainer.append(status.0.prettyDescription.lowercased()) + } } return permissionsContainer } @@ -943,7 +1079,8 @@ public class HomeAssistantAPI { func GetDiscoveryInfo(baseUrl: URL) -> Promise { let queryUrl = baseUrl.appendingPathComponent("/api/discovery_info") return Promise { fulfill, reject in - let _ = Alamofire.request(queryUrl).validate().responseObject { (response: DataResponse) -> Void in + // swiftlint:disable:next line_length + _ = Alamofire.request(queryUrl).validate().responseObject { (response: DataResponse) -> Void in switch response.result { case .success: fulfill(response.result.value!) @@ -965,7 +1102,9 @@ class BonjourDelegate: NSObject, NetServiceBrowserDelegate, NetServiceDelegate { // Browser methods - func netServiceBrowser(_ netServiceBrowser: NetServiceBrowser, didFind netService: NetService, moreComing moreServicesComing: Bool) { + func netServiceBrowser(_ netServiceBrowser: NetServiceBrowser, + didFind netService: NetService, + moreComing moreServicesComing: Bool) { NSLog("BonjourDelegate.Browser.didFindService") netService.delegate = self resolvingDict[netService.name] = netService @@ -974,72 +1113,41 @@ class BonjourDelegate: NSObject, NetServiceBrowserDelegate, NetServiceDelegate { func netServiceDidResolveAddress(_ sender: NetService) { NSLog("BonjourDelegate.Browser.netServiceDidResolveAddress") - let discoveryInfo = DiscoveryInfoFromDict(locationName: sender.name, netServiceDictionary: NetService.dictionary(fromTXTRecord: sender.txtRecordData()!)) - NotificationCenter.default.post(name: NSNotification.Name(rawValue: "homeassistant.discovered"), object: nil, userInfo: discoveryInfo.toJSON()) + if let txtRecord = sender.txtRecordData() { + let serviceDict = NetService.dictionary(fromTXTRecord: txtRecord) + let discoveryInfo = DiscoveryInfoFromDict(locationName: sender.name, netServiceDictionary: serviceDict) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "homeassistant.discovered"), + object: nil, + userInfo: discoveryInfo.toJSON()) + } } - func netServiceBrowser(_ netServiceBrowser: NetServiceBrowser, didRemove netService: NetService, moreComing moreServicesComing: Bool) { + func netServiceBrowser(_ netServiceBrowser: NetServiceBrowser, + didRemove netService: NetService, + moreComing moreServicesComing: Bool) { NSLog("BonjourDelegate.Browser.didRemoveService") let discoveryInfo: [NSObject:Any] = ["name" as NSObject: netService.name] - NotificationCenter.default.post(name: NSNotification.Name(rawValue: "homeassistant.undiscovered"), object: nil, userInfo: discoveryInfo) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "homeassistant.undiscovered"), + object: nil, + userInfo: discoveryInfo) resolvingDict.removeValue(forKey: netService.name) } - // func netServiceBrowser(netServiceBrowser: NetServiceBrowser, didFindDomain domainName: String, moreComing moreDomainsComing: Bool) { - // NSLog("BonjourDelegate.Browser.netServiceBrowser.didFindDomain") - // } - // func netServiceBrowser(netServiceBrowser: NetServiceBrowser, didRemoveDomain domainName: String, moreComing moreDomainsComing: Bool) { - // NSLog("BonjourDelegate.Browser.netServiceBrowser.didRemoveDomain") - // } - // func netServiceBrowserWillSearch(netServiceBrowser: NetServiceBrowser){ - // NSLog("BonjourDelegate.Browser.netServiceBrowserWillSearch") - // } - // func netServiceBrowser(netServiceBrowser: NetServiceBrowser, didNotSearch errorInfo: [String : NSNumber]) { - // NSLog("BonjourDelegate.Browser.netServiceBrowser.didNotSearch") - // } - // func netServiceBrowserDidStopSearch(netServiceBrowser: NetServiceBrowser) { - // NSLog("BonjourDelegate.Browser.netServiceBrowserDidStopSearch") - // } - // func netServiceWillPublish(sender: NetService) { - // NSLog("BonjourDelegate.Browser.netServiceWillPublish:\(sender)"); - // } - - private func DiscoveryInfoFromDict(locationName: String, netServiceDictionary: [String : Data]) -> DiscoveryInfoResponse { + private func DiscoveryInfoFromDict(locationName: String, + netServiceDictionary: [String : Data]) -> DiscoveryInfoResponse { var outputDict: [String:Any] = [:] for (key, value) in netServiceDictionary { outputDict[key] = String(data: value, encoding: .utf8) if outputDict[key] as? String == "true" || outputDict[key] as? String == "false" { - outputDict[key] = Bool(outputDict[key] as! String) + if let stringedKey = outputDict[key] as? String { + outputDict[key] = Bool(stringedKey) + } } } outputDict["location_name"] = locationName return DiscoveryInfoResponse(JSON: outputDict)! } - // Publisher methods - - // func netService(sender: NetService, didNotPublish errorDict: [String : NSNumber]) { - // NSLog("BonjourDelegate.Publisher.didNotPublish:\(sender)"); - // } - // func netServiceDidPublish(sender: NetService) { - // NSLog("BonjourDelegate.Publisher.netServiceDidPublish:\(sender)"); - // } - // func netServiceWillResolve(sender: NetService) { - // NSLog("BonjourDelegate.Publisher.netServiceWillResolve:\(sender)"); - // } - // func netService(sender: NetService, didNotResolve errorDict: [String : NSNumber]) { - // NSLog("BonjourDelegate.Publisher.netServiceDidNotResolve:\(sender)"); - // } - // func netService(sender: NetService, didUpdateTXTRecordData data: NSData) { - // NSLog("BonjourDelegate.Publisher.netServiceDidUpdateTXTRecordData:\(sender)"); - // } - // func netServiceDidStop(sender: NetService) { - // NSLog("BonjourDelegate.Publisher.netServiceDidStopService:\(sender)"); - // } - // func netService(sender: NetService, didAcceptConnectionWithInputStream inputStream: NSInputStream, outputStream stream: NSOutputStream) { - // NSLog("BonjourDelegate.Publisher.netServiceDidAcceptConnection:\(sender)"); - // } - } class Bonjour { @@ -1054,12 +1162,30 @@ class Bonjour { } func buildPublishDict() -> [String: Data] { - return [ - "permanentID": DeviceUID.uid().data(using: .utf8)!, - "bundleIdentifer": Bundle.main.bundleIdentifier!.data(using: .utf8)!, - "versionNumber": (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String).data(using: .utf8)!, - "buildNumber": (Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String).data(using: .utf8)! - ] + var publishDict: [String:Data] = [:] + if let bundleVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") { + if let stringedBundleVersion = bundleVersion as? String { + if let data = stringedBundleVersion.data(using: .utf8) { + publishDict["buildNumber"] = data + } + } + } + if let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") { + if let stringedVersionNumber = versionNumber as? String { + if let data = stringedVersionNumber.data(using: .utf8) { + publishDict["versionNumber"] = data + } + } + } + if let permanentID = DeviceUID.uid().data(using: .utf8) { + publishDict["permanentID"] = permanentID + } + if let bundleIdentifier = Bundle.main.bundleIdentifier { + if let data = bundleIdentifier.data(using: .utf8) { + publishDict["bundleIdentifier"] = data + } + } + return publishDict } func startDiscovery() { diff --git a/HomeAssistant/Utilities/NSURL+QueryDictionary.swift b/HomeAssistant/Utilities/NSURL+QueryDictionary.swift index 3b8e449e0..311af35ab 100644 --- a/HomeAssistant/Utilities/NSURL+QueryDictionary.swift +++ b/HomeAssistant/Utilities/NSURL+QueryDictionary.swift @@ -9,27 +9,25 @@ import Foundation extension URL { var queryDictionary: [String: [String]]? { - get { - if let query = self.query { - var dictionary = [String: [String]]() + if let query = self.query { + var dictionary = [String: [String]]() - for keyValueString in query.components(separatedBy: "&") { - var parts = keyValueString.components(separatedBy: "=") - if parts.count < 2 { continue; } + for keyValueString in query.components(separatedBy: "&") { + var parts = keyValueString.components(separatedBy: "=") + if parts.count < 2 { continue; } - let key = parts[0].removingPercentEncoding! - let value = parts[1].removingPercentEncoding! + let key = parts[0].removingPercentEncoding! + let value = parts[1].removingPercentEncoding! - var values = dictionary[key] ?? [String]() - values.append(value) - dictionary[key] = values - } - - return dictionary + var values = dictionary[key] ?? [String]() + values.append(value) + dictionary[key] = values } - return nil + return dictionary } + + return nil } var queryItems: [String: String]? { diff --git a/HomeAssistant/Utilities/OpenInChromeController.swift b/HomeAssistant/Utilities/OpenInChromeController.swift index 6bc385591..acfa3f979 100644 --- a/HomeAssistant/Utilities/OpenInChromeController.swift +++ b/HomeAssistant/Utilities/OpenInChromeController.swift @@ -59,9 +59,12 @@ open class OpenInChromeController { } let scheme = url.scheme?.lowercased() if scheme == "http" || scheme == "https" { - var chromeURLString = String(format: "%@//x-callback-url/open/?x-source=%@&url=%@", googleChromeCallbackScheme, encodeByAddingPercentEscapes(appName), encodeByAddingPercentEscapes(url.absoluteString)) + var chromeURLString = String(format: "%@//x-callback-url/open/?x-source=%@&url=%@", + googleChromeCallbackScheme, encodeByAddingPercentEscapes(appName), + encodeByAddingPercentEscapes(url.absoluteString)) if callbackURL != nil { - chromeURLString += String(format: "&x-success=%@", encodeByAddingPercentEscapes(callbackURL!.absoluteString)) + chromeURLString += String(format: "&x-success=%@", + encodeByAddingPercentEscapes(callbackURL!.absoluteString)) } if createNewTab { chromeURLString += "&create-new-tab" @@ -78,7 +81,8 @@ open class OpenInChromeController { } if let chromeScheme = chromeScheme { let absoluteURLString = url.absoluteString - let chromeURLString = chromeScheme + absoluteURLString.substring(from: absoluteURLString.range(of: ":")!.lowerBound) + let lowerBound = absoluteURLString.range(of: ":")!.lowerBound + let chromeURLString = chromeScheme + absoluteURLString.substring(from: lowerBound) return UIApplication.shared.openURL(URL(string: chromeURLString)!) } } diff --git a/HomeAssistant/Utilities/Utils.swift b/HomeAssistant/Utilities/Utils.swift index 42065b4e6..277c39fe1 100644 --- a/HomeAssistant/Utilities/Utils.swift +++ b/HomeAssistant/Utilities/Utils.swift @@ -11,28 +11,33 @@ import FontAwesomeKit import Crashlytics func getIconForIdentifier(_ iconIdentifier: String, iconWidth: Double, iconHeight: Double, color: UIColor) -> UIImage { - let iconCodes = FontAwesomeKit.FAKMaterialDesignIcons.allIcons() as! [String:String] - Crashlytics.sharedInstance().setFloatValue(Float(iconWidth), forKey: "iconWidth") - Crashlytics.sharedInstance().setFloatValue(Float(iconHeight), forKey: "iconHeight") - Crashlytics.sharedInstance().setObjectValue(iconIdentifier, forKey: "iconIdentifier") - let fixedIconIdentifier = iconIdentifier.replacingOccurrences(of: ":", with: "-") - let iconCode = iconCodes[fixedIconIdentifier] - if iconIdentifier.contains("mdi") == false || iconCode == nil { - print("Invalid icon requested", iconIdentifier) - CLSLogv("Invalid icon requested %@", getVaList([iconIdentifier])) - let alert = UIAlertController(title: "Invalid icon", message: "There is an invalid icon in your configuration. Please search your configuration files for: \(iconIdentifier) and set it to a valid Material Design Icon. Until then, this icon will be a red exclamation point and this alert will continue to display.", preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) - UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil) - let iconCode = iconCodes["mdi-exclamation"] + if let iconCodes = FontAwesomeKit.FAKMaterialDesignIcons.allIcons() as? [String:String] { + Crashlytics.sharedInstance().setFloatValue(Float(iconWidth), forKey: "iconWidth") + Crashlytics.sharedInstance().setFloatValue(Float(iconHeight), forKey: "iconHeight") + Crashlytics.sharedInstance().setObjectValue(iconIdentifier, forKey: "iconIdentifier") + let fixedIconIdentifier = iconIdentifier.replacingOccurrences(of: ":", with: "-") + let iconCode = iconCodes[fixedIconIdentifier] + if iconIdentifier.contains("mdi") == false || iconCode == nil { + print("Invalid icon requested", iconIdentifier) + CLSLogv("Invalid icon requested %@", getVaList([iconIdentifier])) + let alert = UIAlertController(title: "Invalid icon", + // swiftlint:disable:next line_length + message: "There is an invalid icon in your configuration. Please search your configuration files for: \(iconIdentifier) and set it to a valid Material Design Icon. Until then, this icon will be a red exclamation point and this alert will continue to display.", preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) + UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil) + let iconCode = iconCodes["mdi-exclamation"] + let theIcon = FontAwesomeKit.FAKMaterialDesignIcons(code: iconCode, size: CGFloat(iconWidth)) + theIcon?.addAttribute(NSForegroundColorAttributeName, value: colorWithHexString("#ff0000")) + return theIcon!.image(with: CGSize(width: CGFloat(iconWidth), height: CGFloat(iconHeight))) + } + CLSLogv("Requesting MaterialDesignIcon: Identifier: %@, Fixed Identifier: %@, Width: %f, Height: %f", + getVaList([iconIdentifier, fixedIconIdentifier, iconWidth, iconHeight])) let theIcon = FontAwesomeKit.FAKMaterialDesignIcons(code: iconCode, size: CGFloat(iconWidth)) - theIcon?.addAttribute(NSForegroundColorAttributeName, value: colorWithHexString("#ff0000")) + theIcon?.addAttribute(NSForegroundColorAttributeName, value: color) return theIcon!.image(with: CGSize(width: CGFloat(iconWidth), height: CGFloat(iconHeight))) + } else { + return UIImage() } - CLSLogv("Requesting MaterialDesignIcon: Identifier: %@, Fixed Identifier: %@, Width: %f, Height: %f", getVaList([iconIdentifier, fixedIconIdentifier, iconWidth, iconHeight])) - // print("Requesting MaterialDesignIcon Identifier: \(iconIdentifier), Fixed identifier: \(fixedIconIdentifier), Width: \(iconWidth), Height: \(iconHeight)") - let theIcon = FontAwesomeKit.FAKMaterialDesignIcons(code: iconCode, size: CGFloat(iconWidth)) - theIcon?.addAttribute(NSForegroundColorAttributeName, value: color) - return theIcon!.image(with: CGSize(width: CGFloat(iconWidth), height: CGFloat(iconHeight))) } func colorWithHexString(_ hexString: String, alpha: CGFloat? = 1.0) -> UIColor { @@ -61,13 +66,25 @@ func intFromHexString(_ hexStr: String) -> UInt32 { } // Thanks to http://stackoverflow.com/a/35624018/486182 -// Must reboot device after installing new push sounds (http://stackoverflow.com/questions/34998278/change-push-notification-sound-file-only-works-after-ios-reboot) +// Must reboot device after installing new push sounds (http://stackoverflow.com/q/34998278/486182) +// swiftlint:disable:next function_body_length func movePushNotificationSounds() -> Int { + var movedFiles = 0 let fileManager: FileManager = FileManager() - let libraryPath = try! fileManager.url(for: .libraryDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false) + let libraryPath: URL + + do { + libraryPath = try fileManager.url(for: .libraryDirectory, + in: FileManager.SearchPathDomainMask.userDomainMask, + appropriateFor: nil, create: false) + } catch let error as NSError { + print("Error when building URL for library directory", error) + return 0 + } + let librarySoundsPath = libraryPath.appendingPathComponent("Sounds") do { @@ -78,9 +95,29 @@ func movePushNotificationSounds() -> Int { return 0 } - let documentsPath = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) - let fileList = try! fileManager.contentsOfDirectory(at: documentsPath, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions()) - var movedFiles = 0 + let documentsPath: URL + + do { + documentsPath = try fileManager.url(for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: false) + } catch let error as NSError { + print("Error building documents path URL", error) + return 0 + } + + let fileList: [URL] + + do { + fileList = try fileManager.contentsOfDirectory(at: documentsPath, + includingPropertiesForKeys: nil, + options: FileManager.DirectoryEnumerationOptions()) + } catch let error as NSError { + print("Error getting contents of documents directory", error) + return 0 + } + for file in fileList { if file.lastPathComponent.contains("realm") { continue @@ -93,8 +130,12 @@ func movePushNotificationSounds() -> Int { } catch let rmError as NSError { print("Error removing existing file", rmError) } - try! fileManager.moveItem(at: file, to: finalUrl) - movedFiles = movedFiles+1 + do { + try fileManager.moveItem(at: file, to: finalUrl) + movedFiles = movedFiles+1 + } catch let error as NSError { + print("Error when attempting to move files", error) + } } return movedFiles } @@ -102,9 +143,12 @@ func movePushNotificationSounds() -> Int { func getSoundList() -> [String] { var result: [String] = [] let fileManager = FileManager.default - let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: "/System/Library/Audio/UISounds/New")! + let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: + "/System/Library/Audio/UISounds/New")! for url in enumerator.allObjects { - result.append(url as! String) + if let urlString = url as? String { + result.append(urlString) + } } return result } @@ -113,7 +157,8 @@ func getSoundList() -> [String] { func copyFileToDirectory(_ fileName: String) { let fileManager = FileManager.default - let libraryDir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) + let libraryDir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, + FileManager.SearchPathDomainMask.userDomainMask, true) let directoryPath = "\(libraryDir.first!)/Sounds" do { print("Creating sounds directory at", directoryPath) @@ -127,25 +172,48 @@ func copyFileToDirectory(_ fileName: String) { let notificationSoundPath = "\(directoryPath)/\(fileName)" let fileExist = fileManager.fileExists(atPath: notificationSoundPath) - if (fileExist) { - try! fileManager.removeItem(atPath: notificationSoundPath) + if fileExist { + do { + try fileManager.removeItem(atPath: notificationSoundPath) + } catch let error as NSError { + print("Error when attempting to remove item", error) + } + } + do { + try fileManager.copyItem(atPath: systemSoundPath, toPath: notificationSoundPath) + } catch let error as NSError { + print("Error when attempgint to copy item", error) } - try! fileManager.copyItem(atPath: systemSoundPath, toPath: notificationSoundPath) } func listAllInstalledPushNotificationSounds() -> [String] { let fileManager: FileManager = FileManager() - let libraryPath = try! fileManager.url(for: .libraryDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false) + let libraryPath: URL + + do { + libraryPath = try fileManager.url(for: .libraryDirectory, + in: FileManager.SearchPathDomainMask.userDomainMask, + appropriateFor: nil, + create: false) + } catch let error as NSError { + print("Error when building URL for library directory", error) + return [String]() + } + let librarySoundsPath = libraryPath.appendingPathComponent("Sounds") - let librarySoundsContents = fileManager.enumerator(at: librarySoundsPath, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions(), errorHandler: nil)! + let librarySoundsContents = fileManager.enumerator(at: librarySoundsPath, + includingPropertiesForKeys: nil, + options: FileManager.DirectoryEnumerationOptions(), + errorHandler: nil)! var allSounds = [String]() for obj in librarySoundsContents.allObjects { - let file = obj as! URL - allSounds.append(file.lastPathComponent) + if let fileUrl = obj as? URL { + allSounds.append(fileUrl.lastPathComponent) + } } return allSounds } @@ -178,34 +246,9 @@ func migrateUserDefaultsToAppGroups() { } -func logUserDefaults() { - - let userDefaults = UserDefaults.standard - let groupDefaults = UserDefaults(suiteName: "group.io.robbie.homeassistant") - - if let groupDefaults = groupDefaults { - for key in groupDefaults.dictionaryRepresentation().keys { - print("groupDefaults \(key): \(groupDefaults.dictionaryRepresentation()[key])") - } - } else { - print("Unable to create NSUserDefaults with given app group") - } - - for key in userDefaults.dictionaryRepresentation().keys { - print("userDefaults \(key): \(userDefaults.dictionaryRepresentation()[key])") - } -} - -func checkIfIPIsInternal(ipAddress: String) -> Bool { - let pat = "/(^127\\.)|(^192\\.168\\.)|(^10\\.)|(^172\\.1[6-9]\\.)|(^172\\.2[0-9]\\.)|(^172\\.3[0-1]\\.)|(^::1$)|(^[fF][cCdD])/" - let regex = try! NSRegularExpression(pattern: pat, options: []) - let matches = regex.matches(in: ipAddress, options: [], range: NSRange(location: 0, length: ipAddress.characters.count)) - return (matches.count > 0) -} - func openURLInBrowser(url: String) { if let urlToOpen = URL(string: url) { - if (OpenInChromeController.sharedInstance.isChromeInstalled()) { + if OpenInChromeController.sharedInstance.isChromeInstalled() { _ = OpenInChromeController.sharedInstance.openInChrome(urlToOpen) } else { if #available(iOS 10, *) { diff --git a/HomeAssistant/Views/AboutViewController.swift b/HomeAssistant/Views/AboutViewController.swift index 7e5cf0798..8e53c54de 100644 --- a/HomeAssistant/Views/AboutViewController.swift +++ b/HomeAssistant/Views/AboutViewController.swift @@ -13,16 +13,20 @@ import CPDAcknowledgements class AboutViewController: FormViewController { + // swiftlint:disable:next function_body_length override func viewDidLoad() { super.viewDidLoad() - self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(AboutViewController.closeAboutView(_:))) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, + target: self, + action: #selector(AboutViewController.close(_:))) form - +++ Section() { - $0.header = HeaderFooterView(.nibFile(name: "HomeAssistantLogoView", bundle: nil)) + +++ Section { + $0.header = HeaderFooterView(.nibFile(name: "HomeAssistantLogoView", + bundle: nil)) } - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Acknowledgements" $0.presentationMode = .show(controllerProvider: ControllerProvider.callback { return self.generateAcknowledgements() @@ -31,7 +35,7 @@ class AboutViewController: FormViewController { }) } +++ Section() - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Website" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -42,7 +46,7 @@ class AboutViewController: FormViewController { openURLInBrowser(url: "https://home-assistant.io/") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Forums" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -53,7 +57,7 @@ class AboutViewController: FormViewController { openURLInBrowser(url: "https://community.home-assistant.io/") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Chat" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -64,7 +68,7 @@ class AboutViewController: FormViewController { openURLInBrowser(url: "https://gitter.im/home-assistant/home-assistant") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Documentation" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -75,7 +79,7 @@ class AboutViewController: FormViewController { openURLInBrowser(url: "https://home-assistant.io/ecosystem/ios/") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Home Assistant on Twitter" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -86,7 +90,7 @@ class AboutViewController: FormViewController { self.openInTwitterApp(username: "home_assistant") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Home Assistant on Facebook" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -97,7 +101,7 @@ class AboutViewController: FormViewController { self.openInFacebook(pageId: "292963007723872") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "GitHub" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -108,7 +112,7 @@ class AboutViewController: FormViewController { openURLInBrowser(url: "https://github.com/home-assistant/home-assistant-iOS") }) - <<< ButtonRow() { + <<< ButtonRow { $0.title = "GitHub Issue Tracker" }.cellUpdate { cell, _ in cell.textLabel?.textAlignment = .left @@ -137,11 +141,6 @@ class AboutViewController: FormViewController { */ func generateAcknowledgements() -> CPDAcknowledgementsViewController { - // let robbie = CPDContribution.init(name: "Robbie Trencheny", websiteAddress: "https://twitter.com/robbie", role: "Primary iOS developer") - // robbie.avatarAddress = "https://s.gravatar.com/avatar/04178c46aa6f009adba24b3e7ac64f14" - // let paulus = CPDContribution.init(name: "Paulus Schousten", websiteAddress: "https://twitter.com/balloob", role: "Home Assistant creator & BDFL") - // paulus.avatarAddress = "https://s.gravatar.com/avatar/dee932f2cb7ad0af8c5791217a085d35" - // let contributors = [robbie, paulus] return CPDAcknowledgementsViewController.init(style: nil, acknowledgements: nil, contributions: nil) } @@ -196,7 +195,7 @@ class AboutViewController: FormViewController { } } - func closeAboutView(_ sender: UIBarButtonItem) { + func close(_ sender: UIBarButtonItem) { self.navigationController?.dismiss(animated: true, completion: nil) } } diff --git a/HomeAssistant/Views/DevicesMapViewController.swift b/HomeAssistant/Views/DevicesMapViewController.swift index bffc1e207..bb0c18d0b 100644 --- a/HomeAssistant/Views/DevicesMapViewController.swift +++ b/HomeAssistant/Views/DevicesMapViewController.swift @@ -28,6 +28,7 @@ class DevicesMapViewController: UIViewController, MKMapViewDelegate { var mapView: MKMapView! + // swiftlint:disable:next function_body_length override func viewDidLoad() { super.viewDidLoad() @@ -38,13 +39,27 @@ class DevicesMapViewController: UIViewController, MKMapViewDelegate { let items = ["Standard", "Hybrid", "Satellite"] let typeController = UISegmentedControl(items: items) typeController.selectedSegmentIndex = 0 - typeController.addTarget(self, action: #selector(DevicesMapViewController.switchMapType(_:)), for: .valueChanged) + typeController.addTarget(self, + action: #selector(DevicesMapViewController.switchMapType(_:)), + for: .valueChanged) - let uploadIcon = getIconForIdentifier("mdi:upload", iconWidth: 30, iconHeight: 30, color: colorWithHexString("#44739E", alpha: 1)) + let uploadIcon = getIconForIdentifier("mdi:upload", + iconWidth: 30, + iconHeight: 30, + color: colorWithHexString("#44739E", alpha: 1)) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: uploadIcon, style: .plain, target: self, action: #selector(DevicesMapViewController.sendCurrentLocation(_:))) + let leftBarItem = UIBarButtonItem(image: uploadIcon, + style: .plain, + target: self, + action: #selector(DevicesMapViewController.sendCurrentLocation(_:))) - self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(DevicesMapViewController.closeMapView(_:))) + self.navigationItem.leftBarButtonItem = leftBarItem + + let rightBarItem = UIBarButtonItem(barButtonSystemItem: .done, + target: self, + action: #selector(DevicesMapViewController.closeMapView(_:))) + + self.navigationItem.rightBarButtonItem = rightBarItem // Do any additional setup after loading the view. mapView = MKMapView() @@ -78,7 +93,8 @@ class DevicesMapViewController: UIViewController, MKMapViewDelegate { dropPin.title = device.Name var subtitlePieces: [String] = [] // if let changedTime = device.LastChanged { - // subtitlePieces.append("Last seen: "+changedTime.toRelativeString(abbreviated: true, maxUnits: 1)!+" ago") + // subtitlePieces.append("Last seen: "+changedTime.toRelativeString(abbreviated: true, + // maxUnits: 1)!+" ago") // } if let battery = device.Battery.value { subtitlePieces.append("Battery: "+String(battery)+"%") @@ -107,7 +123,9 @@ class DevicesMapViewController: UIViewController, MKMapViewDelegate { if let firstOverlay = mapView.overlays.first { let rect = mapView.overlays.reduce(firstOverlay.boundingMapRect, {MKMapRectUnion($0, $1.boundingMapRect)}) - mapView.setVisibleMapRect(MKMapRectUnion(zoomRect, rect), edgePadding: UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0), animated: true) + mapView.setVisibleMapRect(MKMapRectUnion(zoomRect, rect), + edgePadding: UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0), + animated: true) } } @@ -119,7 +137,7 @@ class DevicesMapViewController: UIViewController, MKMapViewDelegate { func switchMapType(_ sender: UISegmentedControl) { let mapType = MapType(rawValue: sender.selectedSegmentIndex) - switch (mapType!) { + switch mapType! { case .standard: mapView.mapType = MKMapType.standard case .hybrid: @@ -171,12 +189,17 @@ class DevicesMapViewController: UIViewController, MKMapViewDelegate { func sendCurrentLocation(_ sender: UIBarButtonItem) { HomeAssistantAPI.sharedInstance.sendOneshotLocation().then { _ -> Void in - let alert = UIAlertController(title: "Location updated", message: "Successfully sent a one shot location to the server", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Location updated", + message: "Successfully sent a one shot location to the server", + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) }.catch {error in let nserror = error as NSError - let alert = UIAlertController(title: "Location failed to update", message: "Failed to send current location to server. The error was \(nserror.localizedDescription)", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Location failed to update", + // swiftlint:disable:next line_length + message: "Failed to send current location to server. The error was \(nserror.localizedDescription)", + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } diff --git a/HomeAssistant/Views/EntityAttributesViewController.swift b/HomeAssistant/Views/EntityAttributesViewController.swift index e858125d4..b91b88254 100644 --- a/HomeAssistant/Views/EntityAttributesViewController.swift +++ b/HomeAssistant/Views/EntityAttributesViewController.swift @@ -15,6 +15,7 @@ class EntityAttributesViewController: FormViewController { var entityID: String = "" + // swiftlint:disable:next cyclomatic_complexity function_body_length override func viewDidLoad() { super.viewDidLoad() @@ -41,7 +42,7 @@ class EntityAttributesViewController: FormViewController { paragraphStyle.alignment = .center let attrs: [String:AnyObject] = [NSParagraphStyleAttributeName: paragraphStyle] - let range = NSMakeRange(0, result.length) + let range = NSRange(location: 0, length: 12) result.addAttributes(attrs, range: range) cell.textView.textStorage.setAttributedString(result) @@ -66,7 +67,7 @@ class EntityAttributesViewController: FormViewController { $0.title = prettyLabel $0.value = thermostat.Fan }.onChange { row -> Void in - if (row.value == true) { + if row.value! { thermostat.turnFanOn() } else { thermostat.turnFanOff() @@ -80,7 +81,7 @@ class EntityAttributesViewController: FormViewController { $0.title = prettyLabel $0.value = thermostat.AwayMode }.onChange { row -> Void in - if (row.value == true) { + if row.value! { thermostat.setAwayModeOn() } else { thermostat.setAwayModeOff() @@ -115,7 +116,7 @@ class EntityAttributesViewController: FormViewController { $0.title = "Mute" $0.value = mediaPlayer.IsVolumeMuted.value }.onChange { row -> Void in - if (row.value == true) { + if row.value! { mediaPlayer.muteOn() } else { mediaPlayer.muteOff() @@ -125,14 +126,16 @@ class EntityAttributesViewController: FormViewController { break case "volume_level": if let mediaPlayer = entity as? MediaPlayer { - let volume = Float(attribute.1 as! NSNumber)*100 - form.last! <<< SliderRow(attribute.0) { - $0.title = prettyLabel - $0.value = volume - $0.maximumValue = 100 - $0.steps = 100 - }.onChange { row -> Void in - mediaPlayer.setVolume(row.value!) + if let volumeNumber = attribute.1 as? NSNumber { + let volume = Float(volumeNumber)*100 + form.last! <<< SliderRow(attribute.0) { + $0.title = prettyLabel + $0.value = volume + $0.maximumValue = 100 + $0.steps = 100 + }.onChange { row -> Void in + mediaPlayer.setVolume(row.value!) + } } } break @@ -145,7 +148,7 @@ class EntityAttributesViewController: FormViewController { $0.title = entity?.Name $0.value = (entity?.State == "on") ? true : false }.onChange { row -> Void in - if (row.value == true) { + if row.value! { let _ = HomeAssistantAPI.sharedInstance.turnOn(entityId: entity!.ID) } else { let _ = HomeAssistantAPI.sharedInstance.turnOff(entityId: entity!.ID) @@ -164,62 +167,92 @@ class EntityAttributesViewController: FormViewController { } // Do any additional setup after loading the view. - NotificationCenter.default.addObserver(self, selector: #selector(EntityAttributesViewController.StateChangedSSEEvent(_:)), name:NSNotification.Name(rawValue: "sse.state_changed"), object: nil) + NotificationCenter.default.addObserver(self, + // swiftlint:disable:next line_length + selector: #selector(EntityAttributesViewController.StateChangedSSEEvent(_:)), + name:NSNotification.Name(rawValue: "sse.state_changed"), + object: nil) } + // swiftlint:enable cyclomatic_complexity function_body_length override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } + // swiftlint:disable:next cyclomatic_complexity function_body_length func StateChangedSSEEvent(_ notification: NSNotification) { if let userInfo = (notification as NSNotification).userInfo { - if let event = Mapper().map(JSON: userInfo as! [String : Any]) { - if event.EntityID != entityID { return } - let entity = realm.object(ofType: Entity.self, forPrimaryKey: entityID as AnyObject) - if let newState = event.NewState { - var updateDict: [String:Any] = [:] - newState.Attributes["state"] = entity?.State - for (key, value) in newState.Attributes { - switch key { - case "fan": - updateDict[key] = (entity as! Thermostat).Fan! - break - case "away_mode": - updateDict[key] = (entity as! Thermostat).AwayMode! - break - case "temperature": - updateDict[key] = Float((entity as! Thermostat).Temperature!) - break - case "media_duration": - updateDict[key] = (entity as! MediaPlayer).humanReadableMediaDuration() - break - case "is_volume_muted": - updateDict[key] = (entity as! MediaPlayer).IsVolumeMuted - break - case "volume_level": - updateDict[key] = Float(value as! NSNumber)*100 - break - case "entity_picture", "icon", "supported_media_commands", "hidden", "assumed_state": - // Skip these attributes - break - case "state": - if entity?.Domain == "switch" || entity?.Domain == "light" || entity?.Domain == "input_boolean" { - updateDict["state"] = (entity?.State == "on") as Bool - } else { - fallthrough + if let userInfoDict = userInfo as? [String : Any] { + if let event = Mapper().map(JSON: userInfoDict) { + if event.EntityID != entityID { return } + let entity = realm.object(ofType: Entity.self, forPrimaryKey: entityID as AnyObject) + if let newState = event.NewState { + var updateDict: [String:Any] = [:] + newState.Attributes["state"] = entity?.State + for (key, value) in newState.Attributes { + switch key { + case "fan": + if let thermostat = entity as? Thermostat { + if let fan = thermostat.Fan { + updateDict[key] = fan + } + } + break + case "away_mode": + if let thermostat = entity as? Thermostat { + if let awaymode = thermostat.AwayMode { + updateDict[key] = awaymode + } + } + break + case "temperature": + if let thermostat = entity as? Thermostat { + if let temperature = thermostat.Temperature { + updateDict[key] = Float(temperature) + } + } + break + case "media_duration": + if let mediaPlayer = entity as? MediaPlayer { + updateDict[key] = mediaPlayer.humanReadableMediaDuration() + } + break + case "is_volume_muted": + if let mediaPlayer = entity as? MediaPlayer { + updateDict[key] = mediaPlayer.IsVolumeMuted + } + break + case "volume_level": + if let mediaPlayer = entity as? MediaPlayer { + if let level = mediaPlayer.VolumeLevel.value { + updateDict[key] = Float(level) * Float(100.0) + } + } + break + case "entity_picture", "icon", "supported_media_commands", "hidden", "assumed_state": + // Skip these attributes + break + case "state": + if entity?.Domain == "switch" || + entity?.Domain == "light" || + entity?.Domain == "input_boolean" { + updateDict["state"] = (entity?.State == "on") as Bool + } else { + fallthrough + } + break + default: + updateDict[key] = String(describing: value) + break } - break - default: - updateDict[key] = String(describing: value) - break } + // fatal error: can't unsafeBitCast between types of different sizes + self.form.setValues(updateDict) } - // fatal error: can't unsafeBitCast between types of different sizes - self.form.setValues(updateDict) } } } } - + // swiftlint:enable cyclomatic_complexity } diff --git a/HomeAssistant/Views/EurekaLocationRow.swift b/HomeAssistant/Views/EurekaLocationRow.swift index 7be587701..8542f8927 100644 --- a/HomeAssistant/Views/EurekaLocationRow.swift +++ b/HomeAssistant/Views/EurekaLocationRow.swift @@ -16,7 +16,12 @@ import MapKit public final class LocationRow: SelectorRow, MapViewController>, RowType { public required init(tag: String?) { super.init(tag: tag) - presentationMode = .show(controllerProvider: ControllerProvider.callback { return MapViewController() { _ in } }, onDismiss: { vc in _ = vc.navigationController?.popViewController(animated: true) }) + presentationMode = .show(controllerProvider: ControllerProvider.callback { + return MapViewController { _ in + } + }, onDismiss: { + vc in _ = vc.navigationController?.popViewController(animated: true) + }) displayValueFor = { guard let location = $0 else { return "" } diff --git a/HomeAssistant/Views/GroupViewController.swift b/HomeAssistant/Views/GroupViewController.swift index f31f26d2e..dc7a929cb 100644 --- a/HomeAssistant/Views/GroupViewController.swift +++ b/HomeAssistant/Views/GroupViewController.swift @@ -24,6 +24,7 @@ class GroupViewController: FormViewController { var sendingEntity: Entity? + // swiftlint:disable:next cyclomatic_complexity function_body_length override func viewDidLoad() { super.viewDidLoad() @@ -35,22 +36,23 @@ class GroupViewController: FormViewController { +++ Section() for entity in group!.Entities { switch entity.Domain { - // case "switch", "light", "input_boolean": - // self.form.last! <<< SwitchRow(entity.ID) { - // $0.title = entity.Name - // $0.value = (entity.State == "on") ? true : false - // }.onChange { row -> Void in - // if (row.value == true) { - // HomeAssistantAPI.sharedInstance.turnOn(entity.ID) - // } else { - // HomeAssistantAPI.sharedInstance.turnOff(entity.ID) - // } - // }.cellSetup { cell, row in - // cell.imageView?.image = entity.EntityIcon - // if let picture = entity.DownloadedPicture { - // cell.imageView?.image = picture.scaledToSize(CGSize(width: 30, height: 30)) - // } - // } + // case "switch", "light", "input_boolean": + // self.form.last! <<< SwitchRow(entity.ID) { + // $0.title = entity.Name + // $0.value = (entity.State == "on") ? true : false + // }.onChange { row -> Void in + // if (row.value == true) { + // HomeAssistantAPI.sharedInstance.turnOn(entity.ID) + // } else { + // HomeAssistantAPI.sharedInstance.turnOff(entity.ID) + // } + // }.cellSetup { cell, row in + // cell.imageView?.image = entity.EntityIcon + // if let picture = entity.DownloadedPicture { + // cell.imageView?.image = picture.scaledToSize(CGSize(width: 30, + // height: 30)) + // } + // } case "script", "scene": self.form.last! <<< ButtonRow(entity.ID) { $0.title = entity.Name @@ -67,7 +69,11 @@ class GroupViewController: FormViewController { self.form.last! <<< ButtonRow(entity.ID) { $0.title = entity.Name if url.scheme == "http" || url.scheme == "https" { - $0.presentationMode = .presentModally(controllerProvider: ControllerProvider.callback { return SFSafariViewController(url: url, entersReaderIfAvailable: false) }, onDismiss: { vc in let _ = vc.navigationController?.popViewController(animated: true) }) + $0.presentationMode = .presentModally(controllerProvider: ControllerProvider.callback { + return SFSafariViewController(url: url, entersReaderIfAvailable: false) + }, onDismiss: { vc in + let _ = vc.navigationController?.popViewController(animated: true) + }) } }.cellUpdate { cell, _ in cell.imageView?.image = entity.EntityIcon @@ -80,7 +86,8 @@ class GroupViewController: FormViewController { } } } - case "switch", "light", "input_boolean", "binary_sensor", "camera", "sensor", "media_player", "thermostat", "sun", "climate", "automation", "fan": + case "switch", "light", "input_boolean", "binary_sensor", "camera", "sensor", "media_player", + "thermostat", "sun", "climate", "automation", "fan": self.form.last! <<< ButtonRow(entity.ID) { $0.title = entity.Name $0.cellStyle = .value1 @@ -122,11 +129,15 @@ class GroupViewController: FormViewController { let attributesView = EntityAttributesViewController() attributesView.entityID = entity.ID return attributesView - }, onDismiss: { vc in let _ = vc.navigationController?.popViewController(animated: true) }) + }, onDismiss: { + vc in let _ = vc.navigationController?.popViewController(animated: true) + }) }.cellUpdate { cell, _ in cell.detailTextLabel?.text = entity.CleanedState if let uom = entity.UnitOfMeasurement { - cell.detailTextLabel?.text = (entity.State + " " + uom).replacingOccurrences(of: "_", with: " ").capitalized + let combinedString = entity.State + " " + uom + let withReplacements = combinedString.replacingOccurrences(of: "_", with: " ") + cell.detailTextLabel?.text = withReplacements.capitalized } cell.imageView?.image = entity.EntityIcon if let picture = entity.DownloadedPicture { @@ -146,7 +157,9 @@ class GroupViewController: FormViewController { }.cellUpdate { cell, _ in cell.detailTextLabel?.text = entity.CleanedState if let uom = entity.UnitOfMeasurement { - cell.detailTextLabel?.text = (entity.State + " " + uom).replacingOccurrences(of: "_", with: " ").capitalized + let combinedString = entity.State + " " + uom + let withReplacements = combinedString.replacingOccurrences(of: "_", with: " ") + cell.detailTextLabel?.text = withReplacements.capitalized } cell.imageView?.image = entity.EntityIcon if let picture = entity.DownloadedPicture { @@ -160,10 +173,12 @@ class GroupViewController: FormViewController { // $0.value = entity.State // $0.options = entity.Attributes["options"] as! [String] // }.onChange { row -> Void in + // swiftlint:disable:next line_length // let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "input_select", service: "select_option", serviceData: ["entity_id": entity.ID as AnyObject, "option": row.value! as AnyObject]) // }.cellUpdate { cell, row in // cell.imageView?.image = entity.EntityIcon // if let picture = entity.DownloadedPicture { + // swiftlint:disable:next line_length // cell.imageView?.image = picture.scaledToSize(CGSize(width: 30, height: 30)) // } // } @@ -172,7 +187,11 @@ class GroupViewController: FormViewController { $0.title = entity.Name $0.value = (entity.State == "locked") ? true : false }.onChange { row -> Void in - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "lock", service: ((row.value == true) ? "lock" : "unlock"), serviceData: ["entity_id": entity.ID as AnyObject]) + let whichService = (row.value == true) ? "lock" : "unlock" + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "lock", service: whichService, + serviceData: [ + "entity_id": entity.ID as AnyObject + ]) }.cellUpdate { cell, _ in cell.imageView?.image = entity.EntityIcon if let picture = entity.DownloadedPicture { @@ -184,7 +203,12 @@ class GroupViewController: FormViewController { $0.title = entity.Name $0.value = (entity.State == "open") ? true : false }.onChange { row -> Void in - let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "garage_door", service: ((row.value == true) ? "open" : "close"), serviceData: ["entity_id": entity.ID as AnyObject]) + let whichService = (row.value == true) ? "open" : "close" + let _ = HomeAssistantAPI.sharedInstance.CallService(domain: "garage_door", + service: whichService, + serviceData: [ + "entity_id": entity.ID as AnyObject + ]) }.cellUpdate { cell, _ in cell.imageView?.image = entity.EntityIcon if let picture = entity.DownloadedPicture { @@ -212,9 +236,6 @@ class GroupViewController: FormViewController { slider.SelectValue(row.value!) } }.cellUpdate { _, row in - // if let uom = entity.UnitOfMeasurement { - // cell.detailTextLabel?.text = (entity.State.capitalized + " " + uom) - // } row.displayValueFor = { (_) in if let uom = entity.UnitOfMeasurement { return (entity.State.capitalized + " " + uom) @@ -229,9 +250,11 @@ class GroupViewController: FormViewController { } } - NotificationCenter.default.addObserver(self, selector: #selector(GroupViewController.StateChangedSSEEvent(_:)), name:NSNotification.Name(rawValue: "sse.state_changed"), object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(GroupViewController.StateChangedSSEEvent(_:)), + name:NSNotification.Name(rawValue: "sse.state_changed"), + object: nil) } - override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. @@ -239,24 +262,26 @@ class GroupViewController: FormViewController { func StateChangedSSEEvent(_ notification: NSNotification) { if let userInfo = (notification as NSNotification).userInfo { - if let event = Mapper().map(JSON: userInfo as! [String : Any]) { - if let newState = event.NewState { - if newState.Domain == "lock" || newState.Domain == "garage_door" { - if let row: SwitchRow = self.form.rowBy(tag: newState.ID) { - row.value = (newState.State == "on") ? true : false - row.cell.imageView?.image = newState.EntityIcon - row.updateCell() - row.reload() - } - } else { - if let row: ButtonRow = self.form.rowBy(tag: newState.ID) { - row.value = newState.State - if let uom = newState.UnitOfMeasurement { - row.value = newState.State + " " + uom + if let userInfoDict = userInfo as? [String : Any] { + if let event = Mapper().map(JSON: userInfoDict) { + if let newState = event.NewState { + if newState.Domain == "lock" || newState.Domain == "garage_door" { + if let row: SwitchRow = self.form.rowBy(tag: newState.ID) { + row.value = (newState.State == "on") ? true : false + row.cell.imageView?.image = newState.EntityIcon + row.updateCell() + row.reload() + } + } else { + if let row: ButtonRow = self.form.rowBy(tag: newState.ID) { + row.value = newState.State + if let uom = newState.UnitOfMeasurement { + row.value = newState.State + " " + uom + } + row.cell.imageView?.image = newState.EntityIcon + row.updateCell() + row.reload() } - row.cell.imageView?.image = newState.EntityIcon - row.updateCell() - row.reload() } } } @@ -268,8 +293,9 @@ class GroupViewController: FormViewController { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. if segue.identifier == "ShowEntityAttributes" { - let entityAttributesViewController = segue.destination as! EntityAttributesViewController - entityAttributesViewController.entityID = sendingEntity!.ID + if let destination = segue.destination as? EntityAttributesViewController { + destination.entityID = sendingEntity!.ID + } } } diff --git a/HomeAssistant/Views/RootTabBarViewController.swift b/HomeAssistant/Views/RootTabBarViewController.swift index 44d7f0360..c27255975 100644 --- a/HomeAssistant/Views/RootTabBarViewController.swift +++ b/HomeAssistant/Views/RootTabBarViewController.swift @@ -18,9 +18,13 @@ class RootTabBarViewController: UITabBarController, UITabBarControllerDelegate { super.viewDidLoad() // Do any additional setup after loading the view. - NotificationCenter.default.addObserver(self, selector: #selector(RootTabBarViewController.StateChangedSSEEvent(_:)), name:NSNotification.Name(rawValue: "sse.state_changed"), object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(RootTabBarViewController.StateChangedSSEEvent(_:)), + name:NSNotification.Name(rawValue: "sse.state_changed"), + object: nil) } + // swiftlint:disable:next function_body_length override func viewWillAppear(_ animated: Bool) { let hud = MBProgressHUD.showAdded(to: self.view, animated: true) @@ -90,6 +94,7 @@ class RootTabBarViewController: UITabBarController, UITabBarControllerDelegate { if group.Order.value == nil { // Save the index now since it should be first time running + // swiftlint:disable:next force_try try! realm.write { group.Order.value = index } @@ -98,13 +103,27 @@ class RootTabBarViewController: UITabBarController, UITabBarControllerDelegate { if HomeAssistantAPI.sharedInstance.locationEnabled { var rightBarItems: [UIBarButtonItem] = [] - let uploadIcon = getIconForIdentifier("mdi:upload", iconWidth: 30, iconHeight: 30, color: tabBarIconColor) + let uploadIcon = getIconForIdentifier("mdi:upload", + iconWidth: 30, + iconHeight: 30, + color: tabBarIconColor) - rightBarItems.append(UIBarButtonItem(image: uploadIcon, style: .plain, target: self, action: #selector(RootTabBarViewController.sendCurrentLocation(_:)))) + rightBarItems.append(UIBarButtonItem(image: uploadIcon, + style: .plain, + target: self, + action: #selector(RootTabBarViewController.sendCurrentLocation(_:)) + ) + ) - let mapIcon = getIconForIdentifier("mdi:map", iconWidth: 30, iconHeight: 30, color: tabBarIconColor) + let mapIcon = getIconForIdentifier("mdi:map", + iconWidth: 30, + iconHeight: 30, + color: tabBarIconColor) - rightBarItems.append(UIBarButtonItem(image: mapIcon, style: .plain, target: self, action: #selector(RootTabBarViewController.openMapView(_:)))) + rightBarItems.append(UIBarButtonItem(image: mapIcon, + style: .plain, + target: self, + action: #selector(RootTabBarViewController.openMapView(_:)))) groupView.navigationItem.setRightBarButtonItems(rightBarItems, animated: true) } @@ -131,25 +150,31 @@ class RootTabBarViewController: UITabBarController, UITabBarControllerDelegate { hud.hide(animated: true) } - func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { + func tabBarController(_ tabBarController: UITabBarController, + shouldSelect viewController: UIViewController) -> Bool { return true } - func tabBarController(_ tabBarController: UITabBarController, willEndCustomizing viewControllers: [UIViewController], changed: Bool) { + func tabBarController(_ tabBarController: UITabBarController, + willEndCustomizing viewControllers: [UIViewController], changed: Bool) { } - func tabBarController(_ tabBarController: UITabBarController, didEndCustomizing viewControllers: [UIViewController], changed: Bool) { - if (changed) { + func tabBarController(_ tabBarController: UITabBarController, + didEndCustomizing viewControllers: [UIViewController], changed: Bool) { + if changed { for (index, view) in viewControllers.enumerated() { - if let groupView = (view as! UINavigationController).viewControllers[0] as? GroupViewController { - let update = ["ID": groupView.GroupID, "Order": index] as [String : Any] - try! realm.write { - realm.create(Group.self, value: update as AnyObject, update: true) + if let navController = view as? UINavigationController { + if let groupView = navController.viewControllers[0] as? GroupViewController { + let update = ["ID": groupView.GroupID, "Order": index] as [String : Any] + // swiftlint:disable:next force_try + try! realm.write { + realm.create(Group.self, value: update as AnyObject, update: true) + } + print("\(index): \(groupView.tabBarItem.title!) New: \(index) Old: \(groupView.Order!)") + } else { + print("Couldn't cast to a group, must be settings, skipping!") } - print("\(index): \(groupView.tabBarItem.title!) New: \(index) Old: \(groupView.Order!)") - } else { - print("Couldn't cast to a group, must be settings, skipping!") } } } @@ -164,11 +189,11 @@ class RootTabBarViewController: UITabBarController, UITabBarControllerDelegate { if let userInfo = (notification as NSNotification).userInfo { if let jsonObj = userInfo["jsonObject"] as? [String: Any] { if let event = Mapper().map(JSON: jsonObj) { - let newState = event.NewState! as Entity - let oldState = event.OldState! as Entity - var subtitleString = "\(newState.FriendlyName!) is now \(newState.State). It was \(oldState.State)" - if let uom = newState.UnitOfMeasurement { - subtitleString = "\(newState.State) \(uom). It was \(oldState.State) \(oldState.UnitOfMeasurement)" + let new = event.NewState! as Entity + let old = event.OldState! as Entity + var subtitleString = "\(new.FriendlyName!) is now \(new.State). It was \(old.State)" + if let uom = new.UnitOfMeasurement { + subtitleString = "\(new.State) \(uom). It was \(old.State) \(old.UnitOfMeasurement)" } let _ = Murmur(title: subtitleString) } @@ -185,12 +210,17 @@ class RootTabBarViewController: UITabBarController, UITabBarControllerDelegate { func sendCurrentLocation(_ sender: UIButton) { HomeAssistantAPI.sharedInstance.sendOneshotLocation().then { _ -> Void in - let alert = UIAlertController(title: "Location updated", message: "Successfully sent a one shot location to the server", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Location updated", + message: "Successfully sent a one shot location to the server", + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) }.catch {error in let nserror = error as NSError - let alert = UIAlertController(title: "Location failed to update", message: "Failed to send current location to server. The error was \(nserror.localizedDescription)", preferredStyle: UIAlertControllerStyle.alert) + let message = "Failed to send current location to server. The error was \(nserror.localizedDescription)" + let alert = UIAlertController(title: "Location failed to update", + message: message, + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } diff --git a/HomeAssistant/Views/SettingsDetailViewController.swift b/HomeAssistant/Views/SettingsDetailViewController.swift index 69b5dd9b0..682d5ec5e 100644 --- a/HomeAssistant/Views/SettingsDetailViewController.swift +++ b/HomeAssistant/Views/SettingsDetailViewController.swift @@ -18,6 +18,7 @@ class SettingsDetailViewController: FormViewController { var detailGroup: String = "display" + // swiftlint:disable:next function_body_length override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. @@ -41,38 +42,43 @@ class SettingsDetailViewController: FormViewController { +++ Section(header: zone.Name, footer: "") { $0.tag = zone.ID } - <<< SwitchRow() { + <<< SwitchRow { $0.title = "Updates Enabled" $0.value = zone.trackingEnabled }.onChange { row in + // swiftlint:disable:next force_try try! realm.write { zone.trackingEnabled = row.value! } } - <<< LocationRow() { + <<< LocationRow { $0.title = "Location" $0.value = zone.location() } - <<< LabelRow() { + <<< LabelRow { $0.title = "Radius" $0.value = "\(Int(zone.Radius)) m" } - <<< SwitchRow() { + <<< SwitchRow { $0.title = "Enter Notification" $0.value = zone.enterNotification }.onChange { row in + // swiftlint:disable:next force_try try! realm.write { zone.enterNotification = row.value! } } - <<< SwitchRow() { + <<< SwitchRow { $0.title = "Exit Notification" $0.value = zone.exitNotification }.onChange { row in + // swiftlint:disable:next force_try try! realm.write { zone.exitNotification = row.value! } } } case "notifications": self.title = "Notification Settings" self.form - +++ Section(header: "Push token", footer: "This is the target to use in your Home Assistant configuration. Tap to copy or share.") - <<< TextAreaRow() { + +++ Section(header: "Push token", + // swiftlint:disable:next line_length + footer: "This is the target to use in your Home Assistant configuration. Tap to copy or share.") + <<< TextAreaRow { $0.placeholder = "PushID" if let pushID = prefs.string(forKey: "pushID") { $0.value = pushID @@ -82,32 +88,42 @@ class SettingsDetailViewController: FormViewController { $0.disabled = true $0.textAreaHeight = TextAreaHeight.dynamic(initialTextViewHeight: 40) }.onCellSelection { _, row in - let activityViewController = UIActivityViewController(activityItems: [row.value! as String], applicationActivities: nil) + let activityViewController = UIActivityViewController(activityItems: [row.value! as String], + applicationActivities: nil) self.present(activityViewController, animated: true, completion: {}) } - +++ Section(header: "", footer: "Updating push settings will request the latest push actions and categories from Home Assistant.") - <<< ButtonRow() { + +++ Section(header: "", + // swiftlint:disable:next line_length + footer: "Updating push settings will request the latest push actions and categories from Home Assistant.") + <<< ButtonRow { $0.title = "Update push settings" }.onCellSelection {_, _ in HomeAssistantAPI.sharedInstance.setupPush() - let alert = UIAlertController(title: "Settings Import", message: "Push settings imported from Home Assistant.", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Settings Import", + message: "Push settings imported from Home Assistant.", + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } +++ Section(header: "", footer: "Custom push notification sounds can be added via iTunes.") - <<< ButtonRow() { + <<< ButtonRow { $0.title = "Import sounds from iTunes" }.onCellSelection {_, _ in let moved = movePushNotificationSounds() - let message = (moved > 0) ? "\(moved) sounds were imported. Please restart your phone to complete the import." : "0 sounds were imported." - let alert = UIAlertController(title: "Sounds Import", message: message, preferredStyle: UIAlertControllerStyle.alert) + var message = "0 sounds were imported." + if moved > 0 { + message = "\(moved) sounds were imported. Please restart your phone to complete the import." + } + let alert = UIAlertController(title: "Sounds Import", + message: message, + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } - // <<< ButtonRow() { + // <<< ButtonRow { // $0.title = "Import system sounds" // }.onCellSelection {_,_ in // let list = getSoundList() diff --git a/HomeAssistant/Views/SettingsViewController.swift b/HomeAssistant/Views/SettingsViewController.swift index ec96ed054..ecd729fcd 100644 --- a/HomeAssistant/Views/SettingsViewController.swift +++ b/HomeAssistant/Views/SettingsViewController.swift @@ -14,6 +14,8 @@ import Crashlytics import SafariServices import Alamofire +// swiftlint:disable file_length +// swiftlint:disable:next type_body_length class SettingsViewController: FormViewController { let prefs = UserDefaults(suiteName: "group.io.robbie.homeassistant")! @@ -33,11 +35,15 @@ class SettingsViewController: FormViewController { self.discovery.stopPublish() } + // swiftlint:disable:next cyclomatic_complexity function_body_length override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. - let aboutButton = UIBarButtonItem(title: "About", style: .plain, target: self, action: #selector(SettingsViewController.openAbout(_:))) + let aboutButton = UIBarButtonItem(title: "About", + style: .plain, + target: self, + action: #selector(SettingsViewController.openAbout(_:))) self.navigationItem.setRightBarButton(aboutButton, animated: true) @@ -54,7 +60,9 @@ class SettingsViewController: FormViewController { checkForEmail() if showErrorConnectingMessage { - let alert = UIAlertController(title: "Connection error", message: "There was an error connecting to Home Assistant. Please confirm the settings are correct and save to attempt to reconnect.", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Connection error", + // swiftlint:disable:next line_length + message: "There was an error connecting to Home Assistant. Please confirm the settings are correct and save to attempt to reconnect.", preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } @@ -63,6 +71,7 @@ class SettingsViewController: FormViewController { connectStep = 1 let queue = DispatchQueue(label: "io.robbie.homeassistant", attributes: []) queue.async { () -> Void in + // swiftlint:disable:next line_length NSLog("Attempting to discover Home Assistant instances, also publishing app to Bonjour/mDNS to hopefully have HA load the iOS/ZeroConf components.") self.discovery.stopDiscovery() self.discovery.stopPublish() @@ -71,15 +80,32 @@ class SettingsViewController: FormViewController { self.discovery.startPublish() } - NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.HomeAssistantDiscovered(_:)), name:NSNotification.Name(rawValue: "homeassistant.discovered"), object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.HomeAssistantUndiscovered(_:)), name:NSNotification.Name(rawValue: "homeassistant.undiscovered"), object: nil) + NotificationCenter.default.addObserver(self, + // swiftlint:disable:next line_length + selector: #selector(SettingsViewController.HomeAssistantDiscovered(_:)), + name:NSNotification.Name(rawValue: "homeassistant.discovered"), + object: nil) + + NotificationCenter.default.addObserver(self, + // swiftlint:disable:next line_length + selector: #selector(SettingsViewController.HomeAssistantUndiscovered(_:)), + name:NSNotification.Name(rawValue: "homeassistant.undiscovered"), + object: nil) } - NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.SSEConnectionChange(_:)), name:NSNotification.Name(rawValue: "sse.opened"), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.SSEConnectionChange(_:)), name:NSNotification.Name(rawValue: "sse.error"), object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(SettingsViewController.SSEConnectionChange(_:)), + name:NSNotification.Name(rawValue: "sse.opened"), + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(SettingsViewController.SSEConnectionChange(_:)), + name:NSNotification.Name(rawValue: "sse.error"), + object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.Connected(_:)), name:NSNotification.Name(rawValue: "connected"), object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(SettingsViewController.Connected(_:)), + name:NSNotification.Name(rawValue: "connected"), + object: nil) form +++ Section(header: "Discovered Home Assistants", footer: "") { @@ -100,7 +126,10 @@ class SettingsViewController: FormViewController { if let url = row.value { let cleanUrl = HomeAssistantAPI.sharedInstance.CleanBaseURL(baseUrl: url) if !cleanUrl.hasValidScheme { - let alert = UIAlertController(title: "Invalid URL", message: "The URL must begin with either http:// or https://.", preferredStyle: UIAlertControllerStyle.alert) + let message = "The URL must begin with either http:// or https://." + let alert = UIAlertController(title: "Invalid URL", + message: message, + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } else { @@ -134,6 +163,7 @@ class SettingsViewController: FormViewController { }.onCellSelection { _, row in if self.connectStep == 1 { if let url = self.baseURL { + // swiftlint:disable:next line_length HomeAssistantAPI.sharedInstance.GetDiscoveryInfo(baseUrl: url).then { discoveryInfo -> Void in let urlRow: URLRow = self.form.rowBy(tag: "baseURL")! urlRow.disabled = true @@ -148,14 +178,19 @@ class SettingsViewController: FormViewController { self.connectStep = 2 }.catch { error in print("Hit error when attempting to get discovery information", error) - let alert = UIAlertController(title: "Error during connection attempt", message: "\(error.localizedDescription)\r\n\r\nPlease try again.", preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) + let alert = UIAlertController(title: "Error during connection attempt", + // swiftlint:disable:next line_length + message: "\(error.localizedDescription)\r\n\r\nPlease try again.", + preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", + style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } } } else if self.connectStep == 2 { firstly { - HomeAssistantAPI.sharedInstance.Setup(baseAPIUrl: self.baseURL!.absoluteString, APIPassword: self.password!) + HomeAssistantAPI.sharedInstance.Setup(baseAPIUrl: self.baseURL!.absoluteString, + APIPassword: self.password!) }.then {_ in HomeAssistantAPI.sharedInstance.Connect() }.then { config -> Void in @@ -187,8 +222,12 @@ class SettingsViewController: FormViewController { let resetSection: Section = self.form.sectionBy(tag: "reset")! resetSection.hidden = false resetSection.evaluateHidden() - let alert = UIAlertController(title: "Connected", message: "Please force quit and re-open the app to continue.", preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) + let alert = UIAlertController(title: "Connected", + // swiftlint:disable:next line_length + message: "Please force quit and re-open the app to continue.", + preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", + style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) }.catch { error in print("Connection error!", error) @@ -198,8 +237,13 @@ class SettingsViewController: FormViewController { errorMessage = "The password was incorrect." } } - let alert = UIAlertController(title: "Error during connection with authentication attempt", message: "\(errorMessage)\r\n\r\nPlease try again.", preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) + // swiftlint:disable:next line_length + let alert = UIAlertController(title: "Error during connection with authentication attempt", + message: "\(errorMessage)\r\n\r\nPlease try again.", + preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", + style: UIAlertActionStyle.default, + handler: nil)) self.present(alert, animated: true, completion: nil) } } @@ -242,13 +286,15 @@ class SettingsViewController: FormViewController { $0.hidden = Condition(booleanLiteral: HomeAssistantAPI.sharedInstance.notificationsEnabled == false) } + // swiftlint:disable:next line_length +++ Section(header: "", footer: "Device ID is the identifier used when sending location updates to Home Assistant, as well as the target to send push notifications to.") <<< TextRow("deviceId") { $0.title = "Device ID" if let deviceId = prefs.string(forKey: "deviceId") { $0.value = deviceId } else { - $0.value = removeSpecialCharsFromString(text: UIDevice.current.name).replacingOccurrences(of: " ", with: "_").lowercased() + let cleanedString = removeSpecialCharsFromString(text: UIDevice.current.name) + $0.value = cleanedString.replacingOccurrences(of: " ", with: "_").lowercased() } $0.cell.textField.autocapitalizationType = .none }.cellUpdate { _, row in @@ -257,7 +303,7 @@ class SettingsViewController: FormViewController { self.prefs.synchronize() } } - +++ Section() { + +++ Section { $0.tag = "details" $0.hidden = Condition(booleanLiteral: !self.configured) } @@ -279,7 +325,8 @@ class SettingsViewController: FormViewController { let pscope = PermissionScope() pscope.addPermission(LocationAlwaysPermission(), - message: "We use this to inform\r\nHome Assistant of your device location and state.") + // swiftlint:disable:next line_length + message: "We use this to inform\r\nHome Assistant of your device location and state.") pscope.show({finished, results in if finished { print("Location Permissions finished!", finished, results) @@ -291,7 +338,8 @@ class SettingsViewController: FormViewController { let locationSettingsRow: ButtonRow = self.form.rowBy(tag: "locationSettings")! locationSettingsRow.hidden = false locationSettingsRow.evaluateHidden() - let deviceTrackerComponentLoadedRow: LabelRow = self.form.rowBy(tag: "deviceTrackerComponentLoaded")! + let deviceTrackerComponentLoadedRow: LabelRow = self.form.rowBy( + tag: "deviceTrackerComponentLoaded")! deviceTrackerComponentLoadedRow.hidden = false deviceTrackerComponentLoadedRow.evaluateHidden() } @@ -344,7 +392,8 @@ class SettingsViewController: FormViewController { $0.title = "Notification Settings" $0.hidden = Condition(booleanLiteral: !HomeAssistantAPI.sharedInstance.notificationsEnabled) $0.presentationMode = .show(controllerProvider: ControllerProvider.callback { - print("HomeAssistantAPI.sharedInstance.notificationsEnabled", HomeAssistantAPI.sharedInstance.notificationsEnabled) + print("HomeAssistantAPI.sharedInstance.notificationsEnabled", + HomeAssistantAPI.sharedInstance.notificationsEnabled) let view = SettingsDetailViewController() view.detailGroup = "notifications" return view @@ -353,7 +402,7 @@ class SettingsViewController: FormViewController { }) } - +++ Section() { + +++ Section { $0.tag = "reset" $0.hidden = Condition(booleanLiteral: !self.configured) } @@ -362,7 +411,10 @@ class SettingsViewController: FormViewController { }.cellUpdate { cell, _ in cell.textLabel?.textColor = .red }.onCellSelection { _, _ in - let alert = UIAlertController(title: "Reset", message: "Your settings will be reset and this device will be unregistered from push notifications as well as removed from your Home Assistant configuration.", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Reset", + // swiftlint:disable:next line_length + message: "Your settings will be reset and this device will be unregistered from push notifications as well as removed from your Home Assistant configuration.", + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in print("Handle Cancel Logic here") @@ -389,6 +441,7 @@ class SettingsViewController: FormViewController { if let userInfo = (notification as Notification).userInfo as? [String:Any] { let discoveryInfo = DiscoveryInfoResponse(JSON: userInfo)! let needsPass = discoveryInfo.RequiresPassword ? " - Requires password" : "" + // swiftlint:disable:next line_length let detailTextLabel = "\(discoveryInfo.BaseURL!.host!):\(discoveryInfo.BaseURL!.port!) - \(discoveryInfo.Version) - \(discoveryInfo.BaseURL!.scheme!.uppercased()) \(needsPass)" if self.form.rowBy(tag: discoveryInfo.LocationName) == nil { discoverySection @@ -422,11 +475,12 @@ class SettingsViewController: FormViewController { func HomeAssistantUndiscovered(_ notification: Notification) { if let userInfo = (notification as Notification).userInfo { - let name = userInfo["name"] as! String - if let removingRow: ButtonRow = self.form.rowBy(tag: name) { - removingRow.hidden = true - removingRow.evaluateHidden() - removingRow.updateCell() + if let stringedName = userInfo["name"] as? String { + if let removingRow: ButtonRow = self.form.rowBy(tag: stringedName) { + removingRow.hidden = true + removingRow.evaluateHidden() + removingRow.updateCell() + } } } let discoverySection: Section = self.form.sectionBy(tag: "discoveredInstances")! @@ -476,7 +530,10 @@ class SettingsViewController: FormViewController { func checkForEmail() { if prefs.bool(forKey: "emailSet") == false || prefs.string(forKey: "userEmail") == nil { print("This is first launch, let's prompt user for email.") - let alert = UIAlertController(title: "Welcome", message: "Please enter the email address you used to sign up for the beta program with.", preferredStyle: UIAlertControllerStyle.alert) + let alert = UIAlertController(title: "Welcome", + // swiftlint:disable:next line_length + message: "Please enter the email address you used to sign up for the beta program with.", + preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: emailEntered)) alert.addTextField(configurationHandler: {(textField: UITextField!) in textField.placeholder = "myawesomeemail@gmail.com" @@ -487,49 +544,6 @@ class SettingsViewController: FormViewController { } } - func saveSettings() { - if let urlRow: URLRow = self.form.rowBy(tag: "baseURL") { - if let url = urlRow.value { - self.prefs.setValue(url.absoluteString, forKey: "baseURL") - } - } - if let apiPasswordRow: PasswordRow = self.form.rowBy(tag: "apiPassword") { - if let password = apiPasswordRow.value { - self.prefs.setValue(password, forKey: "apiPassword") - } - } - if let deviceIdRow: TextRow = self.form.rowBy(tag: "deviceId") { - if let deviceId = deviceIdRow.value { - self.prefs.setValue(deviceId, forKey: "deviceId") - } - } - if let allowAllGroupsRow: SwitchRow = self.form.rowBy(tag: "allowAllGroups") { - if let allowAllGroups = allowAllGroupsRow.value { - self.prefs.set(allowAllGroups, forKey: "allowAllGroups") - } - } - - self.prefs.synchronize() - - let pscope = PermissionScope() - - pscope.addPermission(LocationAlwaysPermission(), - message: "We use this to inform\r\nHome Assistant of your device presence.") - pscope.addPermission(NotificationsPermission(), - message: "We use this to let you\r\nsend notifications to your device.") - pscope.show({finished, results in - if finished { - print("Permissions finished, resetting API!", results) - self.dismiss(animated: true, completion: nil) - (UIApplication.shared.delegate as! AppDelegate).initAPI() - } - }, cancelled: { (results) -> Void in - print("Permissions finished, resetting API!") - self.dismiss(animated: true, completion: nil) - (UIApplication.shared.delegate as! AppDelegate).initAPI() - }) - } - func ResetApp() { let bundleId = Bundle.main.bundleIdentifier! UserDefaults.standard.removePersistentDomain(forName: bundleId) diff --git a/NotificationContentExtension/MjpegStreamingController.swift b/NotificationContentExtension/MjpegStreamingController.swift index 458961844..62208b5fe 100644 --- a/NotificationContentExtension/MjpegStreamingController.swift +++ b/NotificationContentExtension/MjpegStreamingController.swift @@ -22,19 +22,23 @@ open class MjpegStreamingController: NSObject, URLSessionDataDelegate { fileprivate var session: Foundation.URLSession! fileprivate var status: Status = .stopped - open var authenticationHandler: ((URLAuthenticationChallenge) -> (Foundation.URLSession.AuthChallengeDisposition, URLCredential?))? + open var authenticationHandler: ((URLAuthenticationChallenge) -> (Foundation.URLSession.AuthChallengeDisposition, + URLCredential?))? open var didStartLoading: (() -> Void)? open var didFinishLoading: (() -> Void)? open var contentURL: URL? open var imageView: UIImageView - public init(imageView: UIImageView, sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default) { + public init(imageView: UIImageView, + sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default) { self.imageView = imageView super.init() self.session = Foundation.URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil) } - public convenience init(imageView: UIImageView, contentURL: URL, sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default) { + public convenience init(imageView: UIImageView, + contentURL: URL, + sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default) { self.init(imageView: imageView, sessionConfiguration: sessionConfiguration) self.contentURL = contentURL } @@ -72,7 +76,10 @@ open class MjpegStreamingController: NSObject, URLSessionDataDelegate { // MARK: - NSURLSessionDataDelegate - open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + open func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + didReceive response: URLResponse, + completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { if let imageData = receivedData, imageData.length > 0, let receivedImage = UIImage(data: imageData as Data) { // I'm creating the UIImage before performing didFinishLoading to minimize the interval @@ -95,7 +102,10 @@ open class MjpegStreamingController: NSObject, URLSessionDataDelegate { // MARK: - NSURLSessionTaskDelegate - open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { var credential: URLCredential? var disposition: Foundation.URLSession.AuthChallengeDisposition = .performDefaultHandling diff --git a/NotificationContentExtension/NotificationViewController.swift b/NotificationContentExtension/NotificationViewController.swift index 7a5238315..290fcd5f9 100644 --- a/NotificationContentExtension/NotificationViewController.swift +++ b/NotificationContentExtension/NotificationViewController.swift @@ -39,7 +39,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi hud.detailsLabel.text = "Loading \(notification.request.content.categoryIdentifier)..." hud.offset = CGPoint(x: 0, y: -MBProgressMaxOffset+50) self.hud = hud - switch (notification.request.content.categoryIdentifier) { + switch notification.request.content.categoryIdentifier { case "map": mapHandler(notification) case "camera": @@ -50,47 +50,48 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi } func mapHandler(_ notification: UNNotification) { - let haDict = notification.request.content.userInfo["homeassistant"] as! [String:Any] - guard let latitudeString = haDict["latitude"] as? String else { return } - guard let longitudeString = haDict["longitude"] as? String else { return } - let latitude = Double.init(latitudeString)! as CLLocationDegrees - let longitude = Double.init(longitudeString)! as CLLocationDegrees + if let haDict = notification.request.content.userInfo["homeassistant"] as? [String:Any] { + guard let latitudeString = haDict["latitude"] as? String else { return } + guard let longitudeString = haDict["longitude"] as? String else { return } + let latitude = Double.init(latitudeString)! as CLLocationDegrees + let longitude = Double.init(longitudeString)! as CLLocationDegrees - let mapCoordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude) - let span = MKCoordinateSpanMake(0.1, 0.1) + let mapCoordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude) + let span = MKCoordinateSpanMake(0.1, 0.1) - let options = MKMapSnapshotOptions() - options.mapType = .standard - options.showsPointsOfInterest = false - options.showsBuildings = false - options.region = MKCoordinateRegion(center: mapCoordinate, span: span) - options.size = self.view.frame.size - options.scale = self.view.contentScaleFactor + let options = MKMapSnapshotOptions() + options.mapType = .standard + options.showsPointsOfInterest = false + options.showsBuildings = false + options.region = MKCoordinateRegion(center: mapCoordinate, span: span) + options.size = self.view.frame.size + options.scale = self.view.contentScaleFactor - let snapshotter = MKMapSnapshotter(options: options) - snapshotter.start() { snapshot, _ in + let snapshotter = MKMapSnapshotter(options: options) + snapshotter.start { snapshot, _ in - let image = snapshot!.image + let image = snapshot!.image - let pin = MKPinAnnotationView(annotation: nil, reuseIdentifier: "") - let pinImage = pin.image + let pin = MKPinAnnotationView(annotation: nil, reuseIdentifier: "") + let pinImage = pin.image - UIGraphicsBeginImageContextWithOptions(image.size, true, image.scale) + UIGraphicsBeginImageContextWithOptions(image.size, true, image.scale) - image.draw(at: CGPoint(x: 0, y: 0)) + image.draw(at: CGPoint(x: 0, y: 0)) - let homePoint = snapshot?.point(for: mapCoordinate) - pinImage?.draw(at: homePoint!) + let homePoint = snapshot?.point(for: mapCoordinate) + pinImage?.draw(at: homePoint!) - let finalImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() + let finalImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() - let imageView = UIImageView(image: finalImage) - imageView.frame = self.view.frame - imageView.contentMode = .scaleAspectFit - self.view.addSubview(imageView) - self.hud!.hide(animated: true) - self.preferredContentSize = CGSize(width: 0, height: imageView.frame.maxY) + let imageView = UIImageView(image: finalImage) + imageView.frame = self.view.frame + imageView.contentMode = .scaleAspectFit + self.view.addSubview(imageView) + self.hud!.hide(animated: true) + self.preferredContentSize = CGSize(width: 0, height: imageView.frame.maxY) + } } } @@ -102,7 +103,9 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi imageView.frame = self.view.frame imageView.contentMode = .scaleAspectFit - let streamingController = MjpegStreamingController(imageView: imageView, contentURL: cameraProxyURL, sessionConfiguration: urlConfiguration) + let streamingController = MjpegStreamingController(imageView: imageView, + contentURL: cameraProxyURL, + sessionConfiguration: urlConfiguration) streamingController.didFinishLoading = { _ in print("Finished loading") self.hud!.hide(animated: true)