Commit 04edf1ff by Dmitriy Stepanets

Merge branch 'feature/air-quality'

# Conflicts:
#	1Weather.xcworkspace/xcuserdata/dstepanets.xcuserdatad/UserInterfaceState.xcuserstate
parents 5ddfebfc ad821a19
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
CD3F6E6925FA59D4002DB99B /* ForecastDetailPeriodButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3F6E6825FA59D4002DB99B /* ForecastDetailPeriodButton.swift */; }; CD3F6E6925FA59D4002DB99B /* ForecastDetailPeriodButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3F6E6825FA59D4002DB99B /* ForecastDetailPeriodButton.swift */; };
CD3F6E6C25FA5A90002DB99B /* PeriodButtonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3F6E6B25FA5A90002DB99B /* PeriodButtonProtocol.swift */; }; CD3F6E6C25FA5A90002DB99B /* PeriodButtonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3F6E6B25FA5A90002DB99B /* PeriodButtonProtocol.swift */; };
CD4742D0261200500061AC95 /* TodayAlertCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4742CF261200500061AC95 /* TodayAlertCell.swift */; }; CD4742D0261200500061AC95 /* TodayAlertCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4742CF261200500061AC95 /* TodayAlertCell.swift */; };
CD55E0BB2615EE2400CC4DC7 /* PollutantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD55E0BA2615EE2400CC4DC7 /* PollutantView.swift */; };
CD593BC226088A5900C93428 /* TimePeriodOffsetHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD593BC126088A5900C93428 /* TimePeriodOffsetHolder.swift */; }; CD593BC226088A5900C93428 /* TimePeriodOffsetHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD593BC126088A5900C93428 /* TimePeriodOffsetHolder.swift */; };
CD593BC926089FC100C93428 /* UITableView+HeaderSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD593BC826089FC100C93428 /* UITableView+HeaderSize.swift */; }; CD593BC926089FC100C93428 /* UITableView+HeaderSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD593BC826089FC100C93428 /* UITableView+HeaderSize.swift */; };
CD593BCC2608A4F200C93428 /* ForecastDailyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD593BCB2608A4F200C93428 /* ForecastDailyCell.swift */; }; CD593BCC2608A4F200C93428 /* ForecastDailyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD593BCB2608A4F200C93428 /* ForecastDailyCell.swift */; };
...@@ -117,6 +118,10 @@ ...@@ -117,6 +118,10 @@
CDE2BF252609D9140085C930 /* ForecastWindButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE2BF242609D9140085C930 /* ForecastWindButton.swift */; }; CDE2BF252609D9140085C930 /* ForecastWindButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE2BF242609D9140085C930 /* ForecastWindButton.swift */; };
CDEE8AD725DA882200C289DE /* ForecastPeriodButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEE8AD625DA882200C289DE /* ForecastPeriodButton.swift */; }; CDEE8AD725DA882200C289DE /* ForecastPeriodButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEE8AD625DA882200C289DE /* ForecastPeriodButton.swift */; };
CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF9BF8D26133D050037847D /* LocationSearchCoordinator.swift */; }; CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF9BF8D26133D050037847D /* LocationSearchCoordinator.swift */; };
CE28474F26159857006C8DC5 /* HealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28474E26159857006C8DC5 /* HealthSource.swift */; };
CE28475226159A32006C8DC5 /* BlendHealthCenterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475126159A32006C8DC5 /* BlendHealthCenterModels.swift */; };
CE28475D2615A5B3006C8DC5 /* Health.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475C2615A5B3006C8DC5 /* Health.swift */; };
CE2847602615A8AD006C8DC5 /* BlendHealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE28475F2615A8AD006C8DC5 /* BlendHealthSource.swift */; };
CE578FD325F7E89400E8B85D /* DayTimeWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */; }; CE578FD325F7E89400E8B85D /* DayTimeWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */; };
CE578FE525FB415F00E8B85D /* CityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE225FB415F00E8B85D /* CityCell.swift */; }; CE578FE525FB415F00E8B85D /* CityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE225FB415F00E8B85D /* CityCell.swift */; };
CE578FE625FB415F00E8B85D /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE325FB415F00E8B85D /* LocationViewController.swift */; }; CE578FE625FB415F00E8B85D /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE578FE325FB415F00E8B85D /* LocationViewController.swift */; };
...@@ -211,6 +216,7 @@ ...@@ -211,6 +216,7 @@
CD3F6E6825FA59D4002DB99B /* ForecastDetailPeriodButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDetailPeriodButton.swift; sourceTree = "<group>"; }; CD3F6E6825FA59D4002DB99B /* ForecastDetailPeriodButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDetailPeriodButton.swift; sourceTree = "<group>"; };
CD3F6E6B25FA5A90002DB99B /* PeriodButtonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeriodButtonProtocol.swift; sourceTree = "<group>"; }; CD3F6E6B25FA5A90002DB99B /* PeriodButtonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeriodButtonProtocol.swift; sourceTree = "<group>"; };
CD4742CF261200500061AC95 /* TodayAlertCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayAlertCell.swift; sourceTree = "<group>"; }; CD4742CF261200500061AC95 /* TodayAlertCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayAlertCell.swift; sourceTree = "<group>"; };
CD55E0BA2615EE2400CC4DC7 /* PollutantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollutantView.swift; sourceTree = "<group>"; };
CD593BC126088A5900C93428 /* TimePeriodOffsetHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriodOffsetHolder.swift; sourceTree = "<group>"; }; CD593BC126088A5900C93428 /* TimePeriodOffsetHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriodOffsetHolder.swift; sourceTree = "<group>"; };
CD593BC826089FC100C93428 /* UITableView+HeaderSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+HeaderSize.swift"; sourceTree = "<group>"; }; CD593BC826089FC100C93428 /* UITableView+HeaderSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+HeaderSize.swift"; sourceTree = "<group>"; };
CD593BCB2608A4F200C93428 /* ForecastDailyCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDailyCell.swift; sourceTree = "<group>"; }; CD593BCB2608A4F200C93428 /* ForecastDailyCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDailyCell.swift; sourceTree = "<group>"; };
...@@ -269,6 +275,10 @@ ...@@ -269,6 +275,10 @@
CDE2BF242609D9140085C930 /* ForecastWindButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastWindButton.swift; sourceTree = "<group>"; }; CDE2BF242609D9140085C930 /* ForecastWindButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastWindButton.swift; sourceTree = "<group>"; };
CDEE8AD625DA882200C289DE /* ForecastPeriodButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastPeriodButton.swift; sourceTree = "<group>"; }; CDEE8AD625DA882200C289DE /* ForecastPeriodButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastPeriodButton.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>"; };
CE28474E26159857006C8DC5 /* HealthSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthSource.swift; sourceTree = "<group>"; };
CE28475126159A32006C8DC5 /* BlendHealthCenterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlendHealthCenterModels.swift; sourceTree = "<group>"; };
CE28475C2615A5B3006C8DC5 /* Health.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Health.swift; sourceTree = "<group>"; };
CE28475F2615A8AD006C8DC5 /* BlendHealthSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlendHealthSource.swift; sourceTree = "<group>"; };
CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayTimeWeather.swift; sourceTree = "<group>"; }; CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayTimeWeather.swift; sourceTree = "<group>"; };
CE578FE225FB415F00E8B85D /* CityCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; }; CE578FE225FB415F00E8B85D /* CityCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; };
CE578FE325FB415F00E8B85D /* LocationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = "<group>"; }; CE578FE325FB415F00E8B85D /* LocationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = "<group>"; };
...@@ -669,6 +679,7 @@ ...@@ -669,6 +679,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CDC6125625E7AB1A00188DA7 /* TodayAirQualityCell.swift */, CDC6125625E7AB1A00188DA7 /* TodayAirQualityCell.swift */,
CD55E0BA2615EE2400CC4DC7 /* PollutantView.swift */,
); );
path = TodayAirQualityCell; path = TodayAirQualityCell;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -737,6 +748,43 @@ ...@@ -737,6 +748,43 @@
path = Forecast; path = Forecast;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CE28474C261597EB006C8DC5 /* Health */ = {
isa = PBXGroup;
children = (
CE28474D261597F1006C8DC5 /* Model */,
CE28474E26159857006C8DC5 /* HealthSource.swift */,
CE28475F2615A8AD006C8DC5 /* BlendHealthSource.swift */,
);
path = Health;
sourceTree = "<group>";
};
CE28474D261597F1006C8DC5 /* Model */ = {
isa = PBXGroup;
children = (
CE28475126159A32006C8DC5 /* BlendHealthCenterModels.swift */,
);
path = Model;
sourceTree = "<group>";
};
CE28475726159B78006C8DC5 /* Weather */ = {
isa = PBXGroup;
children = (
CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */,
CEAFF08B25DFC6BC00DF4EBF /* DailyWeather.swift */,
CEAFF08E25DFC6ED00DF4EBF /* HourlyWeather.swift */,
CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */,
);
path = Weather;
sourceTree = "<group>";
};
CE28475926159B85006C8DC5 /* Health */ = {
isa = PBXGroup;
children = (
CE28475C2615A5B3006C8DC5 /* Health.swift */,
);
path = Health;
sourceTree = "<group>";
};
CE578FE025FB415E00E8B85D /* Locations */ = { CE578FE025FB415E00E8B85D /* Locations */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -783,6 +831,7 @@ ...@@ -783,6 +831,7 @@
CEAFF09925DFC78200DF4EBF /* Network */ = { CEAFF09925DFC78200DF4EBF /* Network */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE28474C261597EB006C8DC5 /* Health */,
87C1724825FF94F400DA3464 /* ConfigManager.swift */, 87C1724825FF94F400DA3464 /* ConfigManager.swift */,
87C171F325FF7A4000DA3464 /* PopularCitiesManager.swift */, 87C171F325FF7A4000DA3464 /* PopularCitiesManager.swift */,
87C171F125FF7A3300DA3464 /* Weather */, 87C171F125FF7A3300DA3464 /* Weather */,
...@@ -807,10 +856,8 @@ ...@@ -807,10 +856,8 @@
children = ( children = (
87C1720C25FF870600DA3464 /* GeoNamesPlace.swift */, 87C1720C25FF870600DA3464 /* GeoNamesPlace.swift */,
CEAFF08225DFC67F00DF4EBF /* Location.swift */, CEAFF08225DFC67F00DF4EBF /* Location.swift */,
CEAFF08825DFC6B200DF4EBF /* CurrentWeather.swift */, CE28475726159B78006C8DC5 /* Weather */,
CEAFF08B25DFC6BC00DF4EBF /* DailyWeather.swift */, CE28475926159B85006C8DC5 /* Health */,
CEAFF08E25DFC6ED00DF4EBF /* HourlyWeather.swift */,
CE578FD225F7E89400E8B85D /* DayTimeWeather.swift */,
); );
path = ModelObjects; path = ModelObjects;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -1022,10 +1069,13 @@ ...@@ -1022,10 +1069,13 @@
CEAFF08925DFC6B200DF4EBF /* CurrentWeather.swift in Sources */, CEAFF08925DFC6B200DF4EBF /* CurrentWeather.swift in Sources */,
CD866A72260F6A5300E96A5C /* SettingsDetailsCell.swift in Sources */, CD866A72260F6A5300E96A5C /* SettingsDetailsCell.swift in Sources */,
CEC5270325E7BB4000DA58A5 /* WdtSurfaceObservation.swift in Sources */, CEC5270325E7BB4000DA58A5 /* WdtSurfaceObservation.swift in Sources */,
CE28474F26159857006C8DC5 /* HealthSource.swift in Sources */,
CEAFF08C25DFC6BD00DF4EBF /* DailyWeather.swift in Sources */, CEAFF08C25DFC6BD00DF4EBF /* DailyWeather.swift in Sources */,
CEDE4F0B25EFA3A7007457E9 /* UpdatableModelObject.swift in Sources */, CEDE4F0B25EFA3A7007457E9 /* UpdatableModelObject.swift in Sources */,
CE28475226159A32006C8DC5 /* BlendHealthCenterModels.swift in Sources */,
87C171EE25FF79CC00DA3464 /* AdConfigManager.swift in Sources */, 87C171EE25FF79CC00DA3464 /* AdConfigManager.swift in Sources */,
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */, CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */,
CE28475D2615A5B3006C8DC5 /* Health.swift in Sources */,
CEF9599F2601DF3300975FAA /* AdLogger.swift in Sources */, CEF9599F2601DF3300975FAA /* AdLogger.swift in Sources */,
CDC6124F25E7964700188DA7 /* TodayDayTimesCell.swift in Sources */, CDC6124F25E7964700188DA7 /* TodayDayTimesCell.swift in Sources */,
CD593BC226088A5900C93428 /* TimePeriodOffsetHolder.swift in Sources */, CD593BC226088A5900C93428 /* TimePeriodOffsetHolder.swift in Sources */,
...@@ -1056,6 +1106,7 @@ ...@@ -1056,6 +1106,7 @@
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */, CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */, CDA5542825EF734200A2E08C /* TodayCellFactory.swift in Sources */,
CEF959742600C3A400975FAA /* FlurryAnalyticsService.swift in Sources */, CEF959742600C3A400975FAA /* FlurryAnalyticsService.swift in Sources */,
CE2847602615A8AD006C8DC5 /* BlendHealthSource.swift in Sources */,
CD86C22225F0DCCB00F38A16 /* PrecipitationView.swift in Sources */, CD86C22225F0DCCB00F38A16 /* PrecipitationView.swift in Sources */,
CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */, CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */,
CD822FF525D6817000A05501 /* TodayForecastCell.swift in Sources */, CD822FF525D6817000A05501 /* TodayForecastCell.swift in Sources */,
...@@ -1106,6 +1157,7 @@ ...@@ -1106,6 +1157,7 @@
CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */, CDF9BF8E26133D050037847D /* LocationSearchCoordinator.swift in Sources */,
CD86246125E662BC0097F3FB /* SunUvLineView.swift in Sources */, CD86246125E662BC0097F3FB /* SunUvLineView.swift in Sources */,
CD32CE0B260C744A00235081 /* MenuCoordinator.swift in Sources */, CD32CE0B260C744A00235081 /* MenuCoordinator.swift in Sources */,
CD55E0BB2615EE2400CC4DC7 /* PollutantView.swift in Sources */,
CEC526FA25E7959A00DA58A5 /* WeatherSource.swift in Sources */, CEC526FA25E7959A00DA58A5 /* WeatherSource.swift in Sources */,
CD37D3DE260C9E37002669D6 /* MenuCell.swift in Sources */, CD37D3DE260C9E37002669D6 /* MenuCell.swift in Sources */,
CD822FFE25D6976F00A05501 /* TodayAdCell.swift in Sources */, CD822FFE25D6976F00A05501 /* TodayAdCell.swift in Sources */,
......
...@@ -32,4 +32,25 @@ extension UIColor { ...@@ -32,4 +32,25 @@ extension UIColor {
} }
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(255 * alpha) / 255) self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(255 * alpha) / 255)
} }
public func toHex(alpha: Bool = false) -> String? {
guard let components = cgColor.components, components.count >= 3 else {
return nil
}
let r = Float(components[0])
let g = Float(components[1])
let b = Float(components[2])
var a = Float(1.0)
if components.count >= 4 {
a = Float(components[3])
}
if alpha {
return String(format: "%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255))
} else {
return String(format: "%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255))
}
}
} }
//
// Health.swift
// 1Weather
//
// Created by Demid Merzlyakov on 01.04.2021.
//
import Foundation
import UIKit
public struct Health: Equatable, Hashable {
public let lastUpdateTime: Date
public let airQuality: AirQuality?
public let pollutants: [String: Pollutant]
}
public struct AirQuality: Equatable, Hashable {
public let index: Double
public let advice: String //TODO: support for localization
public var status: HealthStatus {
get {
HealthStatus(value: index)
}
}
}
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 init(value: Double) {
switch value {
case 0...50:
self = .good
case 51...100:
self = .moderate
case 101...150:
self = .unhealthyForSensitiveGroups
case 151...200:
self = .unhealthy
case 201...300:
self = .veryUnhealthy
default:
self = .hazardous
}
}
public var localized: String {
return self.rawValue.localized()
}
public var gradientColorStart: UIColor {
get {
#warning("Not implemented!")
//TODO: Implement!
return UIColor.green
}
}
public var gradientColorEnd: UIColor {
get {
#warning("Not implemented!")
//TODO: Implement!
return UIColor.red
}
}
}
public struct Pollutant: Equatable, Hashable {
public let name: String
public let value: Double
public var status: HealthStatus {
get {
HealthStatus(value: value)
}
}
}
...@@ -59,6 +59,7 @@ public struct Location { ...@@ -59,6 +59,7 @@ public struct Location {
} }
} }
public private (set) var dayTimeForecast: [DayTimeWeather] = [DayTimeWeather]() public private (set) var dayTimeForecast: [DayTimeWeather] = [DayTimeWeather]()
public var health: Health?
// MARK: - Derived fields // MARK: - Derived fields
public var cityId: String { public var cityId: String {
...@@ -118,6 +119,7 @@ extension Location: UpdatableModelObject { ...@@ -118,6 +119,7 @@ extension Location: UpdatableModelObject {
result.today = result.today?.mergedWith(incrementalChanges: incrementalChanges.today) ?? incrementalChanges.today result.today = result.today?.mergedWith(incrementalChanges: incrementalChanges.today) ?? incrementalChanges.today
result.daily = result.daily.mergedWith(incrementalChanges: incrementalChanges.daily) result.daily = result.daily.mergedWith(incrementalChanges: incrementalChanges.daily)
result.hourly = result.hourly.mergedWith(incrementalChanges: incrementalChanges.hourly) result.hourly = result.hourly.mergedWith(incrementalChanges: incrementalChanges.hourly)
result.health = incrementalChanges.health ?? result.health
return result return result
} }
} }
......
//
// BlendHealthSource.swift
// 1Weather
//
// Created by Demid Merzlyakov on 01.04.2021.
//
import Foundation
public enum BlendHealthError: Error {
case insufficientLocationInfo
case badUrl
case networkError(Error?)
case badServerResponse(Error?)
case dataEncodingError(String)
case alreadyBeingUpdated
}
public class BlendHealthSource {
}
//
// HealthSource.swift
// 1Weather
//
// Created by Demid Merzlyakov on 01.04.2021.
//
import Foundation
public typealias HealthSourceCompletion = (Health?, Error?) -> ()
public protocol HealthSource {
var healthUpdateInterval: TimeInterval { get }
func updateHelath(for location: Location, completion: @escaping HealthSourceCompletion)
}
//
// BlendHealthCenterModels.swift
// 1Weather
//
// Created by Demid Merzlyakov on 01.04.2021.
//
import Foundation
import UIKit
// MARK: - HealthCenter
struct BlendHealthCenter: Codable {
public let s2CellID: String
public let updatedOn: Date
public let airQuality: BlendAirQuality
public let fire: BlendFire
public let pollutants, pollen: [BlendPoll]
enum CodingKeys: String, CodingKey {
case s2CellID = "s2_cell_id"
case updatedOn = "updated_on"
case airQuality = "air_quality"
case fire, pollutants, pollen
}
}
// MARK: - AirQuality
struct BlendAirQuality: Codable {
public let aqi: Double
public let airQualityDescription: String
public let healthAdvice: BlendHealthAdvice
public let color: BlendColor
public let imageURL: String
enum CodingKeys: String, CodingKey {
case aqi
case airQualityDescription = "description"
case healthAdvice = "health_advice"
case color = "color_code"
case imageURL = "image_url"
}
}
// MARK: - HealthAdvice
struct BlendHealthAdvice: Codable {
public let general, sensitive, active: String
}
// MARK: - Fire
struct BlendFire: Codable {
public let windSpeed: Double
public let windDirection, siUnit, fireDescription: String
enum CodingKeys: String, CodingKey {
case windSpeed = "wind_speed"
case windDirection = "wind_direction"
case siUnit = "si_unit"
case fireDescription = "description"
}
}
// MARK: - Poll
struct BlendPoll: Codable {
public let name: String
public let value: Double?
public let siUnit: String
public let status, colorCode: String?
enum CodingKeys: String, CodingKey {
case name, value
case siUnit = "si_unit"
case status
case colorCode = "color_code"
}
}
struct BlendColor: Codable {
public let uiColor: UIColor
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hexString = try container.decode(String.self)
self.uiColor = UIColor(hexString: hexString)
}
func encode(to encoder: Encoder) throws {
guard let hexRepresentation = uiColor.toHex() else {
throw BlendHealthError.dataEncodingError("BlendColor to HEX conversion failed for color \(uiColor)")
}
var container = encoder.singleValueContainer()
try container.encode("#\(hexRepresentation)")
}
}
...@@ -141,3 +141,11 @@ ...@@ -141,3 +141,11 @@
"settings.language" = "Language"; "settings.language" = "Language";
"settings.manageNotifications" = "Manage Notifications"; "settings.manageNotifications" = "Manage Notifications";
"settings.locationAccess" = "Locations Access"; "settings.locationAccess" = "Locations Access";
// Health
"health.airquality.status.good" = "Good";
"health.airquality.status.moderate" = "Moderate";
"health.airquality.status.unhealthyForSensitiveGroups" = "Unhealthy For Sensitive Groups";
"health.airquality.status.unhealthy" = "Unhealthy";
"health.airquality.status.veryUnhealthy" = "Very Unhealthy";
"health.airquality.status.hazardous" = "Hazardous";
//
// PollutionView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 01.04.2021.
//
import UIKit
class PollutionView: UIView {
//Private
private let typeLabel = UILabel()
private let valueLabel = UILabel()
private let statusLabel = UILabel()
private let progressContainer = UIView()
private let progressGradient = CAGradientLayer()
init() {
super.init(frame: .zero)
prepareLabels()
prepareProgress()
}
func configure(pollutant: Pollutant) {
typeLabel.text = pollutant.name.localized
valueLabel.text = "\(pollutant.value)"
statusLabel.text = pollutant.status.localized
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MRAK:- Prepare
private extension PollutionView {
func prepareLabels() {
typeLabel.font = AppFont.SFPro.bold(size: 18)
addSubview(typeLabel)
valueLabel.font = AppFont.SFPro.bold(size: 18)
addSubview(valueLabel)
statusLabel.textAlignment = .right
statusLabel.font = AppFont.SFPro.bold(size: 14)
addSubview(statusLabel)
//Constraints
typeLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.top.equalToSuperview().inset(15)
}
valueLabel.snp.makeConstraints { (make) in
make.left.equalTo(typeLabel.snp.right).offset(30)
make.centerY.equalTo(typeLabel)
}
statusLabel.snp.makeConstraints { (make) in
make.right.equalToSuperview()
make.centerY.equalTo(typeLabel)
}
}
func prepareProgress() {
progressContainer.layer.cornerRadius = 2
progressContainer.backgroundColor = UIColor(hex: 0xe9ebfc)
addSubview(progressContainer)
progressContainer.snp.makeConstraints { (make) in
make.height.equalTo(4)
make.left.right.equalToSuperview()
make.top.equalTo(typeLabel.snp.bottom).offset(18)
make.bottom.equalToSuperview().inset(15)
}
}
}
//
// PollutantView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 01.04.2021.
//
import UIKit
class PollutantView: UIView {
//Private
private let typeLabel = UILabel()
private let valueLabel = UILabel()
private let statusLabel = UILabel()
private let progressContainer = UIView()
private let progressGradient = CAGradientLayer()
init() {
super.init(frame: .zero)
prepareLabels()
prepareProgress()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
func configure(pollutant: Pollutant) {
typeLabel.text = pollutant.name.localized
valueLabel.text = "\(Int(pollutant.value))"
statusLabel.text = pollutant.status.localized
}
private func updateUI() {
switch interfaceStyle {
case .light:
typeLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
valueLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
statusLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
typeLabel.textColor = ThemeManager.currentTheme.primaryTextColor
valueLabel.textColor = ThemeManager.currentTheme.primaryTextColor
statusLabel.textColor = ThemeManager.currentTheme.primaryTextColor
}
}
}
//MRAK:- Prepare
private extension PollutantView {
func prepareLabels() {
typeLabel.font = AppFont.SFPro.bold(size: 18)
typeLabel.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
addSubview(typeLabel)
valueLabel.font = AppFont.SFPro.bold(size: 18)
addSubview(valueLabel)
statusLabel.lineBreakMode = .byWordWrapping
statusLabel.numberOfLines = 2
statusLabel.textAlignment = .right
statusLabel.font = AppFont.SFPro.bold(size: 14)
addSubview(statusLabel)
//Constraints
typeLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.top.equalToSuperview().inset(15)
}
valueLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(100)
make.centerY.equalTo(typeLabel)
}
}
func prepareProgress() {
progressContainer.layer.cornerRadius = 2
progressContainer.backgroundColor = UIColor(hex: 0xe9ebfc)
addSubview(progressContainer)
progressContainer.snp.makeConstraints { (make) in
make.height.equalTo(4)
make.left.right.equalToSuperview()
make.top.equalTo(typeLabel.snp.bottom).offset(18)
make.bottom.equalToSuperview().inset(15)
}
statusLabel.snp.makeConstraints { (make) in
make.right.equalToSuperview()
make.centerY.equalTo(typeLabel)
make.width.equalTo(progressContainer).multipliedBy(0.4)
}
}
}
...@@ -11,7 +11,6 @@ class TodayAirQualityCell: UITableViewCell { ...@@ -11,7 +11,6 @@ class TodayAirQualityCell: UITableViewCell {
//Private //Private
private let headingLabel = UILabel() private let headingLabel = UILabel()
private let valueCircle = CAShapeLayer() private let valueCircle = CAShapeLayer()
private let valueProgressGradient = CAGradientLayer()
private let airQualityValueLabel = UILabel() private let airQualityValueLabel = UILabel()
private let airQualityLabel = UILabel() private let airQualityLabel = UILabel()
private let airDescLabel = UILabel() private let airDescLabel = UILabel()
...@@ -24,8 +23,7 @@ class TodayAirQualityCell: UITableViewCell { ...@@ -24,8 +23,7 @@ class TodayAirQualityCell: UITableViewCell {
prepareHeading() prepareHeading()
prepareAirLabels() prepareAirLabels()
prepareValueProgress() prepareValueProgress()
prepareStackView()
setAirQuality(value: 48)
} }
override func layoutSubviews() { override func layoutSubviews() {
...@@ -36,33 +34,16 @@ class TodayAirQualityCell: UITableViewCell { ...@@ -36,33 +34,16 @@ class TodayAirQualityCell: UITableViewCell {
startAngle: 0, startAngle: 0,
endAngle: 2 * .pi, endAngle: 2 * .pi,
clockwise: false).cgPath clockwise: false).cgPath
let mask = CAShapeLayer()
mask.lineWidth = 6
mask.lineCap = .round
mask.strokeColor = UIColor.red.cgColor
mask.fillColor = UIColor.clear.cgColor
mask.path = UIBezierPath(arcCenter: airQualityValueLabel.center,
radius: 36,
startAngle: -.pi/2,
endAngle: 0,
clockwise: true).cgPath
valueProgressGradient.frame = .init(x: 0,
y: 0,
width: 100,
height: 100)
// valueProgressGradient.mask = valueProgressShape
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
private func setAirQuality(value:CGFloat) { public func configure(health: Health) {
airQualityValueLabel.text = "\(Int(value))" airQualityValueLabel.text = "\(Int(health.airQuality?.index ?? 0))"
let aqiText = "air.quality.is".localized() let aqiText = "air.quality.is".localized()
let aqiConditionText = "air.quality.good".localized().uppercased() let aqiConditionText = health.airQuality?.status.localized ?? ""
let attrString = NSMutableAttributedString(string: "\(aqiText)\n\(aqiConditionText)", let attrString = NSMutableAttributedString(string: "\(aqiText)\n\(aqiConditionText)",
attributes: [.font : AppFont.SFPro.regular(size: 24), attributes: [.font : AppFont.SFPro.regular(size: 24),
.foregroundColor :ThemeManager.currentTheme.secondaryTextColor]) .foregroundColor :ThemeManager.currentTheme.secondaryTextColor])
...@@ -70,6 +51,15 @@ class TodayAirQualityCell: UITableViewCell { ...@@ -70,6 +51,15 @@ class TodayAirQualityCell: UITableViewCell {
range: NSRange(location: aqiText.count + 1, range: NSRange(location: aqiText.count + 1,
length: aqiConditionText.count)) length: aqiConditionText.count))
airQualityLabel.attributedText = attrString airQualityLabel.attributedText = attrString
//Fill pollutions
stackView.removeAll()
health.pollutants.map{$1}.forEach {
let pollutionView = PollutantView()
pollutionView.configure(pollutant: $0)
stackView.addArrangedSubview(pollutionView)
}
stackView.layoutIfNeeded()
} }
} }
...@@ -93,6 +83,8 @@ private extension TodayAirQualityCell { ...@@ -93,6 +83,8 @@ private extension TodayAirQualityCell {
} }
func prepareAirLabels() { func prepareAirLabels() {
airQualityLabel.lineBreakMode = .byWordWrapping
airQualityLabel.numberOfLines = 0
airQualityValueLabel.font = AppFont.SFPro.bold(size: 18) airQualityValueLabel.font = AppFont.SFPro.bold(size: 18)
airQualityValueLabel.textColor = ThemeManager.currentTheme.secondaryTextColor airQualityValueLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
contentView.addSubview(airQualityValueLabel) contentView.addSubview(airQualityValueLabel)
...@@ -113,7 +105,7 @@ private extension TodayAirQualityCell { ...@@ -113,7 +105,7 @@ private extension TodayAirQualityCell {
//Constraints //Constraints
airQualityValueLabel.snp.makeConstraints { (make) in airQualityValueLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(72) make.left.equalToSuperview().inset(72)
make.top.equalTo(headingLabel.snp.bottom).offset(72) make.top.equalTo(headingLabel.snp.bottom).offset(72).priority(999)
} }
airQualityLabel.snp.makeConstraints { (make) in airQualityLabel.snp.makeConstraints { (make) in
...@@ -124,7 +116,6 @@ private extension TodayAirQualityCell { ...@@ -124,7 +116,6 @@ private extension TodayAirQualityCell {
airDescLabel.snp.makeConstraints { (make) in airDescLabel.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(50) make.left.right.equalToSuperview().inset(50)
make.top.equalTo(airQualityLabel.snp.bottom).offset(9) make.top.equalTo(airQualityLabel.snp.bottom).offset(9)
make.bottom.equalToSuperview().inset(30)
} }
} }
...@@ -133,11 +124,18 @@ private extension TodayAirQualityCell { ...@@ -133,11 +124,18 @@ private extension TodayAirQualityCell {
valueCircle.fillColor = UIColor.clear.cgColor valueCircle.fillColor = UIColor.clear.cgColor
valueCircle.lineWidth = 2 valueCircle.lineWidth = 2
contentView.layer.addSublayer(valueCircle) contentView.layer.addSublayer(valueCircle)
}
valueProgressGradient.type = .radial func prepareStackView() {
valueProgressGradient.startPoint = .init(x: 0.5, y: 0.5) stackView.axis = .vertical
valueProgressGradient.endPoint = .init(x: 0, y: 0) stackView.distribution = .fillProportionally
valueProgressGradient.colors = [UIColor.red.cgColor, UIColor.yellow.cgColor] stackView.clipsToBounds = false
contentView.layer.addSublayer(valueProgressGradient) contentView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(50)
make.top.equalTo(airDescLabel.snp.bottom).offset(15)
make.bottom.equalToSuperview().inset(15)
}
} }
} }
...@@ -14,6 +14,7 @@ private enum TodayCellType:Int { ...@@ -14,6 +14,7 @@ private enum TodayCellType:Int {
case conditions case conditions
case forecastPeriod case forecastPeriod
case precipitation case precipitation
case airQuality
case dayTime case dayTime
case sun case sun
case moon case moon
...@@ -38,7 +39,13 @@ class TodayCellFactory: CellFactoryProtocol { ...@@ -38,7 +39,13 @@ class TodayCellFactory: CellFactoryProtocol {
private let todayViewModel:TodayViewModel private let todayViewModel:TodayViewModel
private var todaySection = TodaySection(rows: [.alert, .forecast, .ad, private var todaySection = TodaySection(rows: [.alert, .forecast, .ad,
.conditions, .forecastPeriod, .precipitation, .conditions, .forecastPeriod, .precipitation,
.dayTime, .sun, .moon]) .airQuality, .dayTime, .sun, .moon])
private let health = Health(lastUpdateTime: Date(),
airQuality: .init(index: 48, advice: "some"),
pollutants: ["pm25" : .init(name: "PM 2.5", value: 48),
"pm10" : .init(name: "PM 10", value: 42),
"no2" : .init(name: "NO2", value: 74),
"so2" : .init(name: "SO2", value: 135)])
//Public //Public
init(viewModel: TodayViewModel) { init(viewModel: TodayViewModel) {
...@@ -103,6 +110,10 @@ class TodayCellFactory: CellFactoryProtocol { ...@@ -103,6 +110,10 @@ class TodayCellFactory: CellFactoryProtocol {
cellsToUpdate.remove(.precipitation) cellsToUpdate.remove(.precipitation)
} }
return cell return cell
case .airQuality:
let cell = dequeueReusableCell(type: TodayAirQualityCell.self, tableView: tableView, indexPath: indexPath)
cell.configure(health: self.health)
return cell
case .dayTime: case .dayTime:
let cell = dequeueReusableCell(type: TodayDayTimesCell.self, tableView: tableView, indexPath: indexPath) let cell = dequeueReusableCell(type: TodayDayTimesCell.self, tableView: tableView, indexPath: indexPath)
if cellsToUpdate.contains(.dayTime) { if cellsToUpdate.contains(.dayTime) {
......
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