Commit 8118ec37 by Demid Merzlyakov

Onboarding (no privacy notice).

parent 862b5676
......@@ -251,6 +251,9 @@
CEC5270325E7BB4000DA58A5 /* WdtSurfaceObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC5270225E7BB4000DA58A5 /* WdtSurfaceObservation.swift */; };
CEC5275D25E8E50B00DA58A5 /* WdtDailySummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC5275C25E8E50B00DA58A5 /* WdtDailySummary.swift */; };
CEC5276025E92DDA00DA58A5 /* WdtHourlySummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC5275F25E92DDA00DA58A5 /* WdtHourlySummary.swift */; };
CEC8FBAF2639756A0001A6BF /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */; };
CEC8FBB2263976240001A6BF /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */; };
CEC8FBB5263976400001A6BF /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */; };
CEDE4E8225EEFD56007457E9 /* WdtWeatherCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE4E7E25EEFD56007457E9 /* WdtWeatherCode.swift */; };
CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE4E7F25EEFD56007457E9 /* WdtLocationResponse.swift */; };
CEDE4E8425EEFD56007457E9 /* WdtDailySummariesArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE4E8025EEFD56007457E9 /* WdtDailySummariesArray.swift */; };
......@@ -570,6 +573,9 @@
CEC5270225E7BB4000DA58A5 /* WdtSurfaceObservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WdtSurfaceObservation.swift; sourceTree = "<group>"; };
CEC5275C25E8E50B00DA58A5 /* WdtDailySummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WdtDailySummary.swift; sourceTree = "<group>"; };
CEC5275F25E92DDA00DA58A5 /* WdtHourlySummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WdtHourlySummary.swift; sourceTree = "<group>"; };
CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = "<group>"; };
CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
CEDE4E7E25EEFD56007457E9 /* WdtWeatherCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WdtWeatherCode.swift; sourceTree = "<group>"; };
CEDE4E7F25EEFD56007457E9 /* WdtLocationResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WdtLocationResponse.swift; sourceTree = "<group>"; };
CEDE4E8025EEFD56007457E9 /* WdtDailySummariesArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WdtDailySummariesArray.swift; sourceTree = "<group>"; };
......@@ -764,6 +770,7 @@
CE0457942632B3F900B3C19A /* NotificationsCoordinator.swift */,
87D815A92636D5E60015A6D1 /* NWSAlertCoordinator.swift */,
CD7BF1572620410800A30DF5 /* RadarCoordinator.swift */,
CEC8FBB1263976240001A6BF /* OnboardingCoordinator.swift */,
);
path = Coordinators;
sourceTree = "<group>";
......@@ -869,6 +876,7 @@
CE04578F2632B3BC00B3C19A /* NotificationsViewModel.swift */,
87D815AB2636D61D0015A6D1 /* NWSAlertViewModel.swift */,
CD6761872625C3360079D273 /* RadarViewModel.swift */,
CEC8FBB4263976400001A6BF /* OnboardingViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
......@@ -894,6 +902,7 @@
CD6B3038257267E2004B34B3 /* View controllers */ = {
isa = PBXGroup;
children = (
CEC8FBAD263975170001A6BF /* Onboarding */,
CD8B60AF263819780055CB3F /* Notifications */,
CD8B60A5263819400055CB3F /* NWSAlert */,
CD17C5F425D15B3400EE884E /* Today */,
......@@ -1493,6 +1502,14 @@
path = ModelObjects;
sourceTree = "<group>";
};
CEC8FBAD263975170001A6BF /* Onboarding */ = {
isa = PBXGroup;
children = (
CEC8FBAE2639756A0001A6BF /* OnboardingViewController.swift */,
);
path = Onboarding;
sourceTree = "<group>";
};
CEDE4E7D25EEFD56007457E9 /* HelperObjects */ = {
isa = PBXGroup;
children = (
......@@ -1903,6 +1920,7 @@
CD43DF032636C6640010C9F7 /* CACornerMask+All.swift in Sources */,
CDC6124F25E7964700188DA7 /* TodayDayTimesCell.swift in Sources */,
CD593BC226088A5900C93428 /* TimePeriodOffsetHolder.swift in Sources */,
CEC8FBB5263976400001A6BF /* OnboardingViewModel.swift in Sources */,
CE8962A226175DF500CA274A /* _CoreAirQuality.swift in Sources */,
CD17C5FB25D15B6B00EE884E /* AppCoordinator.swift in Sources */,
CDAD97B426207D14007FCFB1 /* MapTimeControlView.swift in Sources */,
......@@ -1922,6 +1940,7 @@
CEF959652600C2F900975FAA /* AnalyticsService.swift in Sources */,
CD39F2F225DE94C4009FE398 /* SunPhaseCell.swift in Sources */,
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */,
CEC8FBB2263976240001A6BF /* OnboardingCoordinator.swift in Sources */,
CD8B60B626381E0F0055CB3F /* AdsUserDefaultsWrapper.swift in Sources */,
CD9B6B1425DBCDE2001D9B80 /* GraphView.swift in Sources */,
CD39F2EE25DE858D009FE398 /* NotificationName+Localization.swift in Sources */,
......@@ -2001,6 +2020,7 @@
CEC5270025E7BACB00DA58A5 /* WdtLocation.swift in Sources */,
CE8962A726175DF500CA274A /* _CoreLocation.swift in Sources */,
CD67617026259D220079D273 /* RadarMapLayersController.swift in Sources */,
CEC8FBAF2639756A0001A6BF /* OnboardingViewController.swift in Sources */,
CD866A65260F642600E96A5C /* SettingsDetailsViewController.swift in Sources */,
CD647D0225ED07D60034578B /* TodayViewModel.swift in Sources */,
CD593BD32608BC3F00C93428 /* ForecastDayCell.swift in Sources */,
......
......@@ -13,6 +13,7 @@ protocol Coordinator: AnyObject {
var parentCoordinator:Coordinator? { get set }
func start()
func viewControllerDidEnd(controller:UIViewController)
func childDidFinish(child:Coordinator)
}
extension Coordinator {
......
......@@ -14,14 +14,17 @@ class LocationSearchCoordinator: Coordinator {
//Public
var childCoordinators = [Coordinator]()
var parentCoordinator: Coordinator?
let fromOnboarding: Bool
init(parentViewController:UIViewController) {
init(parentViewController:UIViewController, fromOnboarding: Bool = false) {
self.parentViewController = parentViewController
self.fromOnboarding = fromOnboarding
}
func start() {
let searchViewController = LocationViewController(coordinator: self)
searchViewController.openedFromOnboarding = fromOnboarding
let navigationController = UINavigationController(rootViewController: searchViewController)
self.parentViewController.present(navigationController, animated: true)
}
......
//
// OnboardingCoordinator.swift
// 1Weather
//
// Created by Demid Merzlyakov on 28.04.2021.
//
import UIKit
class OnboardingCoordinator: Coordinator {
private let parentViewController:UIViewController
public var childCoordinators = [Coordinator]()
public var parentCoordinator: Coordinator?
private let locationManager: LocationManager
private var onboardingViewController: OnboardingViewController?
init(parentViewController: UIViewController, locationManager: LocationManager = LocationManager.shared) {
self.parentViewController = parentViewController
self.locationManager = locationManager
}
public func start() {
onboardingViewController = OnboardingViewController(coordinator: self, locationManager: locationManager)
onboardingViewController?.modalPresentationStyle = .overFullScreen
onboardingViewController?.modalTransitionStyle = .crossDissolve
if let onboardingViewController = onboardingViewController {
parentViewController.present(onboardingViewController, animated: true)
}
}
public func openLocationsSearch() {
guard let onboardingViewController = self.onboardingViewController else {
return
}
let searchCoordinator = LocationSearchCoordinator(parentViewController: onboardingViewController, fromOnboarding: true)
searchCoordinator.parentCoordinator = self
searchCoordinator.start()
}
func viewControllerDidEnd(controller: UIViewController) {
parentCoordinator?.childDidFinish(child: self)
}
}
......@@ -7,12 +7,18 @@
import UIKit
protocol TodayCoordinatorDelegate: class {
func childCoordinatorDidFinish(in coordinator: TodayCoordinator)
}
class TodayCoordinator: Coordinator {
// MARK: - Private
private let navigationController = UINavigationController(nibName: nil, bundle: nil)
private var tabBarController:UITabBarController?
var todayViewController: TodayViewController?
// MARK: - Public
public weak var delegate: TodayCoordinatorDelegate?
public var childCoordinators = [Coordinator]()
public var parentCoordinator: Coordinator?
......@@ -21,7 +27,11 @@ class TodayCoordinator: Coordinator {
}
public func start() {
let todayViewController = TodayViewController(coordinator: self)
todayViewController = TodayViewController(coordinator: self)
self.delegate = todayViewController
guard let todayViewController = self.todayViewController else {
return
}
navigationController.viewControllers = [todayViewController]
tabBarController?.add(viewController: navigationController)
}
......@@ -38,7 +48,27 @@ class TodayCoordinator: Coordinator {
notificationsCoordinator.start()
}
public func openOnboarding() {
guard let todayViewController = self.todayViewController else {
return
}
let onboardingCoordinator = OnboardingCoordinator(parentViewController: todayViewController, locationManager: LocationManager.shared) // get rid of singleton
onboardingCoordinator.parentCoordinator = self
onboardingCoordinator.start()
}
public func viewControllerDidEnd(controller: UIViewController) {
parentCoordinator?.childDidFinish(child: self)
}
func childDidFinish(child: Coordinator) {
self.delegate?.childCoordinatorDidFinish(in: self)
for (index, coordinator) in childCoordinators.enumerated() {
if coordinator === child {
childCoordinators.remove(at: index)
break
}
}
}
}
......@@ -175,6 +175,18 @@ public class LocationManager {
storage: DelayedSaveStorage(storage: CoreDataStorage(), delay: 2))
public let maxLocationsCount = 12
private var loadedLocations = false
private var actionAfterLocationLoad: (([Location]) -> ())?
public func doAfterLocationLoad(_ action: @escaping ([Location]) -> ()) {
DispatchQueue.main.async {
if self.loadedLocations {
action(self.locations)
}
else {
self.actionAfterLocationLoad = action
}
}
}
public init(weatherUpdateSource: WeatherSource, healthSource: HealthSource, nwsAlertsManager: NWSAlertsManager, fipsSource: FIPSSource, pushNotificationsManager: PushNotificationsManager, storage: Storage) {
self.weatherUpdateSource = weatherUpdateSource
......@@ -191,16 +203,30 @@ public class LocationManager {
storage.load { [weak self] (locations, selectedIndex, error) in
DispatchQueue.main.async {
guard let self = self else { return }
defer {
self.actionAfterLocationLoad?(self.locations)
}
self.loadedLocations = true
guard error == nil else {
self.log.error("Error while loading locations: \(error!)")
return
}
guard let locations = locations else {
guard var locations = locations else {
assertionFailure("Either error or locations have to be nil, not both")
return
}
if locations.first?.deviceLocation == true && !self.deviceLocationMonitor.hasLocationPermissions {
locations.removeFirst()
}
self.nwsAlertsManager.loadAlerts(from: locations)
self.set(locations: locations, selectedIndex: selectedIndex)
if self.deviceLocationMonitor.hasLocationPermissions {
if locations.first?.deviceLocation != true {
if let lastKnownLocation = self.deviceLocationMonitor.lastKnownLocation {
self.addIfNeeded(partialLocation: lastKnownLocation, selectLocation: false)
}
}
}
}
}
}
......
......@@ -21,6 +21,7 @@ class LocationViewController:UIViewController {
return queue
}()
public var openedFromOnboarding: Bool = false
init(coordinator:LocationSearchCoordinator) {
self.coordinator = coordinator
......@@ -55,10 +56,9 @@ class LocationViewController:UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
#warning("TODO Flurry analytics")
// if openedFromOnboarding {
// analytics(log: .ANALYTICS_FTUE_SEARCH_SEEN)
// }
if openedFromOnboarding {
analytics(log: .ANALYTICS_FTUE_SEARCH_SEEN)
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
......@@ -67,7 +67,10 @@ class LocationViewController:UIViewController {
}
private func close() {
self.navigationController?.dismiss(animated: true)
self.navigationController?.dismiss(animated: true, completion: { [weak self] in
guard let self = self else { return }
self.coordinator.viewControllerDidEnd(controller: self)
})
}
private func updateUI() {
......@@ -91,10 +94,9 @@ class LocationViewController:UIViewController {
}
@objc private func handleLocationButton() {
#warning("TODO Flurry analytics")
// if openedFromOnboarding {
// analytics(log: .ANALYTICS_FTUE_SEARCH_GPS)
// }
if openedFromOnboarding {
analytics(log: .ANALYTICS_FTUE_SEARCH_GPS)
}
locationsViewModel.useCurrentLocation(requestedBy: self)
}
}
......@@ -103,11 +105,22 @@ class LocationViewController:UIViewController {
private extension LocationViewController {
func prepareController() {
navigationItem.title = "search.title".localized().capitalized
let closeButton = UIBarButtonItem(title: "general.close".localized().capitalized,
style: .done,
target: self,
action: #selector(handleCloseButton))
navigationItem.leftBarButtonItem = closeButton
if openedFromOnboarding {
if #available(iOS 13, *) {
self.isModalInPresentation = true
}
navigationItem.leftBarButtonItem = nil
}
else {
if #available(iOS 13, *) {
self.isModalInPresentation = false
}
let closeButton = UIBarButtonItem(title: "general.close".localized().capitalized,
style: .done,
target: self,
action: #selector(handleCloseButton))
navigationItem.leftBarButtonItem = closeButton
}
}
func prepareSearchBar() {
......@@ -342,16 +355,14 @@ extension LocationViewController: UITableViewDelegate {
locationsViewModel.select(city: locationsViewModel.cities[indexPath.row])
case .searchResults:
locationsViewModel.add(city: locationsViewModel.cities[indexPath.row])
#warning("TODO Flurry analytics")
// if openedFromOnboarding {
// analytics(log: .ANALYTICS_FTUE_SEARCH_ADD)
// }
if openedFromOnboarding {
analytics(log: .ANALYTICS_FTUE_SEARCH_ADD)
}
case .popularCities:
locationsViewModel.add(city: locationsViewModel.cities[indexPath.row])
#warning("TODO Flurry analytics")
// if openedFromOnboarding {
// analytics(log: .ANALYTICS_FTUE_SEARCH_POPULAR)
// }
if openedFromOnboarding {
analytics(log: .ANALYTICS_FTUE_SEARCH_POPULAR)
}
}
}
}
......@@ -362,10 +373,9 @@ extension LocationViewController: UISearchBarDelegate {
searchBar.setShowsCancelButton(true, animated: true)
self.locationsViewModel.displayMode = .popularCities
self.locationsViewModel.fetchPopularCities()
#warning("TODO Flurry analytics")
// if openedFromOnboarding {
// analytics(log: .ANALYTICS_FTUE_SEARCH_TAP)
// }
if openedFromOnboarding {
analytics(log: .ANALYTICS_FTUE_SEARCH_TAP)
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
......
//
// OnboardingViewController.swift
// 1Weather
//
// Created by Demid Merzlyakov on 28.04.2021.
//
import UIKit
class OnboardingViewController: UIViewController {
private let coordinator: OnboardingCoordinator
private let locationManager: LocationManager
init(coordinator: OnboardingCoordinator, locationManager: LocationManager) {
self.coordinator = coordinator
self.locationManager = locationManager
super.init(nibName: nil, bundle: nil)
self.locationManager.add(delegate: self)
self.title = ""
}
@available(*, unavailable)
required init?(coder: NSCoder) { return nil }
override func viewDidLoad() {
prepareBackground()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
promptForLocationAccess()
}
private func close(animated: Bool) {
self.presentingViewController?.dismiss(animated: animated, completion: { [weak self] in
guard let self = self else { return }
self.coordinator.viewControllerDidEnd(controller: self)
})
}
private func promptForLocationAccess() {
locationManager.useCurrentLocation(presentDialogsIn: self) { [weak self] (result) in
guard let self = self else { return }
switch result {
case .success:
self.close(animated: true)
case .denied:
self.coordinator.openLocationsSearch()
case .useSearch:
self.coordinator.openLocationsSearch()
}
}
}
//MARK: - UI Preparation
private func prepareBackground() {
view.backgroundColor = UIColor.black.withAlphaComponent(0.79)
view.isOpaque = false
}
}
extension OnboardingViewController: LocationManagerDelegate {
func locationManager(_ locationManager: LocationManager, changedSelectedLocation newLocation: Location?) {
// do nothing
}
func locationManager(_ locationManager: LocationManager, updatedLocationsList newList: [Location]) {
if newList.count > 0 {
close(animated: true)
}
}
}
......@@ -44,6 +44,11 @@ class TodayViewController: UIViewController {
viewModel.delegate = self
viewModel.updateWeather()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewModel.showOnboardingIfNeeded()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
......@@ -138,7 +143,11 @@ extension TodayViewController: UITableViewDelegate {
}
//MARK:- ViewModel Delegate
extension TodayViewController: ViewModelDelegate {
extension TodayViewController: TodayViewModelDelegate {
func showOnboarding(viewModel: TodayViewModel) {
coordinator.openOnboarding()
}
func viewModelDidChange<P>(model: P) where P : ViewModelProtocol {
cityButton.configure(with: viewModel.location)
cityButton.isHidden = false
......@@ -146,3 +155,11 @@ extension TodayViewController: ViewModelDelegate {
tableView.reloadData()
}
}
// MARK: - TodayCoordinatorDelegate
extension TodayViewController: TodayCoordinatorDelegate {
func childCoordinatorDidFinish(in coordinator: TodayCoordinator) {
viewModel.showOnboardingIfNeeded()
}
}
//
// OnboardingViewModel.swift
// 1Weather
//
// Created by Demid Merzlyakov on 28.04.2021.
//
import UIKit
protocol OnboardingViewModelDelegate: ViewModelDelegate {
func viewModelAsksToOpenSearch(_ viewModel: OnboardingViewController)
}
class OnboardingViewModel: ViewModelProtocol {
public weak var delegate: OnboardingViewModelDelegate?
private let locationManager: LocationManager
public init(locationManager: LocationManager) {
self.locationManager = locationManager
}
}
......@@ -7,9 +7,13 @@
import UIKit
protocol TodayViewModelDelegate: ViewModelDelegate {
func showOnboarding(viewModel: TodayViewModel)
}
class TodayViewModel: ViewModelProtocol {
//Public
public weak var delegate:ViewModelDelegate?
public weak var delegate:TodayViewModelDelegate?
//Private
private let locationManager = LocationManager.shared
......@@ -33,6 +37,14 @@ class TodayViewModel: ViewModelProtocol {
public func updateWeather() {
locationManager.updateEverythingIfNeeded()
}
public func showOnboardingIfNeeded() {
locationManager.doAfterLocationLoad { (locations) in
if locations.count == 0 {
self.delegate?.showOnboarding(viewModel: self)
}
}
}
}
//MARK:- LocationManager Delegate
......
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