Commit 5d1529f8 by Demid Merzlyakov

Working network request parsing.

parent f77af241
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import UIKit import UIKit
import CoreLocation
@main @main
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
...@@ -14,6 +15,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -14,6 +15,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ThemeManager.refreshAppearance() 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) self.window = UIWindow(frame: UIScreen.main.bounds)
let appCoordinator = AppCoordinator(window: self.window!) let appCoordinator = AppCoordinator(window: self.window!)
......
...@@ -9,7 +9,7 @@ import UIKit ...@@ -9,7 +9,7 @@ import UIKit
class TodayCoordinator:Coordinator { class TodayCoordinator:Coordinator {
//Private //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 let navigationController = UINavigationController(nibName: nil, bundle: nil)
private var tabBarController:UITabBarController? private var tabBarController:UITabBarController?
......
...@@ -14,18 +14,47 @@ public protocol LocationManagerDelegate: class { ...@@ -14,18 +14,47 @@ public protocol LocationManagerDelegate: class {
public class LocationManager { public class LocationManager {
private let log = Logger(componentName: "LocationManager") private let log = Logger(componentName: "LocationManager")
private let delegates = MulticastDelegate<LocationManagerDelegate>() 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? { public var currentLocation: Location? {
didSet { didSet {
if currentLocation != oldValue { if oldValue?.description != currentLocation?.description {
log.info("Current location changed to: \(currentLocation?.description ?? "nil")") log.info("Current location changed to: \(currentLocation?.description ?? "nil")")
delegates.invoke { [weak self] (delegate) in }
guard let self = self else { return } log.info("Location updated.")
delegate.locationManager(self, changedCurrentLocation: currentLocation) 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 @@ ...@@ -8,7 +8,7 @@
import Foundation import Foundation
import CoreLocation import CoreLocation
public struct Location: CustomStringConvertible, Equatable { public struct Location: CustomStringConvertible {
// MARK: - Data fields // MARK: - Data fields
public var coordinates: CLLocationCoordinate2D? public var coordinates: CLLocationCoordinate2D?
public var imageName: String? = "ny_bridge" //we'll possibly need to switch to URL public var imageName: String? = "ny_bridge" //we'll possibly need to switch to URL
...@@ -39,6 +39,7 @@ public struct Location: CustomStringConvertible, Equatable { ...@@ -39,6 +39,7 @@ public struct Location: CustomStringConvertible, Equatable {
public var zip: String? public var zip: String?
public var fipsCode: String? public var fipsCode: String?
public var timeZone: TimeZone
public var today: CurrentWeather? public var today: CurrentWeather?
public var daily: [DailyWeather] = [DailyWeather]() public var daily: [DailyWeather] = [DailyWeather]()
...@@ -57,20 +58,6 @@ public struct Location: CustomStringConvertible, Equatable { ...@@ -57,20 +58,6 @@ public struct Location: CustomStringConvertible, Equatable {
return "\(cityId) (no coordinates)" 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 // MARK: - UpdatableModelObject implementation
......
...@@ -117,8 +117,6 @@ enum WdtWeatherCode: String, Codable { ...@@ -117,8 +117,6 @@ enum WdtWeatherCode: String, Codable {
case .mostlyCloudy, .overcast: case .mostlyCloudy, .overcast:
//TODO: handle mostly cloudy night //TODO: handle mostly cloudy night
return .mostlyCloudyDay return .mostlyCloudyDay
default:
return .unknown
} }
} }
} }
...@@ -46,7 +46,7 @@ struct WdtDailySummary: Codable { ...@@ -46,7 +46,7 @@ struct WdtDailySummary: Codable {
dateFormatter.dateFormat = "MM/dd/YYYY" dateFormatter.dateFormat = "MM/dd/YYYY"
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: dateLocal) else { guard let date = dateFormatter.date(from: dateLocal) else {
throw WdtWeatherSourceError.dataMergeError throw WdtWeatherSourceError.dataMergeError("daily.date: \(dateLocal)")
} }
var weekDay = WeekDay(rawValue: self.weekDay) var weekDay = WeekDay(rawValue: self.weekDay)
...@@ -56,7 +56,7 @@ struct WdtDailySummary: Codable { ...@@ -56,7 +56,7 @@ struct WdtDailySummary: Codable {
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date)) weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
} }
guard let weekDayUnwrapped = weekDay else { guard let weekDayUnwrapped = weekDay else {
throw WdtWeatherSourceError.dataMergeError throw WdtWeatherSourceError.dataMergeError("daily.weekday: \(self.weekDay)")
} }
var result = DailyWeather(date: date, timeZone: timeZone, weekDay: weekDayUnwrapped) var result = DailyWeather(date: date, timeZone: timeZone, weekDay: weekDayUnwrapped)
...@@ -75,7 +75,7 @@ struct WdtDailySummary: Codable { ...@@ -75,7 +75,7 @@ struct WdtDailySummary: Codable {
let sunMoonDateFormatter = DateFormatter() let sunMoonDateFormatter = DateFormatter()
sunMoonDateFormatter.timeZone = TimeZone(identifier: "UTC") 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 { if let sunriseUtc = sunriseUtc {
result.sunrise = sunMoonDateFormatter.date(from: sunriseUtc) result.sunrise = sunMoonDateFormatter.date(from: sunriseUtc)
} }
......
...@@ -36,11 +36,11 @@ struct WdtHourlySummary: Codable { ...@@ -36,11 +36,11 @@ struct WdtHourlySummary: Codable {
func toAppModel(timeZone: TimeZone) throws -> HourlyWeather { func toAppModel(timeZone: TimeZone) throws -> HourlyWeather {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm:ss" dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: self.dateTimeLocal) else { 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) var weekDay = WeekDay(rawValue: self.weekDay)
...@@ -50,7 +50,7 @@ struct WdtHourlySummary: Codable { ...@@ -50,7 +50,7 @@ struct WdtHourlySummary: Codable {
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date)) weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
} }
guard let weekDayUnwrapped = weekDay else { guard let weekDayUnwrapped = weekDay else {
throw WdtWeatherSourceError.dataMergeError throw WdtWeatherSourceError.dataMergeError("hourly.weekday: \(self.weekDay)")
} }
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day" let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
......
...@@ -37,10 +37,19 @@ struct WdtLocation: Codable { ...@@ -37,10 +37,19 @@ struct WdtLocation: Codable {
if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") { if let lat = CLLocationDegrees(lat ?? ""), let lon = CLLocationDegrees(lon ?? "") {
coordinates = CLLocationCoordinate2D(latitude: lat, longitude: 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 dailyWeather = try dailySummaries?.toAppModel(timeZone: timeZone) ?? [DailyWeather]()
let hourlyWeather = try hourlySummaries?.toAppModel(timeZone: timeZone) ?? [HourlyWeather]() 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, return Location(coordinates: coordinates,
imageName: nil, imageName: nil,
countryCode: nil, countryCode: nil,
...@@ -50,6 +59,7 @@ struct WdtLocation: Codable { ...@@ -50,6 +59,7 @@ struct WdtLocation: Codable {
nickname: nil, nickname: nil,
zip: zipcode, zip: zipcode,
fipsCode: nil, fipsCode: nil,
timeZone: timeZone,
today: today, today: today,
daily: dailyWeather, daily: dailyWeather,
hourly: hourlyWeather) hourly: hourlyWeather)
......
...@@ -42,11 +42,11 @@ struct WdtSurfaceObservation: Codable { ...@@ -42,11 +42,11 @@ struct WdtSurfaceObservation: Codable {
public func toAppModel(timeZone: TimeZone) throws -> CurrentWeather { public func toAppModel(timeZone: TimeZone) throws -> CurrentWeather {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm:ss" dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
dateFormatter.timeZone = timeZone dateFormatter.timeZone = timeZone
guard let date = dateFormatter.date(from: self.dateTimeLocal) else { guard let date = dateFormatter.date(from: self.dateTimeLocal) else {
throw WdtWeatherSourceError.dataMergeError throw WdtWeatherSourceError.dataMergeError("today.date: \(dateTimeLocal)")
} }
var weekDay = WeekDay(rawValue: self.weekDay) var weekDay = WeekDay(rawValue: self.weekDay)
if weekDay == nil { if weekDay == nil {
...@@ -55,7 +55,7 @@ struct WdtSurfaceObservation: Codable { ...@@ -55,7 +55,7 @@ struct WdtSurfaceObservation: Codable {
weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date)) weekDay = WeekDay(grigorianWeekDayNumber: calendar.component(.weekday, from: date))
} }
guard let weekDayUnwrapped = weekDay else { guard let weekDayUnwrapped = weekDay else {
throw WdtWeatherSourceError.dataMergeError throw WdtWeatherSourceError.dataMergeError("today.weekday: \(self.weekDay)")
} }
let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day" let isDay = dayNight.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "day"
...@@ -64,13 +64,6 @@ struct WdtSurfaceObservation: Codable { ...@@ -64,13 +64,6 @@ struct WdtSurfaceObservation: Codable {
if let weatherCode = self.weatherCode { if let weatherCode = self.weatherCode {
result.type = WdtWeatherCode(rawValue: weatherCode)?.toAppModel() ?? .unknown 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.windSpeed = WindSpeed(valueString: self.windSpeedKph, unit: .kilometersPerHour)
result.windDirection = WindDirection(rawValue: windDirection ?? "") result.windDirection = WindDirection(rawValue: windDirection ?? "")
......
...@@ -13,7 +13,7 @@ public enum WdtWeatherSourceError: Error { ...@@ -13,7 +13,7 @@ public enum WdtWeatherSourceError: Error {
case badUrl case badUrl
case networkError(Error?) case networkError(Error?)
case badServerResponse(Error?) case badServerResponse(Error?)
case dataMergeError case dataMergeError(String)
} }
public class WdtWeatherSource: WeatherSource { public class WdtWeatherSource: WeatherSource {
...@@ -58,10 +58,11 @@ public class WdtWeatherSource: WeatherSource { ...@@ -58,10 +58,11 @@ public class WdtWeatherSource: WeatherSource {
let decoder = XMLDecoder() let decoder = XMLDecoder()
do { do {
let locationResponse = try decoder.decode(WdtLocationResponse.self, from: data) 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)) completion(nil, WdtWeatherSourceError.badServerResponse(error))
return return
} }
completion(newLocation, nil) completion(newLocation, nil)
} }
catch { catch {
...@@ -71,29 +72,12 @@ public class WdtWeatherSource: WeatherSource { ...@@ -71,29 +72,12 @@ public class WdtWeatherSource: WeatherSource {
dataTask.resume() dataTask.resume()
} }
func update(location: Location, from locationResponse: WdtLocationResponse) -> Location? { func update(location: Location, from locationResponse: WdtLocationResponse) throws -> Location? {
// guard let wdtLocation = locationResponse.locations.first else { guard let wdtLocation = locationResponse.locations.first else {
// return nil return nil
// } }
var updatedLocation = location let incrementalChanges = try wdtLocation.toAppModel(timeZone: location.timeZone)
// if updatedLocation.coordinates == nil { let updatedLocation = location.mergedWith(incrementalChanges: incrementalChanges)
// 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!")
return updatedLocation return updatedLocation
} }
} }
...@@ -35,6 +35,9 @@ class TodayViewController: UIViewController { ...@@ -35,6 +35,9 @@ class TodayViewController: UIViewController {
prepareViewController() prepareViewController()
prepareNavigationBar() prepareNavigationBar()
prepareTableView() prepareTableView()
viewModel.delegate = self
viewModel.updateWeather()
} }
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
...@@ -120,3 +123,9 @@ extension TodayViewController: UITableViewDelegate { ...@@ -120,3 +123,9 @@ extension TodayViewController: UITableViewDelegate {
viewModel.todayCellFactory.willDisplay(cell: cell) viewModel.todayCellFactory.willDisplay(cell: cell)
} }
} }
extension TodayViewController: ViewModelDelegate {
func viewModelDidChange<P>(model: P) where P : ViewModelProtocol {
tableView.reloadData()
}
}
...@@ -6,108 +6,38 @@ ...@@ -6,108 +6,38 @@
// //
import UIKit import UIKit
class TodayViewModel { class TodayViewModel: ViewModelProtocol {
//Public //Public
public let todayCellFactory = TodayCellFactory() public let todayCellFactory = TodayCellFactory()
public weak var delegate:ViewModelDelegate? public weak var delegate:ViewModelDelegate?
public private(set) var location:Location public private(set) var location: Location?
private var locationManager: LocationManager
init() { public init(locationManager: LocationManager) {
func weekDay(byIndex:Int) -> WeekDay { self.locationManager = locationManager
switch byIndex { locationManager.add(delegate: self)
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)
//Setup factory callback //Setup factory callback
todayCellFactory.onGetLocation = {[weak self] in todayCellFactory.onGetLocation = {[weak self] in
return self?.location 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