Commit 969bf37f by Demid Merzlyakov

Search and location selection working. TODO: location deletion.

parent 355b39f0
...@@ -43,5 +43,7 @@ ...@@ -43,5 +43,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>1Weather uses your location to provide you with weather forecasts and ads. For more info visit 1weatherapp.com/privacy</string>
</dict> </dict>
</plist> </plist>
...@@ -183,6 +183,9 @@ internal class DeviceLocationMonitor: NSObject { ...@@ -183,6 +183,9 @@ internal class DeviceLocationMonitor: NSObject {
// MARK: - CLLocationManagerDelegate // MARK: - CLLocationManagerDelegate
extension DeviceLocationMonitor: CLLocationManagerDelegate { extension DeviceLocationMonitor: CLLocationManagerDelegate {
internal func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { internal func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status != .notDetermined else {
return
}
switch status { switch status {
case .denied: case .denied:
analytics(log: .ANALYTICS_LOC_PERM_NO) analytics(log: .ANALYTICS_LOC_PERM_NO)
......
...@@ -38,7 +38,11 @@ public class LocationManager { ...@@ -38,7 +38,11 @@ public class LocationManager {
public private(set) var locations = [Location]() { public private(set) var locations = [Location]() {
didSet { didSet {
log.info("Locations list updated: \(locations.map { $0.description }.joined(separator: ", "))") log.info("Locations list updated: \(locations.map { $0.description }.joined(separator: ", "))")
let newValue = locations
DispatchQueue.main.async { DispatchQueue.main.async {
if let selectedIndex = self.selectedLocationIndex, selectedIndex >= newValue.count {
self.selectedLocationIndex = newValue.count > 0 ? 0 : nil
}
self.delegates.invoke { [weak self] (delegate) in self.delegates.invoke { [weak self] (delegate) in
guard let self = self else { return } guard let self = self else { return }
delegate.locationManager(self, updatedLocationsList: self.locations) delegate.locationManager(self, updatedLocationsList: self.locations)
...@@ -47,17 +51,64 @@ public class LocationManager { ...@@ -47,17 +51,64 @@ public class LocationManager {
} }
} }
private var _selectedLocation: Location? { private var selectedLocationIndex: Int? {
didSet { didSet {
if oldValue?.description != selectedLocation?.description { guard selectedLocationIndex != oldValue else {
return
}
var oldLocation: Location?
if let oldValue = oldValue, oldValue < locations.count {
oldLocation = locations[oldValue]
}
var newLocation: Location?
if let newValue = selectedLocationIndex, newValue < locations.count {
newLocation = locations[newValue]
}
if oldLocation?.description != newLocation?.description {
log.info("Current location changed to: \(selectedLocation?.description ?? "nil")") log.info("Current location changed to: \(selectedLocation?.description ?? "nil")")
} }
log.info("Location updated.") log.info("Location updated.")
DispatchQueue.main.async { DispatchQueue.main.async {
self.delegates.invoke { [weak self] (delegate) in self.delegates.invoke { [weak self] (delegate) in
guard let self = self else { return } guard let self = self else { return }
delegate.locationManager(self, changedSelectedLocation: self.selectedLocation) delegate.locationManager(self, changedSelectedLocation: newLocation)
}
}
self.updateWeather(for: newLocation)
}
}
public var selectedLocation: Location? {
get {
guard let index = selectedLocationIndex else {
// TODO: don't do it this way, because we won't be able to tell that no location is currently selected!
return defaultLocation
}
guard index < locations.count else {
assertionFailure("This shouldn't happen. Got to investigate.")
// But in runtime we can handle it gracefully
DispatchQueue.main.async {
self.selectedLocationIndex = self.locations.count > 0 ? 0 : nil
} }
return nil
}
return locations[index]
}
set {
guard let location = newValue else {
self.selectedLocationIndex = nil
return
}
if let index = locations.firstIndex(of: location) {
self.selectedLocationIndex = index
locations[index] = location
self.delegates.invoke { [weak self] (delegate) in
guard let self = self else { return }
delegate.locationManager(self, changedSelectedLocation: location)
}
}
else {
self.addIfNeeded(location: location, selectLocation: true)
} }
} }
} }
...@@ -65,39 +116,24 @@ public class LocationManager { ...@@ -65,39 +116,24 @@ public class LocationManager {
public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource()) public static let shared = LocationManager(weatherUpdateSource: WdtWeatherSource())
public let maxLocationsCount = 12 public let maxLocationsCount = 12
public init(weatherUpdateSource: WeatherSource) { public init(weatherUpdateSource: WeatherSource) {
self.weatherUpdateSource = weatherUpdateSource self.weatherUpdateSource = weatherUpdateSource
self.deviceLocationMonitor = DeviceLocationMonitor() self.deviceLocationMonitor = DeviceLocationMonitor()
self.deviceLocationMonitor.delegate = self self.deviceLocationMonitor.delegate = self
} }
public var selectedLocation: Location? { public func updateWeather(for location: Location?) {
get { guard let location = location else {
guard let location = _selectedLocation else { log.warning("Update weather: empty location.")
// TODO: don't do it this way! We won't be able to show search if there's no location!
return defaultLocation
}
return location
}
set {
_selectedLocation = newValue
}
}
public func updateWeather() {
guard let location = selectedLocation else {
log.warning("Update weather: no 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 \(location.lastWeatherUpdateDate)), skip update.") log.info("Update weather: fresh enough (last updated at \(lastTimeUpdated)), skip update.")
return return
} }
} }
log.info("Update weather for location: \(location)") log.info("Update weather for location: \(location)")
weatherUpdateSource.updateWeather(for: location) { [weak self] (updatedLocation, error) in weatherUpdateSource.updateWeather(for: location) { [weak self] (updatedLocation, error) in
...@@ -135,22 +171,20 @@ public class LocationManager { ...@@ -135,22 +171,20 @@ public class LocationManager {
locations = [location] + locations locations = [location] + locations
} }
if selectLocation { if selectLocation {
selectedLocation = location selectedLocationIndex = 0
} }
} }
else if let existingLocation = locations.first(where: { $0 == location }) { else if let existingLocationIndex = locations.firstIndex(where: { $0 == location }) {
if selectLocation { if selectLocation {
selectedLocation = existingLocation selectedLocationIndex = existingLocationIndex
} }
} }
else { else {
locations.append(location) locations.append(location)
if selectLocation { if selectLocation {
selectedLocation = location selectedLocationIndex = locations.count - 1
} }
} }
// TODO: we need to update weather for new locations, probably.
// Or not? Should ViewModels handle it?
} }
public func addIfNeeded(partialLocation: PartialLocation, selectLocation: Bool) { public func addIfNeeded(partialLocation: PartialLocation, selectLocation: Bool) {
......
...@@ -442,6 +442,8 @@ extension CitiesViewController: UITableViewDelegate { ...@@ -442,6 +442,8 @@ extension CitiesViewController: UITableViewDelegate {
analytics(log: .ANALYTICS_FTUE_SEARCH_POPULAR) analytics(log: .ANALYTICS_FTUE_SEARCH_POPULAR)
} }
} }
self.close()
//TODO: should be done from ViewModel?
} }
} }
......
...@@ -191,6 +191,8 @@ public class LocationsViewModel { ...@@ -191,6 +191,8 @@ public class LocationsViewModel {
return return
} }
locationManager.addIfNeeded(partialLocation: city, selectLocation: true) locationManager.addIfNeeded(partialLocation: city, selectLocation: true)
// TODO: close View Controller here, otherwise we can't choose already selected city.
// Currently it's done in the didSelectRow method in the VC.
} }
func delete(city: PartialLocation) { func delete(city: PartialLocation) {
......
...@@ -50,7 +50,8 @@ class TodayViewController: UIViewController { ...@@ -50,7 +50,8 @@ class TodayViewController: UIViewController {
} }
@objc private func handleCityButton() { @objc private func handleCityButton() {
print("Handle city button") let locationViewController = LocationViewController(closeButtonIsHidden: false)
present(locationViewController, animated: true)
} }
@objc private func handleNotificationButton() { @objc private func handleNotificationButton() {
......
...@@ -31,7 +31,7 @@ class ForecastViewModel: ViewModelProtocol { ...@@ -31,7 +31,7 @@ class ForecastViewModel: ViewModelProtocol {
} }
public func updateWeather() { public func updateWeather() {
locationManager.updateWeather() locationManager.updateWeather(for: locationManager.selectedLocation)
} }
} }
......
...@@ -31,7 +31,7 @@ class TodayViewModel: ViewModelProtocol { ...@@ -31,7 +31,7 @@ class TodayViewModel: ViewModelProtocol {
} }
public func updateWeather() { public func updateWeather() {
locationManager.updateWeather() locationManager.updateWeather(for: locationManager.selectedLocation)
} }
} }
......
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