Commit 5d1529f8 by Demid Merzlyakov

Working network request parsing.

parent f77af241
......@@ -6,6 +6,7 @@
//
import UIKit
import CoreLocation
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
......@@ -14,6 +15,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ThemeManager.refreshAppearance()
LocationManager.shared.currentLocation = Location(coordinates: CLLocationCoordinate2D(latitude: 40.730610, longitude: -73.935242), timeZone: TimeZone(identifier: "EST")!)
self.window = UIWindow(frame: UIScreen.main.bounds)
let appCoordinator = AppCoordinator(window: self.window!)
......
......@@ -9,7 +9,7 @@ import UIKit
class TodayCoordinator:Coordinator {
//Private
private let todayViewModel = TodayViewModel()
private let todayViewModel = TodayViewModel(locationManager: LocationManager.shared) // TODO: get rid of singleton maybe?
private let navigationController = UINavigationController(nibName: nil, bundle: nil)
private var tabBarController:UITabBarController?
......
......@@ -14,18 +14,47 @@ public protocol LocationManagerDelegate: class {
public class LocationManager {
private let log = Logger(componentName: "LocationManager")
private let delegates = MulticastDelegate<LocationManagerDelegate>()
private let weatherUpdateSource: WeatherSource
public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource())
public init(weatherUpdateSource: WeatherSource) {
self.weatherUpdateSource = weatherUpdateSource
}
public var currentLocation: Location? {
didSet {
if currentLocation != oldValue {
if oldValue?.description != currentLocation?.description {
log.info("Current location changed to: \(currentLocation?.description ?? "nil")")
delegates.invoke { [weak self] (delegate) in
guard let self = self else { return }
delegate.locationManager(self, changedCurrentLocation: currentLocation)
}
log.info("Location updated.")
delegates.invoke { [weak self] (delegate) in
guard let self = self else { return }
delegate.locationManager(self, changedCurrentLocation: currentLocation)
}
}
}
public func updateWeather() {
guard let location = currentLocation else {
log.warning("Update weather: no location.")
return
}
log.info("Update weather for location: \(location)")
weatherUpdateSource.updateWeather(for: location) { [weak self] (updatedLocation, error) in
guard let self = self else { return }
guard let updatedLocation = updatedLocation else {
if let error = error {
self.log.error("Update weather error: \(error)")
}
else {
self.log.error("Update weather error: unknown error")
}
return
}
else {
log.debug("location updated without changing")
DispatchQueue.main.async {
self.currentLocation = updatedLocation
}
}
}
......
......@@ -8,7 +8,7 @@
import Foundation
import CoreLocation
public struct Location: CustomStringConvertible, Equatable {
public struct Location: CustomStringConvertible {
// MARK: - Data fields
public var coordinates: CLLocationCoordinate2D?
public var imageName: String? = "ny_bridge" //we'll possibly need to switch to URL
......@@ -39,6 +39,7 @@ public struct Location: CustomStringConvertible, Equatable {
public var zip: String?
public var fipsCode: String?
public var timeZone: TimeZone
public var today: CurrentWeather?
public var daily: [DailyWeather] = [DailyWeather]()
......@@ -57,20 +58,6 @@ public struct Location: CustomStringConvertible, Equatable {
return "\(cityId) (no coordinates)"
}
}
public static func == (lhs: Location, rhs: Location) -> Bool {
// Should we compare the weather here, too?
// TODO: implement
#warning("Not implemented!")
#if DEBUG
//TODO: REMOVE THIS'
return false
#warning("THIS IS A DEBUG-ONLY PIECE OF CODE! Not even temporary! Remove before making a production build.")
#else
#error("THIS IS A DEBUG-ONLY PIECE OF CODE! Not even temporary! Remove before making a production build.")
#endif
}
}
// MARK: - UpdatableModelObject implementation
......
......@@ -117,8 +117,6 @@ enum WdtWeatherCode: String, Codable {
case .mostlyCloudy, .overcast:
//TODO: handle mostly cloudy night
return .mostlyCloudyDay
default:
return .unknown
}
}
}
......@@ -46,7 +46,7 @@ struct WdtDailySummary: Codable {
dateFormatter.dateFormat = "MM/dd/YYYY"
dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: dateLocal) else {
throw WdtWeatherSourceError.dataMergeError
throw WdtWeatherSourceError.dataMergeError("daily.date: \(dateLocal)")
}
var weekDay = WeekDay(rawValue: self.weekDay)
......@@ -56,7 +56,7 @@ struct WdtDailySummary: Codable {
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
}
guard let weekDayUnwrapped = weekDay else {
throw WdtWeatherSourceError.dataMergeError
throw WdtWeatherSourceError.dataMergeError("daily.weekday: \(self.weekDay)")
}
var result = DailyWeather(date: date, timeZone: timeZone, weekDay: weekDayUnwrapped)
......@@ -75,7 +75,7 @@ struct WdtDailySummary: Codable {
let sunMoonDateFormatter = DateFormatter()
sunMoonDateFormatter.timeZone = TimeZone(identifier: "UTC")
sunMoonDateFormatter.dateFormat = "YYYY-MM-dd hh:mm:ss"
sunMoonDateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
if let sunriseUtc = sunriseUtc {
result.sunrise = sunMoonDateFormatter.date(from: sunriseUtc)
}
......
......@@ -36,11 +36,11 @@ struct WdtHourlySummary: Codable {
func toAppModel(timeZone: TimeZone) throws -> HourlyWeather {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm:ss"
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: self.dateTimeLocal) else {
throw WdtWeatherSourceError.dataMergeError
throw WdtWeatherSourceError.dataMergeError("hourly.date: \(self.dateTimeLocal)")
}
var weekDay = WeekDay(rawValue: self.weekDay)
......@@ -50,7 +50,7 @@ struct WdtHourlySummary: Codable {
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
}
guard let weekDayUnwrapped = weekDay else {
throw WdtWeatherSourceError.dataMergeError
throw WdtWeatherSourceError.dataMergeError("hourly.weekday: \(self.weekDay)")
}
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
......
......@@ -37,10 +37,19 @@ struct WdtLocation: Codable {
if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") {
coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
let today = try surfaceObservation.toAppModel(timeZone: timeZone)
var today = try surfaceObservation.toAppModel(timeZone: timeZone)
let dailyWeather = try dailySummaries?.toAppModel(timeZone: timeZone) ?? [DailyWeather]()
let hourlyWeather = try hourlySummaries?.toAppModel(timeZone: timeZone) ?? [HourlyWeather]()
if let firstDay = dailyWeather.first {
today.minTemp = firstDay.minTemp
today.maxTemp = firstDay.maxTemp
today.precipitationProbability = firstDay.precipitationProbability
today.sunState = firstDay.sunState
today.moonState = firstDay.moonState
today.moonPhase = firstDay.moonPhase
}
return Location(coordinates: coordinates,
imageName: nil,
countryCode: nil,
......@@ -50,6 +59,7 @@ struct WdtLocation: Codable {
nickname: nil,
zip: zipcode,
fipsCode: nil,
timeZone: timeZone,
today: today,
daily: dailyWeather,
hourly: hourlyWeather)
......
......@@ -42,11 +42,11 @@ struct WdtSurfaceObservation: Codable {
public func toAppModel(timeZone: TimeZone) throws -> CurrentWeather {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm:ss"
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: self.dateTimeLocal) else {
throw WdtWeatherSourceError.dataMergeError
throw WdtWeatherSourceError.dataMergeError("today.date: \(dateTimeLocal)")
}
var weekDay = WeekDay(rawValue: self.weekDay)
if weekDay == nil {
......@@ -55,7 +55,7 @@ struct WdtSurfaceObservation: Codable {
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
}
guard let weekDayUnwrapped = weekDay else {
throw WdtWeatherSourceError.dataMergeError
throw WdtWeatherSourceError.dataMergeError("today.weekday: \(self.weekDay)")
}
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
......@@ -64,13 +64,6 @@ struct WdtSurfaceObservation: Codable {
if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown
}
// TODO: gotta fill up values from daily:
// minTemp
// maxTemp
// precipitationProbability
// sunState
// moonState
// moonPhase
result.windSpeed = WindSpeed(valueString: self.windSpeedKph, unit: .kilometersPerHour)
result.windDirection = WindDirection(rawValue: windDirection ?? "")
......
......@@ -13,7 +13,7 @@ public enum WdtWeatherSourceError: Error {
case badUrl
case networkError(Error?)
case badServerResponse(Error?)
case dataMergeError
case dataMergeError(String)
}
public class WdtWeatherSource: WeatherSource {
......@@ -58,10 +58,11 @@ public class WdtWeatherSource: WeatherSource {
let decoder = XMLDecoder()
do {
let locationResponse = try decoder.decode(WdtLocationResponse.self, from: data)
guard let newLocation = self.update(location: location, from: locationResponse) else {
guard let newLocation = try self.update(location: location, from: locationResponse) else {
completion(nil, WdtWeatherSourceError.badServerResponse(error))
return
}
completion(newLocation, nil)
}
catch {
......@@ -71,29 +72,12 @@ public class WdtWeatherSource: WeatherSource {
dataTask.resume()
}
func update(location: Location, from locationResponse: WdtLocationResponse) -> Location? {
// guard let wdtLocation = locationResponse.locations.first else {
// return nil
// }
var updatedLocation = location
// if updatedLocation.coordinates == nil {
// if let lat = CLLocationDegrees(wdtLocation.lat ?? ""), let lon = CLLocationDegrees(wdtLocation.lon ?? "") {
// location.coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
// }
// }
// if updatedLocation.cityName == nil {
// updatedLocation.cityName = wdtLocation.city
// }
// if updatedLocation.countryName == nil {
// updatedLocation.countryName = wdtLocation.country
// }
// if updatedLocation.region == nil {
// updatedLocation.
// }
//
// TOOD: implement
#warning("Not implemented!")
func update(location: Location, from locationResponse: WdtLocationResponse) throws -> Location? {
guard let wdtLocation = locationResponse.locations.first else {
return nil
}
let incrementalChanges = try wdtLocation.toAppModel(timeZone: location.timeZone)
let updatedLocation = location.mergedWith(incrementalChanges: incrementalChanges)
return updatedLocation
}
}
......@@ -35,6 +35,9 @@ class TodayViewController: UIViewController {
prepareViewController()
prepareNavigationBar()
prepareTableView()
viewModel.delegate = self
viewModel.updateWeather()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
......@@ -120,3 +123,9 @@ extension TodayViewController: UITableViewDelegate {
viewModel.todayCellFactory.willDisplay(cell: cell)
}
}
extension TodayViewController: ViewModelDelegate {
func viewModelDidChange<P>(model: P) where P : ViewModelProtocol {
tableView.reloadData()
}
}
......@@ -6,108 +6,38 @@
//
import UIKit
class TodayViewModel {
class TodayViewModel: ViewModelProtocol {
//Public
public let todayCellFactory = TodayCellFactory()
public weak var delegate:ViewModelDelegate?
public private(set) var location:Location
public private(set) var location: Location?
private var locationManager: LocationManager
init() {
func weekDay(byIndex:Int) -> WeekDay {
switch byIndex {
case 0:
return .monday
case 1:
return .tuesday
case 2:
return .wednesday
case 3:
return .thursday
case 4:
return .friday
case 5:
return .saturday
default:
return .sunday
}
}
let today = CurrentWeather(date: Date(),
timeZone: .current,
weekDay: .monday,
type: .heavyRain,
isDay: true,
minTemp: .init(value: -4, unit: .celsius),
maxTemp: .init(value: 9, unit: .celsius),
windSpeed: .init(value: 5, unit: .milesPerHour),
windDirection: .north,
precipitationProbability: .some(30),
temp: .init(value: 4, unit: .celsius),
apparentTemp: .init(value: 5, unit: .celsius),
humidity: .some(30),
visibility: .init(value: 8, unit: .miles),
pressure: .init(value: 29.7, unit: .inchesOfMercury),
sunrise: .init(hours: 6, minutes: 26),
sunset: .init(hours: 17, minutes: 46),
sunState: .alwaysUp,
moonrise: .init(hours: 20, minutes: 00),
moonset: .init(hours: 5, minutes: 30),
moonState: .normal)
var daily = [DailyWeather]()
var dailyStartDate = Date() - TimeInterval(3600 * 24)
for index in 0..<7 {
let day = DailyWeather(date: dailyStartDate,
timeZone: .current,
weekDay: weekDay(byIndex: index),
type: WeatherType.allCases.randomElement() ?? .unknown,
minTemp: .init(value: Double.random(in: -3..<5), unit: .celsius),
maxTemp: .init(value: Double.random(in: 5..<10), unit: .celsius),
windSpeed: .init(value: Double.random(in: 0..<10), unit: .milesPerHour),
windDirection: WindDirection.allCases.randomElement() ?? .east,
precipitationProbability: .some(UInt.random(in: 0..<100)),
sunrise: nil,
sunset: nil,
sunState: .normal,
moonrise: nil,
moonset: nil,
moonState: .normal)
dailyStartDate = dailyStartDate + TimeInterval(3600 * 24)
daily.append(day)
}
var hourly = [HourlyWeather]()
var startDate = Date() - TimeInterval(3600)
for _ in 0..<24 {
let hour = HourlyWeather(date: startDate,
timeZone: TimeZone(abbreviation: "EST") ?? .current,
weekDay: .monday,
type: WeatherType.allCases.randomElement() ?? .unknown,
isDay: true,
temp: .init(value: Double.random(in: -3..<10), unit: .celsius),
windSpeed: .init(value: Double.random(in: 0..<10), unit: .milesPerHour),
windDirection: WindDirection.allCases.randomElement() ?? .east,
precipitationProbability: .some(UInt.random(in: 0..<100)),
humidity: .some(UInt.random(in: 0..<100)))
startDate = startDate + TimeInterval(3600)
hourly.append(hour)
}
self.location = Location(coordinates: nil,
imageName: "ny_bridge",
countryCode: "us",
countryName: "United States",
region: "New York",
cityName: "New York",
nickname: "ny",
fipsCode: "36",
today: today,
daily: daily,
hourly: hourly)
public init(locationManager: LocationManager) {
self.locationManager = locationManager
locationManager.add(delegate: self)
//Setup factory callback
todayCellFactory.onGetLocation = {[weak self] in
return self?.location
}
}
public func updateWeather() {
locationManager.updateWeather()
}
deinit {
self.locationManager.remove(delegate: self)
}
}
extension TodayViewModel: LocationManagerDelegate {
func locationManager(_ locationManager: LocationManager, changedCurrentLocation newLocation: Location?) {
DispatchQueue.main.async {
print("TVM")
self.location = newLocation
self.delegate?.viewModelDidChange(model: self)
}
}
}
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