From 339b243276df2c3b03a19812008d65eaee85b62d Mon Sep 17 00:00:00 2001 From: Andrew Ng Date: Thu, 8 Oct 2020 15:51:21 -0700 Subject: [PATCH] Basic popover UI to change sensor id --- PurpleMenu.xcodeproj/project.pbxproj | 8 ++++++++ PurpleMenu/AppDelegate.swift | 23 +++++++++++++++++++--- PurpleMenu/ContentView.swift | 27 ++++++++++++++++++++++++-- PurpleMenu/SensorViewModel.swift | 21 ++++++++++++++++++++ PurpleMenu/UserDefaultsExtension.swift | 19 ++++++++++++++++++ README.md | 1 - 6 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 PurpleMenu/SensorViewModel.swift create mode 100644 PurpleMenu/UserDefaultsExtension.swift diff --git a/PurpleMenu.xcodeproj/project.pbxproj b/PurpleMenu.xcodeproj/project.pbxproj index 02f9bf7..79b7906 100644 --- a/PurpleMenu.xcodeproj/project.pbxproj +++ b/PurpleMenu.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 3748D197252D505300A758D6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3748D195252D505300A758D6 /* Main.storyboard */; }; 3748D1A0252D50F100A758D6 /* LauncherApplication.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3748D18A252D505200A758D6 /* LauncherApplication.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3748D1A5252D511200A758D6 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3748D1A4252D511200A758D6 /* ServiceManagement.framework */; }; + 37A3C07F252FB74400C2D12C /* SensorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A3C07E252FB74400C2D12C /* SensorViewModel.swift */; }; + 37A3C083252FCCA700C2D12C /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A3C082252FCCA700C2D12C /* UserDefaultsExtension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -56,6 +58,8 @@ 3748D198252D505300A758D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3748D199252D505300A758D6 /* LauncherApplication.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LauncherApplication.entitlements; sourceTree = ""; }; 3748D1A4252D511200A758D6 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; + 37A3C07E252FB74400C2D12C /* SensorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorViewModel.swift; sourceTree = ""; }; + 37A3C082252FCCA700C2D12C /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -109,6 +113,8 @@ 3726C1E0252CF33000B0B216 /* Sensor.swift */, 3726C1E3252CF44600B0B216 /* Result.swift */, 3726C1E6252CF4CE00B0B216 /* PurpleAirApi.swift */, + 37A3C07E252FB74400C2D12C /* SensorViewModel.swift */, + 37A3C082252FCCA700C2D12C /* UserDefaultsExtension.swift */, ); path = PurpleMenu; sourceTree = ""; @@ -256,6 +262,8 @@ 3726C1E1252CF33000B0B216 /* Sensor.swift in Sources */, 3726C1E7252CF4CE00B0B216 /* PurpleAirApi.swift in Sources */, 3726C1E4252CF44600B0B216 /* Result.swift in Sources */, + 37A3C07F252FB74400C2D12C /* SensorViewModel.swift in Sources */, + 37A3C083252FCCA700C2D12C /* UserDefaultsExtension.swift in Sources */, 3726C1CD252CEEA900B0B216 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/PurpleMenu/AppDelegate.swift b/PurpleMenu/AppDelegate.swift index 9a3a300..b9e42eb 100644 --- a/PurpleMenu/AppDelegate.swift +++ b/PurpleMenu/AppDelegate.swift @@ -5,8 +5,10 @@ // Created by Andrew Ng on 10/6/20. // +import Foundation import Cocoa import SwiftUI +import Combine import ServiceManagement extension Notification.Name { @@ -16,12 +18,14 @@ extension Notification.Name { @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { let statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength)) + let sensorViewModel = SensorViewModel() + private var subscriptions: Set = [] var window: NSWindow! lazy var popover: NSPopover = { let contentView = ContentView() let popover = NSPopover() - popover.contentSize = NSSize(width: 400, height: 400) + popover.contentSize = NSSize(width: 300, height: 200) popover.behavior = .transient popover.contentViewController = NSHostingController(rootView: contentView) @@ -29,6 +33,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { }() func applicationDidFinishLaunching(_ aNotification: Notification) { + statusBarItem.button?.action = #selector(togglePopover(_:)) + bindDefaults() refreshAqi() _ = Timer.scheduledTimer(withTimeInterval: 300, repeats: true, block: { (_) in @@ -53,6 +59,17 @@ class AppDelegate: NSObject, NSApplicationDelegate { // MARK: - Private methods + private func bindDefaults() { + UserDefaults.standard + .publisher(for: \.sensorId) + .handleEvents(receiveOutput: { sensorId in + debugPrint("sensorId is now: \(sensorId)") + self.refreshAqi() + }) + .sink { _ in } + .store(in: &subscriptions) + } + @objc func togglePopover(_ sender: AnyObject?) { guard let button = statusBarItem.button else { return } @@ -64,7 +81,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func refreshAqi() { - PurpleAirApi(sensorId: "67533").getData { (sensor) in + PurpleAirApi(sensorId: UserDefaults.standard.sensorId).getData { (sensor) in guard let result = sensor.results?.first, let resultB = sensor.results?.last, let pm25Str = result.pM2_5Value, @@ -77,7 +94,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let humidity = Float(humidityStr) else { return } - debugPrint("pm25 = \(pm25), pm25Cf1 = \(pm25Cf1), RH = \(humidity)") + debugPrint("id = \(self.sensorViewModel.sensorId), pm25 = \(pm25), pm25Cf1 = \(pm25Cf1), RH = \(humidity)") let aqi = self.pmToEPA(paCf1: (pm25Cf1 + pm25Cf1B) * 0.5, humidity: humidity) diff --git a/PurpleMenu/ContentView.swift b/PurpleMenu/ContentView.swift index 233d926..76aa7fb 100644 --- a/PurpleMenu/ContentView.swift +++ b/PurpleMenu/ContentView.swift @@ -8,9 +8,32 @@ import SwiftUI struct ContentView: View { + @ObservedObject var sensor = SensorViewModel() + var body: some View { - Text("Hello, World!") - .frame(maxWidth: .infinity, maxHeight: .infinity) + GeometryReader { g in + VStack { + Spacer() + HStack { + Text("Sensor ID") + .frame(alignment: .trailing) + TextField("Sensor ID", text: $sensor.sensorId, onCommit: { + sensor.update() + }) + .frame(width: 80) + } + .frame(width: g.size.width / 2, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) + HStack { + Button("Update") { + sensor.update() + } + Button("Quit") { + exit(0) + } + } + Spacer() + } + } } } diff --git a/PurpleMenu/SensorViewModel.swift b/PurpleMenu/SensorViewModel.swift new file mode 100644 index 0000000..159286d --- /dev/null +++ b/PurpleMenu/SensorViewModel.swift @@ -0,0 +1,21 @@ +// +// SensorViewModel.swift +// PurpleMenu +// +// Created by Andrew Ng on 10/8/20. +// + +import Foundation +import Combine + +final class SensorViewModel: ObservableObject { + static let userDefaultsKey = "SensorId" + + @Published var sensorId = UserDefaults.standard.sensorId + + func update() { + if !sensorId.isEmpty { + UserDefaults.standard.sensorId = sensorId + } + } +} diff --git a/PurpleMenu/UserDefaultsExtension.swift b/PurpleMenu/UserDefaultsExtension.swift new file mode 100644 index 0000000..79b7d9c --- /dev/null +++ b/PurpleMenu/UserDefaultsExtension.swift @@ -0,0 +1,19 @@ +// +// UserDefaultsExtension.swift +// PurpleMenu +// +// Created by Andrew Ng on 10/8/20. +// + +import Foundation + +extension UserDefaults { + @objc var sensorId: String { + get { + return string(forKey: "sensor_id") ?? "67533" + } + set { + set(newValue, forKey: "sensor_id") + } + } +} diff --git a/README.md b/README.md index 8a8004f..d8e166b 100644 --- a/README.md +++ b/README.md @@ -6,5 +6,4 @@ For now you can change the `sensorId` [here](https://github.com/ayn/PurpleMenu/b ## TODOS * Settings UI - * choose nearby sensor * choose which conversion (or none) to use