Skip to content

Commit

Permalink
Sensors in Settings (#640)
Browse files Browse the repository at this point in the history
- Shows the device sensors and information about them, but doesn't let you turn off individual sensors yet.
- Only refreshes on appearance or when pulling to refresh the list of sensors.

Refs #358.
  • Loading branch information
zacwest committed Jun 15, 2020
1 parent a35c493 commit f6b803f
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 0 deletions.
16 changes: 16 additions & 0 deletions HomeAssistant.xcodeproj/project.pbxproj
Expand Up @@ -17,6 +17,8 @@
11B7FD742493225200E60ED9 /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B7FD732493225200E60ED9 /* BackgroundTask.swift */; };
11B7FD752493225200E60ED9 /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B7FD732493225200E60ED9 /* BackgroundTask.swift */; };
11B7FD772493232400E60ED9 /* BackgroundTask.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B7FD762493232400E60ED9 /* BackgroundTask.test.swift */; };
11F3D74C2495377B00C05BBA /* SensorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F3D74B2495377B00C05BBA /* SensorListViewController.swift */; };
11F3D7512495434C00C05BBA /* SensorDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F3D7502495434C00C05BBA /* SensorDetailViewController.swift */; };
36D6C02C95DA3AA629947952 /* Pods_Shared_watchOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76519B1977F02AC5D1309705 /* Pods_Shared_watchOS.framework */; };
3A9474676287819D0F5A8A37 /* Pods-TodayWidget-metadata.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4F3D32AC650F6D7A0F3C3D08 /* Pods-TodayWidget-metadata.plist */; };
474AA46EC65ABB974EFC74A4 /* Pods-APNSAttachmentService-metadata.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B8A292E4944AD7D9EBC2B14 /* Pods-APNSAttachmentService-metadata.plist */; };
Expand Down Expand Up @@ -719,6 +721,8 @@
119D765E2492F8FA00183C5F /* UIApplication+BackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+BackgroundTask.swift"; sourceTree = "<group>"; };
11B7FD732493225200E60ED9 /* BackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTask.swift; sourceTree = "<group>"; };
11B7FD762493232400E60ED9 /* BackgroundTask.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTask.test.swift; sourceTree = "<group>"; };
11F3D74B2495377B00C05BBA /* SensorListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorListViewController.swift; sourceTree = "<group>"; };
11F3D7502495434C00C05BBA /* SensorDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorDetailViewController.swift; sourceTree = "<group>"; };
1C5332DAFA7CC79107B5429E /* Pods-Shared-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Shared-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Shared-iOS/Pods-Shared-iOS.release.xcconfig"; sourceTree = "<group>"; };
1E42E3B8B9E9B8F7FC915A94 /* Pods-TodayWidget.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TodayWidget.release.xcconfig"; path = "Pods/Target Support Files/Pods-TodayWidget/Pods-TodayWidget.release.xcconfig"; sourceTree = "<group>"; };
22DFD473C941D5E380068120 /* Pods-Shared-iOS-SharedTests.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Shared-iOS-SharedTests.beta.xcconfig"; path = "Pods/Target Support Files/Pods-Shared-iOS-SharedTests/Pods-Shared-iOS-SharedTests.beta.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1438,6 +1442,15 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
11F3D74F2495433800C05BBA /* Sensors */ = {
isa = PBXGroup;
children = (
11F3D74B2495377B00C05BBA /* SensorListViewController.swift */,
11F3D7502495434C00C05BBA /* SensorDetailViewController.swift */,
);
path = Sensors;
sourceTree = "<group>";
};
29278BB24639BA945D3D86B4 /* Frameworks */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1819,6 +1832,7 @@
B661FB6B226BCC8500E541DD /* Settings */ = {
isa = PBXGroup;
children = (
11F3D74F2495433800C05BBA /* Sensors */,
B661FB6E226BCCAD00E541DD /* ConnectionSettingsViewController.swift */,
B65C0B512282BA13007E057B /* NotificationSettingsViewController.swift */,
);
Expand Down Expand Up @@ -3980,13 +3994,15 @@
B6022213226DAC9D00E8DBFE /* ScaledFont.swift in Sources */,
B68EDD03215F0E2900DD6B28 /* NotificationCategoryConfigurator.swift in Sources */,
D0FF79D220D87D200034574D /* ClientEventTableViewController.swift in Sources */,
11F3D74C2495377B00C05BBA /* SensorListViewController.swift in Sources */,
B6617EED1CFE79AD004DEE6D /* NSURL+QueryDictionary.swift in Sources */,
B6DAC737215F06B100727D2A /* NotificationAction.swift in Sources */,
B626AAF11D8F972800A0D225 /* SettingsDetailViewController.swift in Sources */,
B658AA782250AC8000C9BFE3 /* String+HA.swift in Sources */,
B6022223226DBA3800E8DBFE /* OnboardingNavigationViewController.swift in Sources */,
B616B29A227ED69300828165 /* InternetAddress.swift in Sources */,
B6DF8BC1221C890600370A59 /* UIImageView+UIActivityIndicator.swift in Sources */,
11F3D7512495434C00C05BBA /* SensorDetailViewController.swift in Sources */,
D0EEF324214DF2B700D1D360 /* Utils.swift in Sources */,
B641BC251E20A17B002CCBC1 /* OpenInChromeController.swift in Sources */,
B661FB6A226BBDA900E541DD /* SettingsViewController.swift in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions HomeAssistant/Resources/en.lproj/Localizable.strings
Expand Up @@ -135,6 +135,7 @@
"location_change_notification.unknown.body" = "Location updated via unknown method";
"cancel_label" = "Cancel";
"username_label" = "Username";
"retry_label" = "Retry";
"client_events.event_type.service_call" = "Service Call";
"client_events.event_type.notification" = "Notification";
"client_events.event_type.location_update" = "Location Update";
Expand Down Expand Up @@ -748,3 +749,10 @@ Thank you.\
"settings_details.general.restoration.title" = "Remember Last Page";
"settings_details.location.new_one_shot.title" = "In-Development Updating";
"settings_details.location.new_one_shot.description" = "This may or may not deliver good results for the sources above. Your feedback is appreciated.";
"settings_sensors.title" = "Sensors";
"settings_sensors.loading_error.title" = "Failed to load sensors";
"settings_sensors.detail.unique_id" = "Unique ID";
"settings_sensors.detail.state" = "State";
"settings_sensors.detail.attributes" = "Attributes";
"settings_sensors.detail.device_class" = "Device Class";
"settings_sensors.detail.icon" = "Icon";
@@ -0,0 +1,70 @@
import Foundation
import Shared
import Eureka
import UIKit

class SensorDetailViewController: FormViewController {
private(set) var sensor: WebhookSensor

init(sensor: WebhookSensor) {
self.sensor = sensor
super.init(style: .grouped)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
updateModels()
}

private func updateModels() {
title = sensor.Name

form.removeAll()

let baseSection = Section()
baseSection <<< LabelRow {
$0.title = L10n.SettingsSensors.Detail.uniqueId
$0.value = sensor.UniqueID
}
baseSection <<< LabelRow {
$0.title = L10n.SettingsSensors.Detail.state
$0.value = sensor.StateDescription
}

if let deviceClass = sensor.DeviceClass {
baseSection <<< LabelRow {
$0.title = L10n.SettingsSensors.Detail.deviceClass
$0.value = deviceClass.rawValue
}
}

if let icon = sensor.Icon {
baseSection <<< LabelRow {
$0.title = L10n.SettingsSensors.Detail.icon
$0.value = icon
}
}

form +++ baseSection

if let attributes = sensor.Attributes {
let attributesSection = Section(header: L10n.SettingsSensors.Detail.attributes, footer: nil)
let attributeRows = attributes
.sorted(by: { lhs, rhs in lhs.0 < rhs.0 })
.map(Self.row(attribute:value:))
attributesSection.append(contentsOf: attributeRows)
form.append(attributesSection)
}
}

class func row(attribute: String, value: Any) -> BaseRow {
return LabelRow { row in
row.title = attribute
row.value = String(describing: value)
}
}
}
@@ -0,0 +1,91 @@
import Foundation
import Eureka
import Shared
import Iconic
import PromiseKit

class SensorListViewController: FormViewController {
private let sensorSection = Section()
private let refreshControl = UIRefreshControl()
private let sensors = WebhookSensors()

override func viewDidLoad() {
super.viewDidLoad()

title = L10n.SettingsSensors.title

updateSensors(section: sensorSection)
form +++ sensorSection

tableView.addSubview(refreshControl)
refreshControl.addTarget(self, action: #selector(refresh), for: .primaryActionTriggered)
}

@objc private func refresh() {
updateSensors(section: sensorSection)
}

private func updateSensors(section: Section) {
guard !refreshControl.isRefreshing else { return }

refreshControl.beginRefreshing()

firstly {
sensors.AllSensors
}.map {
$0.map { Self.row(for: $0) }
}.done {
section.removeAll()
section.append(contentsOf: $0)
}.ensure { [refreshControl] in
refreshControl.endRefreshing()
}.catch { error in
let alert = UIAlertController(
title: L10n.SettingsSensors.LoadingError.title,
message: error.localizedDescription,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: L10n.retryLabel, style: .default, handler: { [weak self] _ in
self?.refresh()
}))
alert.addAction(UIAlertAction(title: L10n.cancelLabel, style: .cancel, handler: { _ in

}))
self.present(alert, animated: true, completion: nil)
}
}

class func row(for sensor: WebhookSensor) -> BaseRow {
return ButtonRow { row in
func updateDetails(from sensor: WebhookSensor, isInitial: Bool) {
row.title = sensor.Name
row.value = sensor.StateDescription
row.cellStyle = .value1

row.cellUpdate { cell, _ in
cell.detailTextLabel?.text = row.value

cell.imageView?.image =
sensor.Icon
.flatMap(MaterialDesignIcons.init(named:))?
.image(ofSize: CGSize(width: 28, height: 28), color: .black)
.withRenderingMode(.alwaysTemplate)
}

if !isInitial {
row.updateCell()
}
}

updateDetails(from: sensor, isInitial: true)

row.presentationMode = .show(controllerProvider: .callback(builder: {
SensorDetailViewController(sensor: sensor)
}), onDismiss: {
if let controller = $0 as? SensorDetailViewController {
updateDetails(from: controller.sensor, isInitial: false)
}
})
}
}
}
7 changes: 7 additions & 0 deletions HomeAssistant/Views/SettingsViewController.swift
Expand Up @@ -103,6 +103,13 @@ class SettingsViewController: FormViewController {
}, onDismiss: nil)
}

<<< ButtonRow {
$0.title = L10n.SettingsSensors.title
$0.presentationMode = .show(controllerProvider: .callback {
SensorListViewController()
}, onDismiss: nil)
}

+++ Section(L10n.Settings.DetailsSection.Integrations.header)
<<< ButtonRow {
$0.tag = "actions"
Expand Down
8 changes: 8 additions & 0 deletions Shared/API/Models/WebhookSensor.swift
Expand Up @@ -67,6 +67,14 @@ public class WebhookSensor: Mappable {
UnitOfMeasurement <- map["unit_of_measurement"]
}
}

public var StateDescription: String? {
if let value = State {
return String(describing: value) + (UnitOfMeasurement ?? "")
} else {
return nil
}
}
}

public enum DeviceClass: String, CaseIterable {
Expand Down
2 changes: 2 additions & 0 deletions Shared/API/Webhook/WebhookSensors.swift
Expand Up @@ -20,6 +20,8 @@ import Reachability

// swiftlint:disable:next type_body_length
public class WebhookSensors {
public init() { }

public var AllSensors: Promise<[WebhookSensor]> {
return firstly {
when(fulfilled: self.Activity, self.Pedometer)
Expand Down
23 changes: 23 additions & 0 deletions Shared/Resources/Swiftgen/Strings.swift
Expand Up @@ -29,6 +29,8 @@ internal enum L10n {
internal static let onLabel = L10n.tr("Localizable", "on_label")
/// Preview Output
internal static let previewOutput = L10n.tr("Localizable", "preview_output")
/// Retry
internal static let retryLabel = L10n.tr("Localizable", "retry_label")
/// Success
internal static let successLabel = L10n.tr("Localizable", "success_label")
/// Username
Expand Down Expand Up @@ -1672,6 +1674,27 @@ internal enum L10n {
}
}

internal enum SettingsSensors {
/// Sensors
internal static let title = L10n.tr("Localizable", "settings_sensors.title")
internal enum Detail {
/// Attributes
internal static let attributes = L10n.tr("Localizable", "settings_sensors.detail.attributes")
/// Device Class
internal static let deviceClass = L10n.tr("Localizable", "settings_sensors.detail.device_class")
/// Icon
internal static let icon = L10n.tr("Localizable", "settings_sensors.detail.icon")
/// State
internal static let state = L10n.tr("Localizable", "settings_sensors.detail.state")
/// Unique ID
internal static let uniqueId = L10n.tr("Localizable", "settings_sensors.detail.unique_id")
}
internal enum LoadingError {
/// Failed to load sensors
internal static let title = L10n.tr("Localizable", "settings_sensors.loading_error.title")
}
}

internal enum SiriShortcuts {
internal enum Configurator {
internal enum Fields {
Expand Down

0 comments on commit f6b803f

Please sign in to comment.