You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

328 lines
12 KiB

// Copyright © 2015 Abhishek Banthia
import Cocoa
import ServiceManagement
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 welcomeVC = (storyboard?.instantiateController(withIdentifier: .welcomeIdentifier) as? WelcomeViewController)
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 = "Get Started"
positiveButton.tag = OnboardingType.welcome.rawValue
backButton.tag = OnboardingType.welcome.rawValue
[negativeButton, backButton].forEach { $0?.isHidden = true }
}
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 = "Continue"
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 = "Continue"
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"
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 = "Continue"
self.negativeButton.isHidden = true
}
}
private func navigateToFinalStage() {
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"
}
}
private func performFinalStepsBeforeFinishing() {
self.positiveButton.tag = OnboardingType.complete.rawValue
// Install the menubar option!
let appDelegate = NSApplication.shared.delegate as? AppDelegate
appDelegate?.continueUsually()
view.window?.close()
if ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument) == false {
UserDefaults.standard.set(true, forKey: CLShowOnboardingFlow)
}
}
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 = "Continue"
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"
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 = "Continue"
}
}
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 = "Get Started"
}
}
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(CLOnboaringTestsLaunchArgument) {
return
}
UserDefaults.standard.set(shouldStart ? 1 : 0, forKey: CLStartAtLogin)
if !SMLoginItemSetEnabled("com.abhishek.ClockerHelper" as CFString, shouldStart) {
Logger.log(object: ["Successful": "NO"], for: "Start Clocker Login")
} else {
Logger.log(object: ["Successful": "YES"], for: "Start Clocker Login")
}
if shouldStart {
Logger.log(object: [:], for: "Enable Launch at Login while Onboarding")
} else {
Logger.log(object: [:], for: "Disable Launch at Login while Onboarding")
}
}
func logExitPoint() {
let currentViewController = currentController()
print(currentViewController)
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"]
}
}
}