Commit 45c821a5 by Demid Merzlyakov

Update weather for all locations.

parent 237e05ae
...@@ -22,5 +22,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -22,5 +22,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true return true
} }
func applicationDidBecomeActive(_ application: UIApplication) {
LocationManager.shared.updateAllWeatherIfNeeded()
}
} }
...@@ -211,10 +211,11 @@ extension DeviceLocationMonitor: CLLocationManagerDelegate { ...@@ -211,10 +211,11 @@ extension DeviceLocationMonitor: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let newLocation = (locations.min { $0.horizontalAccuracy < $1.horizontalAccuracy }) else { guard let newLocation = (locations.min { $0.horizontalAccuracy < $1.horizontalAccuracy }) else {
log.info("updated location: no location") log.debug("updated location: no location")
return return
} }
_lastKnownLocation = newLocation _lastKnownLocation = newLocation
log.debug("updated location: (\(newLocation.coordinate.latitude); \(newLocation.coordinate.longitude))")
DispatchQueue.main.async { DispatchQueue.main.async {
self.delegate?.deviceLocationMonitor(self, didUpdateLocation: DeviceLocation(location: self._lastKnownLocation)) self.delegate?.deviceLocationMonitor(self, didUpdateLocation: DeviceLocation(location: self._lastKnownLocation))
} }
......
...@@ -51,6 +51,7 @@ public class LocationManager { ...@@ -51,6 +51,7 @@ public class LocationManager {
self.selectedLocationIndex = newValue.count > 0 ? 0 : nil self.selectedLocationIndex = newValue.count > 0 ? 0 : nil
} }
else { else {
updateAllWeatherIfNeeded() //TODO: the whole flow is not optimal.
self.delegates.invoke { (delegate) in self.delegates.invoke { (delegate) in
delegate.locationManager(self, changedSelectedLocation: self.selectedLocation) delegate.locationManager(self, changedSelectedLocation: self.selectedLocation)
} }
...@@ -84,9 +85,10 @@ public class LocationManager { ...@@ -84,9 +85,10 @@ public class LocationManager {
delegate.locationManager(self, changedSelectedLocation: newLocation) delegate.locationManager(self, changedSelectedLocation: newLocation)
} }
} }
self.updateWeather(for: newLocation) updateAllWeatherIfNeeded()
} }
} }
public var selectedLocation: Location? { public var selectedLocation: Location? {
get { get {
guard let index = selectedLocationIndex else { guard let index = selectedLocationIndex else {
...@@ -131,34 +133,52 @@ public class LocationManager { ...@@ -131,34 +133,52 @@ public class LocationManager {
self.deviceLocationMonitor.delegate = self self.deviceLocationMonitor.delegate = self
} }
public func updateWeather(for location: Location?) { public func updateAllWeatherIfNeeded() {
let locations = self.locations
guard locations.count > 0 else {
log.info("Update all: update default location if needed.")
updateWeather(for: defaultLocation, updateType: .full)
return
}
log.info("Update all \(locations.count) locations if needed...")
if let selectedLocation = self.selectedLocation {
updateWeather(for: selectedLocation, updateType: .full)
}
for location in locations {
if selectedLocation != location {
updateWeather(for: location, updateType: .preferIncremental)
}
}
}
public func updateWeather(for location: Location?, updateType: WeatherUpdateType) {
guard let location = location else { guard let location = location else {
log.warning("Update weather: empty location.") log.warning("Update weather: empty location.")
return return
} }
if let lastTimeUpdated = location.lastWeatherUpdateDate { if let lastTimeUpdated = location.lastWeatherUpdateDate {
guard Date().timeIntervalSince(lastTimeUpdated) >= weatherUpdateSource.weatherUpdateInterval else { guard Date().timeIntervalSince(lastTimeUpdated) >= weatherUpdateSource.weatherUpdateInterval else {
log.info("Update weather: fresh enough (last updated at \(lastTimeUpdated)), skip update.") log.info("Update weather (\(location)): fresh enough (last updated at \(lastTimeUpdated)), skip update.")
return return
} }
} }
log.info("Update weather for location: \(location)") log.info("Update weather for: \(location); type: \(updateType)")
weatherUpdateSource.updateWeather(for: location) { [weak self] (updatedLocation, error) in weatherUpdateSource.updateWeather(for: location, type: updateType) { [weak self] (updatedLocation, error) in
guard let self = self else { return } guard let self = self else { return }
guard let updatedLocation = updatedLocation else { guard let updatedLocation = updatedLocation else {
if let error = error { if let error = error {
self.log.error("Update weather error: \(error)") self.log.error("Update weather (\(location)) error: \(error)")
} }
else { else {
self.log.error("Update weather error: unknown error") self.log.error("Update weather (\(location)) error: unknown error")
} }
return return
} }
DispatchQueue.main.async { DispatchQueue.main.async {
self.log.info("Update weather finished for location: \(location)") self.log.info("Update weather finished for \(location)")
if let indexToUpdate = self.locations.firstIndex(where: { $0 == updatedLocation }) { if let indexToUpdate = self.locations.firstIndex(where: { $0 == updatedLocation }) {
self.locations[indexToUpdate] = updatedLocation self.locations[indexToUpdate] = updatedLocation
} }
......
...@@ -36,7 +36,7 @@ public class WdtWeatherSource: WeatherSource { ...@@ -36,7 +36,7 @@ public class WdtWeatherSource: WeatherSource {
public let weatherUpdateInterval = TimeInterval(15 * 60) // 15 minutes public let weatherUpdateInterval = TimeInterval(15 * 60) // 15 minutes
public func updateWeather(for location: Location, completion: @escaping WeatherSourceCompletion) { public func updateWeather(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion) {
internalQueue.addOperation { [weak self] in internalQueue.addOperation { [weak self] in
let extendedCompletion: WeatherSourceCompletion = { [weak self] (updatedLocation, error) in let extendedCompletion: WeatherSourceCompletion = { [weak self] (updatedLocation, error) in
self?.internalQueue.addOperation { self?.internalQueue.addOperation {
...@@ -44,19 +44,28 @@ public class WdtWeatherSource: WeatherSource { ...@@ -44,19 +44,28 @@ public class WdtWeatherSource: WeatherSource {
self?.locationsBeingUpdated.remove(location) self?.locationsBeingUpdated.remove(location)
} }
} }
self?.updateWeatherInternal(for: location, completion: extendedCompletion) self?.updateWeatherInternal(for: location, type: type, completion: extendedCompletion)
} }
} }
/// This method should only be run from the internalQueue. /// This method should only be run from the internalQueue.
private func updateWeatherInternal(for location: Location, completion: @escaping WeatherSourceCompletion) { private func updateWeatherInternal(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion) {
guard !locationsBeingUpdated.contains(location) else { guard !locationsBeingUpdated.contains(location) else {
completion(nil, WdtWeatherSourceError.alreadyBeingUpdated) completion(nil, WdtWeatherSourceError.alreadyBeingUpdated)
return return
} }
locationsBeingUpdated.insert(location)
log.debug("Start update for \(location)")
guard var urlComponents = URLComponents(string: WdtWeatherSource.updateUrlMega) else { var urlToUse = WdtWeatherSource.updateUrlMega
if type == .preferIncremental && location.lastWeatherUpdateDate != nil {
urlToUse = WdtWeatherSource.updateUrlMicro
log.debug("Start update (incremental) for \(location)")
}
else {
log.debug("Start update (MEGA) for \(location)")
}
guard var urlComponents = URLComponents(string: urlToUse) else {
assertionFailure("Should never happen. The URL should be correct.") assertionFailure("Should never happen. The URL should be correct.")
return return
} }
......
...@@ -9,7 +9,15 @@ import Foundation ...@@ -9,7 +9,15 @@ import Foundation
public typealias WeatherSourceCompletion = (Location?, Error?) -> () public typealias WeatherSourceCompletion = (Location?, Error?) -> ()
public enum WeatherUpdateType {
/// Request all the data the weather source can provide for a given location. Suitable for updating the location for the first time or for pull to refresh. Also for selected location.
case full
/// If the location has been requested before, so it has the majority of the data, but some of it may need to be refreshed, then the weather source is allowed to request a small incremental update (e.g. only the data that changes frequently). Otherwise the weather source should go for a full update.
case preferIncremental
}
public protocol WeatherSource { public protocol WeatherSource {
var weatherUpdateInterval: TimeInterval { get } var weatherUpdateInterval: TimeInterval { get }
func updateWeather(for location: Location, completion: @escaping WeatherSourceCompletion)
func updateWeather(for location: Location, type: WeatherUpdateType, completion: @escaping WeatherSourceCompletion)
} }
...@@ -43,7 +43,7 @@ class ForecastViewModel: ViewModelProtocol { ...@@ -43,7 +43,7 @@ class ForecastViewModel: ViewModelProtocol {
} }
public func updateWeather() { public func updateWeather() {
locationManager.updateWeather(for: locationManager.selectedLocation) locationManager.updateAllWeatherIfNeeded()
} }
public func selectDailyWeatherAt(index:Int) { public func selectDailyWeatherAt(index:Int) {
......
...@@ -11,20 +11,21 @@ class TodayViewModel: ViewModelProtocol { ...@@ -11,20 +11,21 @@ class TodayViewModel: ViewModelProtocol {
//Public //Public
public weak var delegate:ViewModelDelegate? public weak var delegate:ViewModelDelegate?
private(set) var location:Location? private(set) var location:Location?
private let locationManager = LocationManager.shared
deinit { deinit {
LocationManager.shared.remove(delegate: self) locationManager.remove(delegate: self)
Settings.shared.delegate.remove(delegate: self) Settings.shared.delegate.remove(delegate: self)
} }
public init() { public init() {
self.location = LocationManager.shared.selectedLocation self.location = LocationManager.shared.selectedLocation
LocationManager.shared.add(delegate: self) locationManager.add(delegate: self)
Settings.shared.delegate.add(delegate: self) Settings.shared.delegate.add(delegate: self)
} }
public func updateWeather() { public func updateWeather() {
LocationManager.shared.updateWeather(for: LocationManager.shared.selectedLocation) locationManager.updateAllWeatherIfNeeded()
} }
} }
......
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