diff --git a/CHANGELOG.md b/CHANGELOG.md index 5912f5c4..fb1bfb02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 2.1.0.26 + +Fixes: + +* Another attempt to fix night mode transition by requesting more than 1 location objects, until 1 is valid + +## 2.1.0.25 + +Fixes: + +* Prevent multiple calls to update theme +* Update error message + +## 2.1.0.24 + +* Adding additional logging for Night Mode to see why animation is disabled on schedule + ## 2.1.0.23 Fixes: diff --git a/Extensions/RoomConditions/Info.plist b/Extensions/RoomConditions/Info.plist index 4225e5a6..e68a0020 100644 --- a/Extensions/RoomConditions/Info.plist +++ b/Extensions/RoomConditions/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 2.1.0.23 + 2.1.0.26 NSExtension NSExtensionMainStoryboard diff --git a/Sense.xcodeproj/xcshareddata/xcschemes/Sense.xcscheme b/Sense.xcodeproj/xcshareddata/xcschemes/Sense.xcscheme index 4777fde1..4e4ab964 100644 --- a/Sense.xcodeproj/xcshareddata/xcschemes/Sense.xcscheme +++ b/Sense.xcodeproj/xcshareddata/xcschemes/Sense.xcscheme @@ -75,12 +75,12 @@ + isEnabled = "NO"> + isEnabled = "NO"> diff --git a/SleepModel/Base.lproj/Localizable.strings b/SleepModel/Base.lproj/Localizable.strings index 4dd231eb..16248e45 100644 --- a/SleepModel/Base.lproj/Localizable.strings +++ b/SleepModel/Base.lproj/Localizable.strings @@ -192,6 +192,7 @@ "settings.night-mode.option.scheduled.description" = "Automatic from sunset to sunrise."; "settings.night-mode.no-location.message.format" = "%@ must be enabled for Sense to know local sunset and sunrise times."; "settings.night-mode.location.service" = "Location Services"; +"settings.night-mode.error.no-location" = "Your location could not be determined. Make sure Location Service is enabled or try moving to a different location."; "settings.units" = "Units and time"; "settings.support" = "Support"; "settings.tell-a-friend" = "Tell a friend about Sense"; diff --git a/SleepModel/HEMLocationService.h b/SleepModel/HEMLocationService.h index 2dea68f9..c5f828ba 100644 --- a/SleepModel/HEMLocationService.h +++ b/SleepModel/HEMLocationService.h @@ -46,7 +46,8 @@ typedef void(^HEMLocationHandler)(HEMLocation* _Nullable mostRecentLocation, NSE - (HEMLocationAuthStatus)authorizationStatus; - (BOOL)requiresPermission; - (BOOL)hasDeniedPermission; -- (NSError*)quickLocation:(HEMLocationHandler)completion; +- (BOOL)isEnabled; +- (nullable NSError*)quickLocation:(HEMLocationHandler)completion; - (void)requestPermission:(HEMLocationAuthorizationHandler)authHandler; - (nullable HEMLocationActivity*)startLocationActivity:(HEMLocationHandler)update error:(NSError**)error; diff --git a/SleepModel/HEMLocationService.m b/SleepModel/HEMLocationService.m index a07c78d7..10fe7dce 100644 --- a/SleepModel/HEMLocationService.m +++ b/SleepModel/HEMLocationService.m @@ -102,6 +102,10 @@ - (HEMLocationAuthStatus)authorizationStatus { return HEMLocationAuthStatusAuthorized; } } + +- (BOOL)isEnabled { + return [CLLocationManager locationServicesEnabled]; +} - (BOOL)hasDeniedPermission { return [self authorizationStatus] == HEMLocationAuthStatusDenied; diff --git a/SleepModel/NightModeService.swift b/SleepModel/NightModeService.swift index 4901b9ee..924163ce 100644 --- a/SleepModel/NightModeService.swift +++ b/SleepModel/NightModeService.swift @@ -148,7 +148,7 @@ class NightModeService: SENService { func scheduleForSunset(latitude: Double, longitude: Double) { self.updateLocation(latitude: latitude, longitude: longitude) self.save(option: .sunsetToSunrise) - } + } @objc func updateLocation(latitude: Double, longitude: Double) { let latKey = NightModeService.settingsLatKey diff --git a/SleepModel/NightModeSettingsPresenter.swift b/SleepModel/NightModeSettingsPresenter.swift index b14c24db..98004edd 100644 --- a/SleepModel/NightModeSettingsPresenter.swift +++ b/SleepModel/NightModeSettingsPresenter.swift @@ -17,6 +17,7 @@ class NightModeSettingsPresenter: HEMListPresenter { fileprivate var footer: UIView? fileprivate var waitingOnPermission: Bool fileprivate weak var transitionView: UIView? + fileprivate var locationActivity: HEMLocationActivity? init(nightModeService: NightModeService, locationService: HEMLocationService) { let optionsTitle = NSLocalizedString("settings.night-mode.options.title", comment: "table options title") @@ -74,9 +75,12 @@ class NightModeSettingsPresenter: HEMListPresenter { textView.isScrollEnabled = false textView.backgroundColor = self.tableView!.backgroundColor + let enabled = self.locationService.isEnabled() + let denied = self.locationService.hasDeniedPermission() + let footer = UIView() footer.addSubview(textView) - footer.isHidden = self.locationService.hasDeniedPermission() == false + footer.isHidden = denied == false && enabled == true return footer } @@ -134,7 +138,9 @@ class NightModeSettingsPresenter: HEMListPresenter { cell.descriptionLabel?.text = self.detail(forItem: item) if option == .sunsetToSunrise { - self.footer?.isHidden = self.locationService.hasDeniedPermission() == false + let enabled = self.locationService.isEnabled() + let denied = self.locationService.hasDeniedPermission() + self.footer?.isHidden = denied == false && enabled == true cell.enable(self.footer?.isHidden == true) } else { cell.enable(true) @@ -143,38 +149,27 @@ class NightModeSettingsPresenter: HEMListPresenter { override func didNotifyDelegateOfSelection() { super.didNotifyDelegateOfSelection() + + let selectedName = self.selectedItemNames?.last as? String ?? "" + guard let option = NightModeService.Option.fromDescription(description: selectedName) else { + return + } + // snapshot the screen if self.activityContainerView != nil { if let snapshot = self.activityContainerView!.snapshotView(afterScreenUpdates: false) { snapshot.frame = self.activityContainerView!.bounds self.activityContainerView!.addSubview(snapshot) self.transitionView = snapshot + // the transitionView will be removed when the theme is changed, in didChange: } } - let selectedName = self.selectedItemNames?.last as? String ?? "" - guard let option = NightModeService.Option.fromDescription(description: selectedName) else { - return - } - switch option { case .sunsetToSunrise: if self.locationService.requiresPermission() == true { self.waitingOnPermission = true - self.locationService.requestPermission({ (status: HEMLocationAuthStatus) in - self.waitingOnPermission = false - switch status { - case .notEnabled: - fallthrough - case .denied: - let savedOption = self.nightModeService.savedOption() - self.selectedItemNames = [savedOption.localizedDescription()] - self.tableView?.reloadData() // to disable the schedule cell - self.removeTransitionView(animate: false) - default: - self.scheduleNightModeFromLocation() - } - }) + self.requestLocationPermission() } else { self.scheduleNightModeFromLocation() } @@ -190,54 +185,100 @@ class NightModeSettingsPresenter: HEMListPresenter { } + fileprivate func requestLocationPermission() { + self.locationService.requestPermission({ (status: HEMLocationAuthStatus) in + self.waitingOnPermission = false + switch status { + case .notEnabled: + fallthrough + case .denied: + self.revertSelection(withError: false) + default: + self.scheduleNightModeFromLocation() + } + }) + } + + fileprivate func revertSelection(withError: Bool) { + let savedOption = self.nightModeService.savedOption() + self.selectedItemNames = [savedOption.localizedDescription()] + self.tableView?.reloadData() // to disable the schedule cell + + self.removeTransitionView(animate: true) + + if withError == true { + // show error + let title = NSLocalizedString("settings.night-mode", comment: "title, same as screen title") + let message = NSLocalizedString("settings.night-mode.error.no-location", comment: "no location error") + self.presenterDelegate?.presentError(withTitle: title, message: message, from: self) + } + } + fileprivate func removeTransitionView(animate: Bool) { guard let view = self.transitionView else { + SENAnalytics.trackWarning(withMessage: "snapshot already removed in night mode settings") return } guard animate == true else { + SENAnalytics.trackWarning(withMessage: "animation disabled when removing snapshot in night mode settings") view.removeFromSuperview() return } - UIView.animate(withDuration: 0.35, animations: { + UIView.animate(withDuration: 0.35, delay: 0.0, options: .beginFromCurrentState, animations: { view.alpha = 0.0 }) { (finished: Bool) in view.removeFromSuperview() } } - fileprivate func showLocationError() { - // TODO: throw an alert + fileprivate func scheduleNightModeFromLocation() { + guard let service = self.locationService else { + self.revertSelection(withError: false) + return + } + + var done = false + do { + self.locationActivity = try service.startLocationActivity({ [weak self] (loc: HEMLocation?, err: Error?) in + // to ensure only 1 location is used and to not call it too many times + guard done == false else { + return + } + + if loc != nil { + done = true + SENAnalytics.trackNightModeChange(withSetting: kHEMAnalyticsPropNightModeValueAuto) + self?.nightModeService.scheduleForSunset(latitude: Double(loc!.lat), longitude: Double(loc!.lon)) + self?.removeTransitionView(animate: true) + } else if err != nil { + done = true + self?.revertSelection(withError: true) + } + + if done == true { + if let activity = self?.locationActivity, let locService = self?.locationService { + locService.stop(activity) + } + } + + }) + } catch _ { + self.revertSelection(withError: true) + } } - fileprivate func scheduleNightModeFromLocation() { - var scheduled = false - let service = self.locationService - let error = service?.quickLocation({[weak self] (loc: HEMLocation?, err: Error?) in - // to ensure only 1 location is used and to not call it too many times - guard scheduled == false else { - return - } - - scheduled = true - - if loc != nil { - SENAnalytics.trackNightModeChange(withSetting: kHEMAnalyticsPropNightModeValueAuto) - self?.nightModeService.scheduleForSunset(latitude: Double(loc!.lat), - longitude: Double(loc!.lon)) - self?.removeTransitionView(animate: true) - } else { - self?.removeTransitionView(animate: false) - self?.showLocationError() - } - - }) + deinit { + guard let service = self.locationService else { + return + } - if error != nil { - self.removeTransitionView(animate: false) - self.showLocationError() + guard let activity = self.locationActivity else { + return } + + service.stop(activity) } } diff --git a/SleepModel/Sense-Info.plist b/SleepModel/Sense-Info.plist index ee6b4a04..a27e4f7b 100644 --- a/SleepModel/Sense-Info.plist +++ b/SleepModel/Sense-Info.plist @@ -35,7 +35,7 @@ CFBundleVersion - 2.1.0.23 + 2.1.0.26 FacebookAppID 372438546161587 FacebookDisplayName diff --git a/SleepModel/Theme.swift b/SleepModel/Theme.swift index 1cab7874..8f059d2c 100644 --- a/SleepModel/Theme.swift +++ b/SleepModel/Theme.swift @@ -220,8 +220,8 @@ import UIKit let app = UIApplication.shared let keyWindow = app.delegate?.window let rootVC = keyWindow??.rootViewController - self.apply(viewController: rootVC, auto: auto) self.applyApearances() + self.apply(viewController: rootVC, auto: auto) } fileprivate func applyApearances() { @@ -298,14 +298,17 @@ import UIKit return } + var viewControllers: [UIViewController]? if let navVC = controller as? UINavigationController { - navVC.viewControllers.forEach({ (controllerInStack: UIViewController) in + viewControllers = navVC.viewControllers + viewControllers?.forEach({ (controllerInStack: UIViewController) in self.apply(viewController: controllerInStack, auto: auto) }) } if let tabVC = controller as? UITabBarController { - tabVC.viewControllers?.forEach({ (tabController: UIViewController) in + viewControllers = tabVC.viewControllers + viewControllers?.forEach({ (tabController: UIViewController) in self.apply(viewController: tabController, auto: auto) }) } @@ -316,7 +319,9 @@ import UIKit themedVC.didChange(theme: self, auto: auto) controller.childViewControllers.forEach { (child: UIViewController) in - self.apply(viewController: child, auto: auto) + if viewControllers == nil || viewControllers!.contains(child) == false { + self.apply(viewController: child, auto: auto) + } } }