From 52dd086e09b7b7e6d1e5e693e90e6db38fe98501 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 26 Apr 2024 15:02:49 -0700 Subject: [PATCH] IAQ legend, basic AQI options. --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Enums/TelemetryEnums.swift | 67 +++++++- Meshtastic/Extensions/Color.swift | 3 + .../Helpers/Weather/AirQualityIndex.swift | 146 ++++++++++++++++++ .../Weather/AirQualityIndexCompact.swift | 67 -------- .../Views/Helpers/Weather/IAQScale.swift | 26 +++- 6 files changed, 242 insertions(+), 75 deletions(-) create mode 100644 Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift delete mode 100644 Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 50002bf1..e54c834f 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -88,7 +88,7 @@ DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */; }; DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5200298EE33B00D21B61 /* apponly.pb.swift */; }; DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */; }; - DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */; }; + DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */; }; DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; @@ -341,7 +341,7 @@ DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = remote_hardware.pb.swift; sourceTree = ""; }; DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = ""; }; DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = ""; }; - DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndexCompact.swift; sourceTree = ""; }; + DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndex.swift; sourceTree = ""; }; DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; @@ -651,7 +651,7 @@ DD5E523D298F5A7D00D21B61 /* Weather */ = { isa = PBXGroup; children = ( - DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */, + DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */, DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */, DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */, DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */, @@ -1207,7 +1207,7 @@ DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, DD0E20FD2B87090400F2D100 /* clientonly.pb.swift in Sources */, D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */, - DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */, + DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */, diff --git a/Meshtastic/Enums/TelemetryEnums.swift b/Meshtastic/Enums/TelemetryEnums.swift index 1189660c..7aa548ff 100644 --- a/Meshtastic/Enums/TelemetryEnums.swift +++ b/Meshtastic/Enums/TelemetryEnums.swift @@ -8,6 +8,69 @@ import Foundation import SwiftUI +enum Aqi: Int, CaseIterable, Identifiable { + case good = 0 + case moderate = 1 + case sensitive = 2 + case unhealthy = 3 + case veryUnhealthy = 4 + case hazardous = 5 + + var id: Int { self.rawValue } + var description: String { + switch self { + case .good: + return "Good" + case .moderate: + return "Moderate" + case .sensitive: + return "Unhealthy for Sensitive Groups" + case .unhealthy: + return "Unhealthy" + case .veryUnhealthy: + return "Very Unhealthy" + case .hazardous: + return "Hazardous" + } + } + var color: Color { + switch self { + case .good: + return .green + case .moderate: + return .yellow + case .sensitive: + return .orange + case .unhealthy: + return .red + case .veryUnhealthy: + return .purple + case .hazardous: + return .magenta + } + } + static func getAqi(for value: Int) -> Aqi { + let aqi: Aqi + switch value { + case 0...50: + aqi = .good + case 51...100: + aqi = .moderate + case 101...150: + aqi = .sensitive + case 151...200: + aqi = .unhealthy + case 201...300: + aqi = .veryUnhealthy + case 301...500: + aqi = .hazardous + default: + fatalError("Invalid int value") + } + return aqi + } +} + enum Iaq: Int, CaseIterable, Identifiable { case excellent = 0 case good = 1 @@ -27,7 +90,7 @@ enum Iaq: Int, CaseIterable, Identifiable { case .lightlyPolluted: return "Lightly Polluted" case .moderatelyPolluted: - return "Lightly Polluted" + return "Moderately Polluted" case .heavilyPolluted: return "Heavily Polluted" case .severelyPolluted: @@ -49,7 +112,7 @@ enum Iaq: Int, CaseIterable, Identifiable { case .heavilyPolluted: return .red case .severelyPolluted: - return .purple + return .magenta case .extremelyPolluted: return .brown } diff --git a/Meshtastic/Extensions/Color.swift b/Meshtastic/Extensions/Color.swift index da47cadc..428e5707 100644 --- a/Meshtastic/Extensions/Color.swift +++ b/Meshtastic/Extensions/Color.swift @@ -17,6 +17,9 @@ extension Color { let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 return (brightness > 0.5) } + public static var magenta: Color { + return Color(UIColor(red: 0.50, green: 0.00, blue: 0.00, alpha: 1.00)) //return Color(UIColor.magenta) + } } extension UIColor { diff --git a/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift b/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift new file mode 100644 index 00000000..160caae5 --- /dev/null +++ b/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift @@ -0,0 +1,146 @@ +// +// AQICircleDisplay.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 2/4/23. +// +import SwiftUI + +enum AqiDisplayMode: Int, CaseIterable, Identifiable { + + case pill = 0 + case dot = 1 + case text = 2 + case gauge = 3 + case gradient = 4 + + var id: Int { self.rawValue } +} + +struct AirQualityIndex: View { + var aqi: Int + var displayMode: IaqDisplayMode = .pill + let gradient = Gradient(colors: [.green, .yellow, .orange, .red, .purple, .magenta]) + + var body: some View { + + let aqiEnum = Aqi.getAqi(for: aqi) + switch displayMode { + case .pill: + ZStack (alignment: .leading) { + RoundedRectangle(cornerRadius: 10) + .fill(aqiEnum.color) + .frame(width: 125, height: 30) + Label("IAQ \(aqi)", systemImage: aqi < 100 ? "aqi.low" : ((aqi > 100 && aqi < 201) ? "aqi.medium" : "aqi.high")) + .padding(.leading, 4) + } + case .dot: + VStack { + HStack { + Text("\(aqi)") + Circle() + .fill(aqiEnum.color) + .frame(width: 10, height: 10) + } + } + case .text: + Text(aqiEnum.description) + .font(.caption) + case .gauge: + Gauge(value: Double(aqi), in: 0...500) { + + Text("IAQ") + .foregroundColor(aqiEnum.color) + } currentValueLabel: { + Text("\(Int(aqi))") + } + .tint(gradient) + .gaugeStyle(.accessoryCircular) + case .gradient: + HStack { + Gauge(value: Double(aqi), in: 0...500) { + Text("IAQ") + .foregroundColor(aqiEnum.color) + } currentValueLabel: { + Text("IAQ ")+Text("\(Int(aqi))") + .foregroundColor(.gray) + } + .tint(gradient) + .gaugeStyle(.accessoryLinear) + Text(aqiEnum.description) + .font(.caption) + } + .padding([.leading, .trailing]) + } + } +} + +struct AirQualityIndex_Previews: PreviewProvider { + static var previews: some View { + VStack { + Text(".pill") + .font(.title2) + HStack { + AirQualityIndex(aqi: 6) + AirQualityIndex(aqi: 51) + } + HStack { + AirQualityIndex(aqi: 101) + AirQualityIndex(aqi: 151) + } + HStack { + AirQualityIndex(aqi: 201) + AirQualityIndex(aqi: 351) + } + Text(".dot") + .font(.title2) + HStack { + AirQualityIndex(aqi: 6, displayMode: .dot) + AirQualityIndex(aqi: 51, displayMode: .dot) + AirQualityIndex(aqi: 101, displayMode: .dot) + AirQualityIndex(aqi: 201, displayMode: .dot) + AirQualityIndex(aqi: 350, displayMode: .dot) + AirQualityIndex(aqi: 351, displayMode: .dot) + } + Text(".text") + .font(.title2) + HStack { + AirQualityIndex(aqi: 6, displayMode: .text) + AirQualityIndex(aqi: 51, displayMode: .text) + AirQualityIndex(aqi: 101, displayMode: .text) + } + HStack { + AirQualityIndex(aqi: 201, displayMode: .text) + AirQualityIndex(aqi: 350, displayMode: .text) + } + Text(".gauge") + .font(.title2) + HStack (alignment: .top) { + AirQualityIndex(aqi: 6, displayMode: .gauge) + AirQualityIndex(aqi: 51, displayMode: .gauge) + AirQualityIndex(aqi: 101, displayMode: .gauge) + AirQualityIndex(aqi: 151, displayMode: .gauge) + } + HStack (alignment: .top) { + AirQualityIndex(aqi: 201, displayMode: .gauge) + AirQualityIndex(aqi: 251, displayMode: .gauge) + AirQualityIndex(aqi: 301, displayMode: .gauge) + AirQualityIndex(aqi: 351, displayMode: .gauge) + } + HStack (alignment: .top) { + AirQualityIndex(aqi: 401, displayMode: .gauge) + AirQualityIndex(aqi: 500, displayMode: .gauge) + } + Text(".gradient") + .font(.title2) + AirQualityIndex(aqi: 6, displayMode: .gradient) + AirQualityIndex(aqi: 51, displayMode: .gradient) + AirQualityIndex(aqi: 101, displayMode: .gradient) + AirQualityIndex(aqi: 201, displayMode: .gradient) + AirQualityIndex(aqi: 351, displayMode: .gradient) + AirQualityIndex(aqi: 401, displayMode: .gradient) + AirQualityIndex(aqi: 500, displayMode: .gradient) + + }.previewLayout(.fixed(width: 300, height: 800)) + } +} diff --git a/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift b/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift deleted file mode 100644 index 642a0596..00000000 --- a/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// AQICircleDisplay.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 2/4/23. -// -import SwiftUI - -struct AirQualityIndexCompact: View { - var aqi: Int - - var body: some View { - - HStack(spacing: 0.5) { - Text("AQI \(aqi)") - .foregroundColor(.gray) - .padding(.trailing, 0) - .font(.caption) - - if aqi > 0 && aqi < 51 { - // Good - Circle() - .fill(.green) - .frame(width: 10, height: 10) - } else if aqi > 50 && aqi < 101 { - // Satisfactory - Circle() - .fill(Color(red: 0, green: 0.9882, blue: 0.1804)) - .frame(width: 10, height: 10) - } else if aqi > 100 && aqi < 201 { - // Moderate - Circle() - .fill(.yellow) - .frame(width: 10, height: 10) - } else if aqi > 200 && aqi < 301 { - // Poor - Circle() - .fill(.orange) - .frame(width: 10, height: 10) - - } else if aqi > 300 && aqi < 401 { - // Very Poor - Circle() - .fill(.red) - .frame(width: 10, height: 10) - } else if aqi >= 401 { - // Very Poor - Circle() - .fill(Color(red: 0.8392, green: 0.0667, blue: 0)) - .frame(width: 10, height: 10) - } - } - } -} -struct AQICircleDisplay_Previews: PreviewProvider { - static var previews: some View { - - VStack { - AirQualityIndexCompact(aqi: 5) - AirQualityIndexCompact(aqi: 51) - AirQualityIndexCompact(aqi: 101) - AirQualityIndexCompact(aqi: 201) - AirQualityIndexCompact(aqi: 301) - AirQualityIndexCompact(aqi: 401) - } - } -} diff --git a/Meshtastic/Views/Helpers/Weather/IAQScale.swift b/Meshtastic/Views/Helpers/Weather/IAQScale.swift index 7fa50902..97ec91e6 100644 --- a/Meshtastic/Views/Helpers/Weather/IAQScale.swift +++ b/Meshtastic/Views/Helpers/Weather/IAQScale.swift @@ -9,10 +9,32 @@ import SwiftUI struct IAQScale: View { - let gradient = Gradient(colors: [.green, .mint, .yellow, .orange, .red, .purple, .purple, .brown, .brown, .brown, .brown]) var body: some View { - ZStack (alignment: .leading) { + VStack(alignment:.leading) { + ForEach(Iaq.allCases) { iaq in + HStack { + RoundedRectangle(cornerRadius: 5) + .fill(iaq.color) + .frame(width: 30, height: 20) + Text(iaq.description) + .font(.callout) + } + } + } + .padding() + .background(.white) + .cornerRadius(20) /// make the background rounded + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(.secondary, lineWidth: 4) + ) + } +} +struct IAQSCalePreviews: PreviewProvider { + static var previews: some View { + VStack { + IAQScale() } } }