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.

316 lines
11 KiB

// Copyright © 2015 Abhishek Banthia
import Cocoa
6 years ago
open class AppDelegate: NSObject, NSApplicationDelegate {
6 years ago
private lazy var floatingWindow: FloatingWindowController = FloatingWindowController.shared()
private lazy var panelController: PanelController = PanelController.shared()
private var statusBarHandler: StatusItemHandler!
private var panelObserver: NSKeyValueObservation?
6 years ago
deinit {
panelObserver?.invalidate()
}
6 years ago
6 years ago
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
if let path = keyPath, path == PreferencesConstants.hotKeyPathIdentifier {
let hotKeyCenter = PTHotKeyCenter.shared()
6 years ago
// Unregister old hot key
let oldHotKey = hotKeyCenter?.hotKey(withIdentifier: path)
hotKeyCenter?.unregisterHotKey(oldHotKey)
6 years ago
// We don't register unless there's a valid key combination
guard let newObject = object as? NSObject, let newShortcut = newObject.value(forKeyPath: path) as? [AnyHashable: Any] else {
return
}
6 years ago
// Register new key
let newHotKey: PTHotKey = PTHotKey(identifier: keyPath,
keyCombo: newShortcut,
target: self,
action: #selector(ping(_:)))
6 years ago
hotKeyCenter?.register(newHotKey)
}
}
6 years ago
6 years ago
public func applicationWillFinishLaunching(_: Notification) {
iVersion.sharedInstance().useAllAvailableLanguages = true
iVersion.sharedInstance().verboseLogging = false
}
6 years ago
6 years ago
public func applicationDidFinishLaunching(_: Notification) {
// Initializing the event store takes really long
EventCenter.sharedCenter()
6 years ago
AppDefaults.initialize()
6 years ago
// Check if we can show the onboarding flow!
showOnboardingFlowIfEligible()
6 years ago
// Ratings Controller initialization
RateController.applicationDidLaunch(UserDefaults.standard)
#if RELEASE
Fabric.with([Crashlytics.self])
checkIfRunFromApplicationsFolder()
#endif
logCurrentLanguagePreferences()
}
// Help us priortize our localization efforts
private func logCurrentLanguagePreferences() {
let firstLanguage = Locale.preferredLanguages.first ?? "en-US"
// We don't care if the language is English!
if firstLanguage.contains("en") {
return
}
let annotations = [
"Language": firstLanguage,
]
Logger.log(object: annotations, for: "Locale")
}
6 years ago
6 years ago
public func applicationDockMenu(_: NSApplication) -> NSMenu? {
let menu = NSMenu(title: "Quick Access")
6 years ago
let toggleMenuItem = NSMenuItem(title: "Toggle Panel", action: #selector(AppDelegate.togglePanel(_:)), keyEquivalent: "")
let openPreferences = NSMenuItem(title: "Preferences", action: #selector(AppDelegate.openPreferencesWindow), keyEquivalent: ",")
let hideFromDockMenuItem = NSMenuItem(title: "Hide from Dock", action: #selector(AppDelegate.hideFromDock), keyEquivalent: "")
6 years ago
[toggleMenuItem, openPreferences, hideFromDockMenuItem].forEach {
$0.isEnabled = true
menu.addItem($0)
}
6 years ago
return menu
}
6 years ago
@objc private func openPreferencesWindow() {
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground)
6 years ago
if displayMode == 1 {
let floatingWindow = FloatingWindowController.shared()
floatingWindow.openPreferences(NSButton())
} else {
let panelController = PanelController.shared()
panelController.openPreferences(NSButton())
}
}
6 years ago
@objc func hideFromDock() {
4 years ago
UserDefaults.standard.set(0, forKey: CLAppDisplayOptions)
NSApp.setActivationPolicy(.accessory)
}
private lazy var controller: OnboardingController? = {
6 years ago
let onboardingStoryboard = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil)
return onboardingStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController
}()
private func showOnboardingFlowIfEligible() {
6 years ago
let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: CLShowOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty)
6 years ago
|| ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument)
6 years ago
5 years ago
shouldLaunchOnboarding ? controller?.launch() : continueUsually()
}
6 years ago
func continueUsually() {
// Check if another instance of the app is already running. If so, then stop this one.
checkIfAppIsAlreadyOpen()
6 years ago
// Make sure the old models are not used anymore
TimezoneData.convert()
6 years ago
// Install the menubar item!
statusBarHandler = StatusItemHandler()
6 years ago
if UserDefaults.standard.object(forKey: CLInstallHomeIndicatorObject) == nil {
fetchLocalTimezone()
UserDefaults.standard.set(1, forKey: CLInstallHomeIndicatorObject)
}
6 years ago
if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) {
RateController.setPreviewMode(true)
}
6 years ago
UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true])
6 years ago
assignShortcut()
6 years ago
6 years ago
panelObserver = panelController.observe(\.hasActivePanel, options: [.new]) { obj, _ in
self.statusBarHandler.setHasActiveIcon(obj.hasActivePanelGetter())
}
6 years ago
let defaults = UserDefaults.standard
6 years ago
setActivationPolicy()
6 years ago
// Set the display mode default as panel!
if let displayMode = defaults.object(forKey: CLShowAppInForeground) as? NSNumber, displayMode.intValue == 1 {
showFloatingWindow()
} else if let displayMode = defaults.object(forKey: CLShowAppInForeground) as? Int, displayMode == 1 {
showFloatingWindow()
}
}
6 years ago
// Should we have a dock icon or just stay in the menubar?
private func setActivationPolicy() {
let defaults = UserDefaults.standard
6 years ago
let currentActivationPolicy = NSRunningApplication.current.activationPolicy
4 years ago
let activationPolicy: NSApplication.ActivationPolicy = defaults.integer(forKey: CLAppDisplayOptions) == 0 ? .accessory : .regular
if currentActivationPolicy != activationPolicy {
NSApp.setActivationPolicy(activationPolicy)
}
}
6 years ago
private func checkIfAppIsAlreadyOpen() {
guard let bundleID = Bundle.main.bundleIdentifier else {
6 years ago
return
}
6 years ago
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID)
6 years ago
if apps.count > 1 {
let currentApplication = NSRunningApplication.current
for app in apps where app != currentApplication {
app.terminate()
}
}
}
6 years ago
private func showAppAlreadyOpenMessage() {
showAlert(message: "An instance of Clocker is already open 😅",
informativeText: "This instance of Clocker will terminate now.",
buttonTitle: "Close")
}
6 years ago
private func showAlert(message: String, informativeText: String, buttonTitle: String) {
NSApplication.shared.activate(ignoringOtherApps: true)
let alert = NSAlert()
alert.messageText = message
alert.informativeText = informativeText
alert.addButton(withTitle: buttonTitle)
alert.runModal()
}
6 years ago
private func fetchLocalTimezone() {
let identifier = TimeZone.autoupdatingCurrent.identifier
6 years ago
let currentTimezone = TimezoneData()
currentTimezone.timezoneID = identifier
currentTimezone.setLabel(identifier)
currentTimezone.formattedAddress = identifier
currentTimezone.isSystemTimezone = true
currentTimezone.placeID = "Home"
6 years ago
let operations = TimezoneDataOperations(with: currentTimezone)
operations.saveObject(at: 0)
6 years ago
// Retrieve Location
// retrieveLatestLocation()
}
6 years ago
@IBAction func ping(_ sender: Any) {
togglePanel(sender)
}
private func retrieveLatestLocation() {
let locationController = LocationController.sharedController()
locationController.determineAndRequestLocationAuthorization()
}
6 years ago
private func showFloatingWindow() {
// Display the Floating Window!
floatingWindow.showWindow(nil)
floatingWindow.updateTableContent()
floatingWindow.startWindowTimer()
6 years ago
NSApp.activate(ignoringOtherApps: true)
}
6 years ago
private func assignShortcut() {
NSUserDefaultsController.shared.addObserver(self,
forKeyPath: PreferencesConstants.hotKeyPathIdentifier,
6 years ago
options: [.initial, .new],
context: nil)
}
6 years ago
private func checkIfRunFromApplicationsFolder() {
if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true {
return
}
6 years ago
let bundlePath = Bundle.main.bundlePath
let applicationDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationDirectory,
FileManager.SearchPathDomainMask.localDomainMask,
true)
for appDir in applicationDirectory {
if bundlePath.hasPrefix(appDir) {
return
}
}
6 years ago
6 years ago
let informativeText = """
6 years ago
Clocker must be run from the Applications folder in order to work properly.
Please quit Clocker, move it to the Applications folder, and relaunch.
Current folder: \(applicationDirectory)"
"""
6 years ago
// Clocker is installed out of Applications directory
// This breaks start at login! Time to show an alert and terminate
showAlert(message: "Move Clocker to the Applications folder",
6 years ago
informativeText: informativeText,
buttonTitle: "Quit")
6 years ago
// Terminate
NSApp.terminate(nil)
}
6 years ago
6 years ago
@IBAction open func togglePanel(_: Any) {
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground)
6 years ago
if displayMode == 1 {
floatingWindow.showWindow(nil)
floatingWindow.updateTableContent()
floatingWindow.startWindowTimer()
} else {
panelController.showWindow(nil)
panelController.setActivePanel(newValue: !panelController.hasActivePanelGetter())
}
NSApp.activate(ignoringOtherApps: true)
}
open func setupFloatingWindow() {
showFloatingWindow()
}
6 years ago
open func closeFloatingWindow() {
floatingWindow.window?.close()
}
6 years ago
func statusItemForPanel() -> StatusItemHandler {
return statusBarHandler
}
6 years ago
open func setPanelDefaults() {
panelController.updateDefaultPreferences()
}
6 years ago
open func setupMenubarTimer() {
statusBarHandler.setupStatusItem()
}
6 years ago
open func invalidateMenubarTimer(_ showIcon: Bool) {
statusBarHandler.invalidateTimer(showIcon: showIcon, isSyncing: true)
}
}