|
|
|
// 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"]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|