IAQ legend, basic AQI options.

This commit is contained in:
Garth Vander Houwen 2024-04-26 15:02:49 -07:00
parent 3aa924c954
commit 52dd086e09
6 changed files with 242 additions and 75 deletions

View file

@ -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 = "<group>"; };
DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = "<group>"; };
DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = "<group>"; };
DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndexCompact.swift; sourceTree = "<group>"; };
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndex.swift; sourceTree = "<group>"; };
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = "<group>"; };
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
@ -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 */,

View file

@ -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
}

View file

@ -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 {

View file

@ -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))
}
}

View file

@ -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)
}
}
}

View file

@ -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()
}
}
}