Commit 968adf41 by Demid Merzlyakov

Moved all the health-related entities to separate files.

parent 2ecc807f
...@@ -120,6 +120,9 @@ ...@@ -120,6 +120,9 @@
CDF4808F261727E00076E9F5 /* CLAuthorizationStatus+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF4808E261727E00076E9F5 /* CLAuthorizationStatus+Localized.swift */; }; CDF4808F261727E00076E9F5 /* CLAuthorizationStatus+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF4808E261727E00076E9F5 /* CLAuthorizationStatus+Localized.swift */; };
CDF48092261729680076E9F5 /* UIApplication+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF48091261729680076E9F5 /* UIApplication+Settings.swift */; }; CDF48092261729680076E9F5 /* UIApplication+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF48091261729680076E9F5 /* UIApplication+Settings.swift */; };
CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF9BF8D26133D050037847D /* LocationSearchCoordinator.swift */; }; CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF9BF8D26133D050037847D /* LocationSearchCoordinator.swift */; };
CE13B72826245CE2007CBD4D /* AirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13B72726245CE2007CBD4D /* AirQuality.swift */; };
CE13B72B26245D0D007CBD4D /* HealthStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13B72A26245D0D007CBD4D /* HealthStatus.swift */; };
CE13B72E26245D42007CBD4D /* Pollutant.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13B72D26245D42007CBD4D /* Pollutant.swift */; };
CE28474F26159857006C8DC5 /* HealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28474E26159857006C8DC5 /* HealthSource.swift */; }; CE28474F26159857006C8DC5 /* HealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28474E26159857006C8DC5 /* HealthSource.swift */; };
CE28475226159A32006C8DC5 /* BlendHealthModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475126159A32006C8DC5 /* BlendHealthModels.swift */; }; CE28475226159A32006C8DC5 /* BlendHealthModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475126159A32006C8DC5 /* BlendHealthModels.swift */; };
CE28475D2615A5B3006C8DC5 /* Health.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475C2615A5B3006C8DC5 /* Health.swift */; }; CE28475D2615A5B3006C8DC5 /* Health.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475C2615A5B3006C8DC5 /* Health.swift */; };
...@@ -304,6 +307,9 @@ ...@@ -304,6 +307,9 @@
CDF4808E261727E00076E9F5 /* CLAuthorizationStatus+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLAuthorizationStatus+Localized.swift"; sourceTree = "<group>"; }; CDF4808E261727E00076E9F5 /* CLAuthorizationStatus+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLAuthorizationStatus+Localized.swift"; sourceTree = "<group>"; };
CDF48091261729680076E9F5 /* UIApplication+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Settings.swift"; sourceTree = "<group>"; }; CDF48091261729680076E9F5 /* UIApplication+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Settings.swift"; sourceTree = "<group>"; };
CDF9BF8D26133D050037847D /* LocationSearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSearchCoordinator.swift; sourceTree = "<group>"; }; CDF9BF8D26133D050037847D /* LocationSearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSearchCoordinator.swift; sourceTree = "<group>"; };
CE13B72726245CE2007CBD4D /* AirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQuality.swift; sourceTree = "<group>"; };
CE13B72A26245D0D007CBD4D /* HealthStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthStatus.swift; sourceTree = "<group>"; };
CE13B72D26245D42007CBD4D /* Pollutant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pollutant.swift; sourceTree = "<group>"; };
CE28474E26159857006C8DC5 /* HealthSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthSource.swift; sourceTree = "<group>"; }; CE28474E26159857006C8DC5 /* HealthSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthSource.swift; sourceTree = "<group>"; };
CE28475126159A32006C8DC5 /* BlendHealthModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlendHealthModels.swift; sourceTree = "<group>"; }; CE28475126159A32006C8DC5 /* BlendHealthModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlendHealthModels.swift; sourceTree = "<group>"; };
CE28475C2615A5B3006C8DC5 /* Health.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Health.swift; sourceTree = "<group>"; }; CE28475C2615A5B3006C8DC5 /* Health.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Health.swift; sourceTree = "<group>"; };
...@@ -839,6 +845,9 @@ ...@@ -839,6 +845,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE28475C2615A5B3006C8DC5 /* Health.swift */, CE28475C2615A5B3006C8DC5 /* Health.swift */,
CE13B72726245CE2007CBD4D /* AirQuality.swift */,
CE13B72A26245D0D007CBD4D /* HealthStatus.swift */,
CE13B72D26245D42007CBD4D /* Pollutant.swift */,
); );
path = Health; path = Health;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -1182,6 +1191,7 @@ ...@@ -1182,6 +1191,7 @@
CDE2BF222609D4250085C930 /* ForecastWindSpeedCell.swift in Sources */, CDE2BF222609D4250085C930 /* ForecastWindSpeedCell.swift in Sources */,
CD866A6C260F676400E96A5C /* SettingsDetailsCellFactory.swift in Sources */, CD866A6C260F676400E96A5C /* SettingsDetailsCellFactory.swift in Sources */,
CDA5543025EFA13F00A2E08C /* Measurement+String.swift in Sources */, CDA5543025EFA13F00A2E08C /* Measurement+String.swift in Sources */,
CE13B72E26245D42007CBD4D /* Pollutant.swift in Sources */,
CEF9596C2600C32E00975FAA /* AnalyticsEvent.swift in Sources */, CEF9596C2600C32E00975FAA /* AnalyticsEvent.swift in Sources */,
CEF959982600C88100975FAA /* AnalyticsParameter.swift in Sources */, CEF959982600C88100975FAA /* AnalyticsParameter.swift in Sources */,
87C171F425FF7A4000DA3464 /* PopularCitiesManager.swift in Sources */, 87C171F425FF7A4000DA3464 /* PopularCitiesManager.swift in Sources */,
...@@ -1255,6 +1265,7 @@ ...@@ -1255,6 +1265,7 @@
CE8962AC26175DF500CA274A /* CoreCurrentWeather.swift in Sources */, CE8962AC26175DF500CA274A /* CoreCurrentWeather.swift in Sources */,
CE9F01BB261B31A6009BA500 /* CoreDataError.swift in Sources */, CE9F01BB261B31A6009BA500 /* CoreDataError.swift in Sources */,
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */, CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */,
CE13B72826245CE2007CBD4D /* AirQuality.swift in Sources */,
CE8962A526175DF500CA274A /* _CoreHourlyWeather.swift in Sources */, CE8962A526175DF500CA274A /* _CoreHourlyWeather.swift in Sources */,
CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */, CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */,
CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */, CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */,
...@@ -1316,6 +1327,7 @@ ...@@ -1316,6 +1327,7 @@
CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */, CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */,
CEF959932600C63500975FAA /* Analytics.swift in Sources */, CEF959932600C63500975FAA /* Analytics.swift in Sources */,
CEDE4F0F25EFA3B4007457E9 /* UpdatableModelObjectInTime.swift in Sources */, CEDE4F0F25EFA3B4007457E9 /* UpdatableModelObjectInTime.swift in Sources */,
CE13B72B26245D0D007CBD4D /* HealthStatus.swift in Sources */,
CE8962A426175DF500CA274A /* _CorePollutant.swift in Sources */, CE8962A426175DF500CA274A /* _CorePollutant.swift in Sources */,
CD3F6E6925FA59D4002DB99B /* ForecastDetailPeriodButton.swift in Sources */, CD3F6E6925FA59D4002DB99B /* ForecastDetailPeriodButton.swift in Sources */,
CD37D405260DFFDD002669D6 /* CellFactory.swift in Sources */, CD37D405260DFFDD002669D6 /* CellFactory.swift in Sources */,
......
//
// AirQuality.swift
// 1Weather
//
// Created by Demid Merzlyakov on 12.04.2021.
//
import Foundation
import UIKit
public struct AirQuality: Equatable, Hashable {
public let index: Double
public let advice: String //TODO: support for localization
public let progress: CGFloat
public init(index: Double, advice: String) {
self.index = index
self.advice = advice
var progressValue = CGFloat(sqrt(0.1 + (index * 0.9) / 400)) // just to make it look nicer, so that we didn't have too short gradient circle in areas with generally good air.
progressValue = max(0, progressValue)
progressValue = min(1, progressValue)
self.progress = progressValue
}
public var status: HealthStatus {
switch index {
case 0...50: return .good
case 51...100: return .moderate
case 101...150: return .unhealthyForSensitiveGroups
case 151...200: return .unhealthy
case 201...300: return .veryUnhealthy
default: return .hazardous
}
}
}
//
// HealthStatus.swift
// 1Weather
//
// Created by Demid Merzlyakov on 12.04.2021.
//
import Foundation
import UIKit
public enum HealthStatus: String {
case good = "health.airquality.status.good"
case moderate = "health.airquality.status.moderate"
case unhealthyForSensitiveGroups = "health.airquality.status.unhealthyForSensitiveGroups"
case unhealthy = "health.airquality.status.unhealthy"
case veryUnhealthy = "health.airquality.status.veryUnhealthy"
case hazardous = "health.airquality.status.hazardous"
public var localized: String {
return self.rawValue.localized()
}
public var textColor: UIColor {
get {
switch self {
case .good:
guard let result = UIColor(named: "good_text") else {
assertionFailure("Color not available fair quality good_textor !")
return UIColor(red: 8.0 / 255.0, green: 207.0 / 255.0, blue: 0.0 / 97.0, alpha: 1.0)
}
return result
case .moderate:
guard let result = UIColor(named: "moderate_text") else {
assertionFailure("Color not available for air quality moderate_text!")
return UIColor(red: 255.0 / 255.0, green: 191.0 / 255.0, blue: 69.0 / 255.0, alpha: 1.0)
}
return result
case .unhealthyForSensitiveGroups:
guard let result = UIColor(named: "unhealthy_for_sensitive_groups_text") else {
assertionFailure("Color not available for air quality unhealthy_for_sensitive_groups_text!")
return UIColor(red: 255.0 / 255.0, green: 127.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
return result
case .unhealthy:
guard let result = UIColor(named: "unhealthy_text") else {
assertionFailure("Color not available for air quality unhealthy_text!")
return UIColor(red: 237.0 / 255.0, green: 81.0 / 255.0, blue: 81.0 / 255.0, alpha: 1.0)
}
return result
case .veryUnhealthy:
guard let result = UIColor(named: "very_unhealthy_text") else {
assertionFailure("Color not available for air quality very_unhealthy_text!")
return UIColor(red: 143.0 / 255.0, green: 63.0 / 255.0, blue: 151.0 / 255.0, alpha: 1.0)
}
return result
case .hazardous:
guard let result = UIColor(named: "hazardous_text") else {
assertionFailure("Color not available for air quality hazardous_text!")
return UIColor(red: 126.0 / 255.0, green: 0.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
}
return result
}
}
}
public var gradientColorStart: UIColor {
get {
switch self {
case .good:
guard let result = UIColor(named: "good_start") else {
assertionFailure("Color not available fair quality good_startor !")
return UIColor(red: 33.0 / 255.0, green: 238.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
return result
case .moderate:
guard let result = UIColor(named: "moderate_start") else {
assertionFailure("Color not available for air quality moderate_start!")
return UIColor(red: 255.0 / 255.0, green: 110.0 / 255.0, blue: 16.0 / 255.0, alpha: 1.0)
}
return result
case .unhealthyForSensitiveGroups:
guard let result = UIColor(named: "unhealthy_for_sensitive_groups_start") else {
assertionFailure("Color not available for air quality unhealthy_for_sensitive_groups_start!")
return UIColor(red: 255.0 / 255.0, green: 127.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
return result
case .unhealthy:
guard let result = UIColor(named: "unhealthy_start") else {
assertionFailure("Color not available for air quality unhealthy_start!")
return UIColor(red: 237.0 / 255.0, green: 81.0 / 255.0, blue: 81.0 / 255.0, alpha: 1.0)
}
return result
case .veryUnhealthy:
guard let result = UIColor(named: "very_unhealthy_start") else {
assertionFailure("Color not available for air quality very_unhealthy_start!")
return UIColor(red: 143.0 / 255.0, green: 63.0 / 255.0, blue: 151.0 / 255.0, alpha: 1.0)
}
return result
case .hazardous:
guard let result = UIColor(named: "hazardous_start") else {
assertionFailure("Color not available for air quality hazardous_start!")
return UIColor(red: 126.0 / 255.0, green: 0.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
}
return result
}
}
}
public var gradientColorEnd: UIColor {
get {
switch self {
case .good:
guard let result = UIColor(named: "good_end") else {
assertionFailure("Color not availableair quality good_end for !")
return UIColor(red: 8.0 / 255.0, green: 207.0 / 255.0, blue: 97.0 / 255.0, alpha: 1.0)
}
return result
case .moderate:
guard let result = UIColor(named: "moderate_end") else {
assertionFailure("Color not available forair quality moderate_end !")
return UIColor(red: 255.0 / 255.0, green: 238.0 / 255.0, blue: 65.0 / 255.0, alpha: 1.0)
}
return result
case .unhealthyForSensitiveGroups:
guard let result = UIColor(named: "unhealthy_for_sensitive_groups_end") else {
assertionFailure("Color not available for air quality unhealthy_for_sensitive_groups_end!")
return UIColor(red: 223.0 / 255.0, green: 146.0 / 255.0, blue: 70.0 / 255.0, alpha: 1.0)
}
return result
case .unhealthy:
guard let result = UIColor(named: "unhealthy_end") else {
assertionFailure("Color not available for air quality unhealthy_end!")
return UIColor(red: 255.0 / 255.0, green: 163.0 / 255.0, blue: 163.0 / 255.0, alpha: 1.0)
}
return result
case .veryUnhealthy:
guard let result = UIColor(named: "very_unhealthy_end") else {
assertionFailure("Color not available for air quality very_unhealthy_end!")
return UIColor(red: 214.0 / 255.0, green: 136.0 / 255.0, blue: 221.0 / 255.0, alpha: 1.0)
}
return result
case .hazardous:
guard let result = UIColor(named: "hazardous_end") else {
assertionFailure("Color not available for air quality hazardous_end!")
return UIColor(red: 205.0 / 255.0, green: 73.0 / 255.0, blue: 108.0 / 255.0, alpha: 1.0)
}
return result
}
}
}
}
//
// Pollutant.swift
// 1Weather
//
// Created by Demid Merzlyakov on 12.04.2021.
//
import Foundation
import UIKit
public struct Pollutant: Equatable, Hashable {
public let name: String
public let value: Double
public let progress: CGFloat
public init(name: String, value: Double) {
self.name = name
self.value = value
var statusValue = HealthStatus.good
let shortName = name.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: " ", with: "").lowercased()
if let statusLimits: [HealthStatus: Double] = Pollutant.upperLimits[shortName] {
let statusesSequence: [HealthStatus] = [.good, .moderate, .unhealthyForSensitiveGroups, .unhealthy, .veryUnhealthy, .hazardous]
for possibleStatus: HealthStatus in statusesSequence {
if let limit: Double = statusLimits[possibleStatus] {
if value < limit {
statusValue = possibleStatus
break
}
}
else {
assertionFailure("No limit for status \(possibleStatus) of pollutant \(name)") // should never happen.
statusValue = possibleStatus
continue
}
}
if let veryUnhealthyUpperLimit = statusLimits[.veryUnhealthy] {
hazardousLevelStart = veryUnhealthyUpperLimit
}
else {
assertionFailure("No limit for status \(HealthStatus.veryUnhealthy) of pollutant \(name)") // should never happen.
hazardousLevelStart = nil
}
}
else {
assertionFailure("No limits for Pollutant \(name)!")
statusValue = .good
hazardousLevelStart = nil
}
self.status = statusValue
if let hazardousLevelStart = self.hazardousLevelStart {
var progressValue = max(0, CGFloat(value / hazardousLevelStart))
progressValue = min(1, progressValue)
self.progress = progressValue
}
else {
self.progress = 0.1 // not great, not terrible
}
}
public private (set) var status: HealthStatus
private let hazardousLevelStart: Double?
/// Climacell API reference: https://docs.tomorrow.io/reference/data-layers-air
private static let upperLimits: [String: [HealthStatus: Double]] = [
"pm10": [
.good: 27.0,
.moderate: 104.0,
.unhealthyForSensitiveGroups: 204.0,
.unhealthy: 304.0,
.veryUnhealthy: 390.0,
.hazardous: Double.infinity // 515.0
],
"pm2.5": [
.good: 6.0,
.moderate: 24.0,
.unhealthyForSensitiveGroups: 45.0,
.unhealthy: 103.0,
.veryUnhealthy: 200.0,
.hazardous: Double.infinity // 375.0
],
"no2": [
.good: 22.0,
.moderate: 77.0,
.unhealthyForSensitiveGroups: 230.0,
.unhealthy: 505.0,
.veryUnhealthy: 950.0,
.hazardous: Double.infinity // 1650.0
],
"o3": [
.good: 27.0,
.moderate: 62.0,
.unhealthyForSensitiveGroups: 145.0,
.unhealthy: 185.0,
.veryUnhealthy: 305.0,
.hazardous: Double.infinity // 505.0
],
"co": [
.good: 2.2,
.moderate: 7.0,
.unhealthyForSensitiveGroups: 11.0,
.unhealthy: 14.0,
.veryUnhealthy: 23.0,
.hazardous: Double.infinity // 41.0
],
"so2": [
.good: 18.0,
.moderate: 56.0,
.unhealthyForSensitiveGroups: 131.0,
.unhealthy: 245.0,
.veryUnhealthy: 455.0,
.hazardous: Double.infinity // 805.0
]
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment