// Copyright © 2015 Abhishek Banthia import Cocoa import CoreLoggerKit import CoreModelKit import StartupKit extension NSStoryboard.SceneIdentifier { static let welcomeIdentifier = NSStoryboard.SceneIdentifier("welcomeVC") static let onboardingPermissionsIdentifier = NSStoryboard.SceneIdentifier("onboardingPermissionsVC") static let startAtLoginIdentifier = NSStoryboard.SceneIdentifier("startAtLoginVC") static let onboardingSearchIdentifier = NSStoryboard.SceneIdentifier("onboardingSearchVC") static let finalOnboardingIdentifier = NSStoryboard.SceneIdentifier("finalOnboardingVC") } private enum OnboardingType: Int { case welcome case permissions case launchAtLogin case search case final case complete // Added for logging purposes } class OnboardingParentViewController: NSViewController { @IBOutlet private var containerView: NSView! @IBOutlet private var negativeButton: NSButton! @IBOutlet private var backButton: NSButton! @IBOutlet private var positiveButton: NSButton! private lazy var startupManager = StartupManager() private lazy var welcomeVC = (storyboard?.instantiateController(withIdentifier: .welcomeIdentifier) as? OnboardingWelcomeViewController) private lazy var permissionsVC = (storyboard?.instantiateController(withIdentifier: .onboardingPermissionsIdentifier) as? OnboardingPermissionsViewController) private lazy var startAtLoginVC = (storyboard?.instantiateController(withIdentifier: .startAtLoginIdentifier) as? StartAtLoginViewController) private lazy var onboardingSearchVC = (storyboard?.instantiateController(withIdentifier: .onboardingSearchIdentifier) as? OnboardingSearchController) private lazy var finalOnboardingVC = (storyboard?.instantiateController(withIdentifier: .finalOnboardingIdentifier) as? FinalOnboardingViewController) override func viewDidLoad() { super.viewDidLoad() setup() } private func setup() { setupWelcomeScreen() setupUI() } private func setupWelcomeScreen() { guard let firstVC = welcomeVC else { assertionFailure() return } addChildIfNeccessary(firstVC) containerView.addSubview(firstVC.view) firstVC.view.frame = containerView.bounds } private func setupUI() { setIdentifiersForTests() positiveButton.title = NSLocalizedString("Get Started", comment: "Title for Welcome View Controller's Continue Button") positiveButton.tag = OnboardingType.welcome.rawValue backButton.tag = OnboardingType.welcome.rawValue [negativeButton, backButton].forEach { $0?.isHidden = true } if #available(OSX 11.0, *) { negativeButton.controlSize = .large positiveButton.controlSize = .large backButton.controlSize = .large } backButton.title = NSLocalizedString("Back", comment: "Button title for going back to the previous screen") } private func setIdentifiersForTests() { positiveButton.setAccessibilityIdentifier("Forward") negativeButton.setAccessibilityIdentifier("Alternate") backButton.setAccessibilityIdentifier("Backward") } @IBAction func negativeAction(_: Any) { guard let fromViewController = startAtLoginVC, let toViewController = onboardingSearchVC else { assertionFailure() return } addChildIfNeccessary(toViewController) shouldStartAtLogin(false) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.positiveButton.tag = OnboardingType.search.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.negativeButton.isHidden = true } } @IBAction func continueOnboarding(_: NSButton) { if positiveButton.tag == OnboardingType.welcome.rawValue { navigateToPermissions() } else if positiveButton.tag == OnboardingType.permissions.rawValue { navigateToStartAtLogin() } else if positiveButton.tag == OnboardingType.launchAtLogin.rawValue { navigateToSearch() } else if positiveButton.tag == OnboardingType.search.rawValue { navigateToFinalStage() } else { performFinalStepsBeforeFinishing() } } private func navigateToPermissions() { guard let fromViewController = welcomeVC, let toViewController = permissionsVC else { assertionFailure() return } addChildIfNeccessary(toViewController) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.positiveButton.tag = OnboardingType.permissions.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.backButton.isHidden = false } } private func navigateToStartAtLogin() { guard let fromViewController = permissionsVC, let toViewController = startAtLoginVC else { assertionFailure() return } addChildIfNeccessary(toViewController) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.backButton.tag = OnboardingType.permissions.rawValue self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.title = "Open Clocker At Login".localized() self.negativeButton.isHidden = false } } private func navigateToSearch() { guard let fromViewController = startAtLoginVC, let toViewController = onboardingSearchVC else { assertionFailure() return } addChildIfNeccessary(toViewController) shouldStartAtLogin(true) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.tag = OnboardingType.search.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.negativeButton.isHidden = true } } private func fetchLocalTimezone() { let identifier = TimeZone.autoupdatingCurrent.identifier let currentTimezone = TimezoneData() currentTimezone.timezoneID = identifier currentTimezone.setLabel(identifier) currentTimezone.formattedAddress = identifier currentTimezone.isSystemTimezone = true currentTimezone.placeID = "Home" let operations = TimezoneDataOperations(with: currentTimezone, store: DataStore.shared()) operations.saveObject(at: 0) } private func navigateToFinalStage() { if UserDefaults.standard.object(forKey: UserDefaultKeys.installHomeIndicatorObject) == nil, DataStore.shared().timezones().isEmpty { fetchLocalTimezone() UserDefaults.standard.set(1, forKey: UserDefaultKeys.installHomeIndicatorObject) } guard let fromViewController = onboardingSearchVC, let toViewController = finalOnboardingVC else { assertionFailure() return } addChildIfNeccessary(toViewController) transition(from: fromViewController, to: toViewController, options: .slideLeft) { self.backButton.tag = OnboardingType.search.rawValue self.positiveButton.tag = OnboardingType.final.rawValue self.positiveButton.title = "Launch Clocker".localized() } } func performFinalStepsBeforeFinishing() { positiveButton.tag = OnboardingType.complete.rawValue view.window?.close() if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.onboardingTestsLaunchArgument) == false { UserDefaults.standard.set(true, forKey: UserDefaultKeys.showOnboardingFlow) } // Install the menubar option! let appDelegate = NSApplication.shared.delegate as? AppDelegate appDelegate?.continueUsually() } private func addChildIfNeccessary(_ viewController: NSViewController) { if children.contains(viewController) == false { addChild(viewController) } } @IBAction func back(_: Any) { if backButton.tag == OnboardingType.welcome.rawValue { goBackToWelcomeScreen() } else if backButton.tag == OnboardingType.permissions.rawValue { goBackToPermissions() } else if backButton.tag == OnboardingType.launchAtLogin.rawValue { goBackToStartAtLogin() } else if backButton.tag == OnboardingType.search.rawValue { goBackToSearch() } } private func goBackToSearch() { guard let fromViewController = finalOnboardingVC, let toViewController = onboardingSearchVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.search.rawValue self.backButton.tag = OnboardingType.launchAtLogin.rawValue self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") self.negativeButton.isHidden = true } } private func goBackToStartAtLogin() { guard let fromViewController = onboardingSearchVC, let toViewController = startAtLoginVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.launchAtLogin.rawValue self.backButton.tag = OnboardingType.permissions.rawValue self.positiveButton.title = "Open Clocker At Login".localized() self.negativeButton.isHidden = false } } private func goBackToPermissions() { // We're on StartAtLogin VC and we have to go back to Permissions guard let fromViewController = startAtLoginVC, let toViewController = permissionsVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.permissions.rawValue self.backButton.tag = OnboardingType.welcome.rawValue self.negativeButton.isHidden = true self.positiveButton.title = NSLocalizedString("Continue", comment: "Continue Button Title") } } private func goBackToWelcomeScreen() { guard let fromViewController = permissionsVC, let toViewController = welcomeVC else { assertionFailure() return } transition(from: fromViewController, to: toViewController, options: .slideRight) { self.positiveButton.tag = OnboardingType.welcome.rawValue self.backButton.isHidden = true self.positiveButton.title = NSLocalizedString("Get Started", comment: "Title for Welcome View Controller's Continue Button") } } private func shouldStartAtLogin(_ shouldStart: Bool) { // If tests are going on, we don't want to enable/disable launch at login! if ProcessInfo.processInfo.arguments.contains(UserDefaultKeys.onboardingTestsLaunchArgument) { return } UserDefaults.standard.set(shouldStart ? 1 : 0, forKey: UserDefaultKeys.startAtLogin) startupManager.toggleLogin(shouldStart) shouldStart ? Logger.log(object: nil, for: "Enable Launch at Login while Onboarding") : Logger.log(object: nil, for: "Disable Launch at Login while Onboarding") } func logExitPoint() { let currentViewController = currentController() Logger.log(object: currentViewController, for: "Onboarding Process Exit") } private func currentController() -> [String: String] { switch positiveButton.tag { case 0: return ["Onboarding Process Interrupted": "Welcome View"] case 1: return ["Onboarding Process Interrupted": "Onboarding Permissions View"] case 2: return ["Onboarding Process Interrupted": "Start At Login View"] case 3: return ["Onboarding Process Interrupted": "Onboarding Search View"] case 4: return ["Onboarding Process Interrupted": "Finish Onboarding View"] case 5: return ["Onboarding Process Completed": "Successfully"] default: return ["Onboarding Process Interrupted": "Error"] } } }