// C o p y r i g h t © 2 0 1 5 A b h i s h e k B a n t h i a
import Cocoa
open class AppDelegate : NSObject , NSApplicationDelegate {
lazy private var floatingWindow : FloatingWindowController = FloatingWindowController . shared ( )
lazy private var panelController : PanelController = PanelController . shared ( )
private var statusBarHandler : StatusItemHandler !
private var panelObserver : NSKeyValueObservation ?
deinit {
panelObserver ? . invalidate ( )
}
open override func observeValue ( forKeyPath keyPath : String ? , of object : Any ? , change : [ NSKeyValueChangeKey : Any ] ? , context : UnsafeMutableRawPointer ? ) {
if let path = keyPath , path = = " values.globalPing " {
let hotKeyCenter = PTHotKeyCenter . shared ( )
// U n r e g i s t e r o l d h o t k e y
let oldHotKey = hotKeyCenter ? . hotKey ( withIdentifier : path )
hotKeyCenter ? . unregisterHotKey ( oldHotKey )
// W e d o n ' t r e g i s t e r u n l e s s t h e r e ' s a v a l i d k e y c o m b i n a t i o n
guard let newObject = object as ? NSObject , let newShortcut = newObject . value ( forKeyPath : path ) as ? [ AnyHashable : Any ] else {
return
}
// R e g i s t e r n e w k e y
let newHotKey : PTHotKey = PTHotKey ( identifier : keyPath ,
keyCombo : newShortcut ,
target : self ,
action : #selector ( ping ( _ : ) ) )
hotKeyCenter ? . register ( newHotKey )
}
}
public func applicationWillFinishLaunching ( _ notification : Notification ) {
iVersion . sharedInstance ( ) . useAllAvailableLanguages = true
iVersion . sharedInstance ( ) . verboseLogging = false
}
public func applicationDidFinishLaunching ( _ notification : Notification ) {
// I n i t i a l i z i n g t h e e v e n t s t o r e t a k e s r e a l l y l o n g
EventCenter . sharedCenter ( )
AppDefaults . initialize ( )
// C h e c k i f w e c a n s h o w t h e o n b o a r d i n g f l o w !
showOnboardingFlow ( )
// R a t i n g s C o n t r o l l e r i n i t i a l i z a t i o n
RateController . applicationDidLaunch ( UserDefaults . standard )
#if RELEASE
Crashlytics . sharedInstance ( ) . debugMode = true
Fabric . with ( [ Crashlytics . self ] )
checkIfRunFromApplicationsFolder ( )
#endif
}
public func applicationDockMenu ( _ sender : NSApplication ) -> NSMenu ? {
let menu = NSMenu ( title : " Quick Access " )
Logger . log ( object : [ " Dock Menu Triggered " : " YES " ] , for : " Dock Menu Triggered " )
let toggleMenuItem = NSMenuItem ( title : " Toggle Panel " , action : #selector ( AppDelegate . togglePanel ( _ : ) ) , keyEquivalent : " " )
let openPreferences = NSMenuItem ( title : " Preferences " , action : #selector ( AppDelegate . openPreferencesWindow ) , keyEquivalent : " , " )
[ toggleMenuItem , openPreferences ] . forEach {
$0 . isEnabled = true
menu . addItem ( $0 )
}
return menu
}
@objc private func openPreferencesWindow ( ) {
let displayMode = UserDefaults . standard . integer ( forKey : CLShowAppInForeground )
if displayMode = = 1 {
let floatingWindow = FloatingWindowController . shared ( )
floatingWindow . openPreferences ( NSButton ( ) )
} else {
let panelController = PanelController . shared ( )
panelController . openPreferences ( NSButton ( ) )
}
}
private lazy var controller : OnboardingController ? = {
let onboardingStoryboard = NSStoryboard ( name : NSStoryboard . Name ( " Onboarding " ) , bundle : nil )
return onboardingStoryboard . instantiateController ( withIdentifier : NSStoryboard . SceneIdentifier ( " onboardingFlow " ) ) as ? OnboardingController
} ( )
private func showOnboardingFlow ( ) {
let shouldLaunchOnboarding = ( DataStore . shared ( ) . retrieve ( key : CLShowOnboardingFlow ) = = nil && DataStore . shared ( ) . timezones ( ) . isEmpty ) || ( ProcessInfo . processInfo . arguments . contains ( CLOnboaringTestsLaunchArgument ) )
shouldLaunchOnboarding ? controller ? . launch ( ) : continueUsually ( )
}
func continueUsually ( ) {
// C h e c k i f a n o t h e r i n s t a n c e o f t h e a p p i s a l r e a d y r u n n i n g . I f s o , t h e n s t o p t h i s o n e .
checkIfAppIsAlreadyOpen ( )
// M a k e s u r e t h e o l d m o d e l s a r e n o t u s e d a n y m o r e
TimezoneData . convert ( )
// I n s t a l l t h e m e n u b a r i t e m !
statusBarHandler = StatusItemHandler ( )
if UserDefaults . standard . object ( forKey : CLInstallHomeIndicatorObject ) = = nil {
fetchLocalTimezone ( )
UserDefaults . standard . set ( 1 , forKey : CLInstallHomeIndicatorObject )
}
if ProcessInfo . processInfo . arguments . contains ( CLUITestingLaunchArgument ) {
RateController . setPreviewMode ( true )
}
UserDefaults . standard . register ( defaults : [ " NSApplicationCrashOnExceptions " : true ] )
assignShortcut ( )
panelObserver = panelController . observe ( \ . hasActivePanel , options : [ . new ] ) { ( obj , change ) in
self . statusBarHandler . setHasActiveIcon ( obj . hasActivePanelGetter ( ) )
}
let defaults = UserDefaults . standard
setActivationPolicy ( )
// S e t t h e d i s p l a y m o d e d e f a u l t a s p a n e l !
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 ( )
}
}
// S h o u l d w e h a v e a d o c k i c o n o r j u s t s t a y i n t h e m e n u b a r ?
private func setActivationPolicy ( ) {
let defaults = UserDefaults . standard
let activationPolicy : NSApplication . ActivationPolicy = defaults . integer ( forKey : CLAppDislayOptions ) = = 0 ? . accessory : . regular
NSApp . setActivationPolicy ( activationPolicy )
}
private func checkIfAppIsAlreadyOpen ( ) {
guard let bundleID = Bundle . main . bundleIdentifier else {
return
}
let apps = NSRunningApplication . runningApplications ( withBundleIdentifier : bundleID )
if apps . count > 1 {
let currentApplication = NSRunningApplication . current
for app in apps where app != currentApplication {
app . terminate ( )
}
}
}
private func showAppAlreadyOpenMessage ( ) {
showAlert ( message : " An instance of Clocker is already open 😅 " ,
informativeText : " This instance of Clocker will terminate now. " ,
buttonTitle : " Close " )
}
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 ( )
}
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 )
operations . saveObject ( at : 0 )
// R e t r i e v e L o c a t i o n
// r e t r i e v e L a t e s t L o c a t i o n ( )
}
@IBAction func ping ( _ sender : Any ) {
togglePanel ( sender )
}
private func retrieveLatestLocation ( ) {
let locationController = LocationController . sharedController ( )
locationController . determineAndRequestLocationAuthorization ( )
}
private func showFloatingWindow ( ) {
// D i s p l a y t h e F l o a t i n g W i n d o w !
floatingWindow . showWindow ( nil )
floatingWindow . updateTableContent ( )
floatingWindow . startWindowTimer ( )
NSApp . activate ( ignoringOtherApps : true )
}
private func assignShortcut ( ) {
NSUserDefaultsController . shared . addObserver ( self ,
forKeyPath : " values.globalPing " ,
options : [ . initial , . new ] ,
context : nil )
}
private func checkIfRunFromApplicationsFolder ( ) {
if let shortCircuit = UserDefaults . standard . object ( forKey : " AllowOutsideApplicationsFolder " ) as ? Bool , shortCircuit = = true {
return
}
let bundlePath = Bundle . main . bundlePath
let applicationDirectory = NSSearchPathForDirectoriesInDomains ( FileManager . SearchPathDirectory . applicationDirectory ,
FileManager . SearchPathDomainMask . localDomainMask ,
true )
for appDir in applicationDirectory {
if bundlePath . hasPrefix ( appDir ) {
return
}
}
// C l o c k e r i s i n s t a l l e d o u t o f A p p l i c a t i o n s d i r e c t o r y
// T h i s b r e a k s s t a r t a t l o g i n ! T i m e t o s h o w a n a l e r t a n d t e r m i n a t e
showAlert ( message : " Move Clocker to the Applications folder " ,
informativeText : " Clocker must be run from the Applications folder in order to work properly. \n \n Please quit Clocker, move it to the Applications folder, and relaunch. Current folder: \( applicationDirectory ) " ,
buttonTitle : " Quit " )
// T e r m i n a t e
NSApp . terminate ( nil )
}
@IBAction open func togglePanel ( _ sender : Any ) {
let displayMode = UserDefaults . standard . integer ( forKey : CLShowAppInForeground )
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 ( )
}
open func closeFloatingWindow ( ) {
floatingWindow . window ? . close ( )
}
func statusItemForPanel ( ) -> StatusItemHandler {
return statusBarHandler
}
open func setPanelDefaults ( ) {
panelController . updateDefaultPreferences ( )
}
open func setupMenubarTimer ( ) {
statusBarHandler . setupStatusItem ( )
}
open func invalidateMenubarTimer ( _ showIcon : Bool ) {
statusBarHandler . invalidateTimer ( showIcon : showIcon , isSyncing : true )
}
}