// // AppDelegate.swift // LunaMac // // Created by Claire Davis on 9/21/22. // Copyright © 2022 A Better Geek. All rights reserved. // /* ====== TO DO ======= * auto-update at a set interval * make help menu item work (show the window) * fix alignment of status bar icon (it's too high) */ import Cocoa import Foundation import CoreFoundation @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { // static array of possible lunar values let phaseArray:[String:[String:NSString]] = [ "new" : [ "nice" : "New Moon", "start" : "0", "end" : "1", "icon" : "🌑" ], "waxc" : [ "nice" : "Waxing Crescent", "start" : "1", "end" : "6.38264692644", "icon" : "🌒" ], "firstq" : [ "nice" : "First Quarter", "start" : "6.38264692644", "end" : "8.38264692644", "icon" : "🌓" ], "waxg" : [ "nice" : "Waxing Gibbous", "start" : "8.38264692644", "end" : "13.76529385288", "icon" : "🌔" ], "full" : [ "nice" : "Full Moon", "start" : "13.76529385288", "end" : "15.76529385288", "icon" : "🌕" ], "wang" : [ "nice" : "Waning Gibbous", "start" : "15.76529385288", "end" : "21.14794077932", "icon" : "🌖" ], "lastq" : [ "nice" : "Last Quarter", "start" : "21.14794077932", "end" : "23.14794077932", "icon" : "🌗" ], "wanc" : [ "nice" : "Waning Crescent", "start" : "23.14794077932", "end" : "28.53058770576", "icon" : "🌘" ], "new2" : [ "nice" : "New Moon", "start" : "28.53058770576", "end" : "29.53058770576", "icon" : "🌑" ], "default" : [ "nice" : "Failed to calculate.", "icon" : "🌙" ] ] @IBAction func clickClick(_ sender: NSButtonCell) { // show changelog showLog() } // make sure applet stays in memory var statusBarItem: NSStatusItem? // create empty timer for event loop var lunaTimer = Timer() // is the info box open? var infoOpen = false // is the changelog open? var logOpen = false func applicationDidFinishLaunching(_ aNotification: Notification) { // build the icon and menu updateIcon() // listen for theme changes (dark and light) themeListener() // start the the timer for keeping the icon updated automatically startTimer() } // start the timer for auto-updating the icon func startTimer() { lunaTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(updateIcon), userInfo: nil, repeats: true) RunLoop.current.add(lunaTimer, forMode: RunLoop.Mode.common) } @objc func buildMenu(key: String = "default") { // get system-wide menu bar let statusBar = NSStatusBar.system // set icon size to square statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength) // set icon using emoji //statusBarItem?.button?.title = phaseArray[key]?["icon"] as String? ?? "🌙" var suffix = "" // check for dark mode and set the right image file if #available(OSX 10.14, *) { let darkMode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") if darkMode == "Dark" { suffix += "-dark" } } // set image filename let imgName = key + suffix + ".png" statusBarItem?.button?.image = NSImage(named: imgName) // create a menu for the icon let statusBarMenu = NSMenu(title: "LunaMac Menu") // add the menu to the menu bar icon statusBarItem?.menu = statusBarMenu let topTxt = phaseArray["default"]?["nice"] as String? ?? "" // add menu items to the menu if (!topTxt.isEmpty) { statusBarMenu.addItem( withTitle: phaseArray[key]?["nice"] as String? ?? topTxt, action: nil, keyEquivalent: "" ) } statusBarMenu.addItem(NSMenuItem.separator()) statusBarMenu.addItem( withTitle: "Update moon phase", action: #selector(AppDelegate.updateIcon), keyEquivalent: "" ) statusBarMenu.addItem(NSMenuItem.separator()) statusBarMenu.addItem( withTitle: "About LunaMac...", action: #selector(AppDelegate.showAbout), keyEquivalent: "" ) statusBarMenu.addItem( withTitle: "Quit LunaMac", action: #selector(AppDelegate.quitApp), keyEquivalent: "" ) } @objc func showAbout() { if (!infoOpen) { // create infobox window object let aboutWin = NSWindowController(windowNibName: "InfoBox") aboutWin.loadWindow() } NSApp.activate(ignoringOtherApps: true) } @objc func showLog() { if (!logOpen) { // create changelog window object // this creates duplicate windows let logWin = NSWindowController(windowNibName: "Changelog") logWin.loadWindow() } NSApp.activate(ignoringOtherApps: true) } // update menu bar icon @objc func updateIcon() { // current system time in UTC let sysDate = Date() // get baseline date // let baseDateStr = "2000-01-06 18:14:00 +0000" // create date object from baseDate // date components var bdc = DateComponents() bdc.year = 2000 bdc.month = 1 bdc.day = 6 bdc.hour = 18 bdc.minute = 14 // create date object let ucal = Calendar(identifier: .gregorian) let baseDate = ucal.date(from: bdc) // days in a lunar cycle let lunarDays = 29.53058770576 // seconds in a lunar cycle let lunarSecs = lunarDays * (24 * 60 * 60) // calculate seconds between sysDate and baseDate // if baseDate is nil, default to current system time let totalSecs = sysDate.timeIntervalSince(baseDate ?? Date()) if (totalSecs.sign != .minus) { // a positive number is valid // % calculates seconds of current cycle let currSecs = totalSecs.truncatingRemainder(dividingBy: lunarSecs) let currFrac = currSecs / lunarSecs let currDays = currFrac * lunarDays var newArr = [String:[String:NSString]]() newArr = phaseArray.filter{findPhase(key: $0.key, val: $0.value, start: $0.value["start"]?.doubleValue ?? 0, end: $0.value["end"]?.doubleValue ?? 0, curr: currDays)} // we want newArr[0].value which is [String:NSString] let theKey = newArr.first!.key buildMenu(key: theKey) } else { // if dateDiff is negative, something went wrong. buildMenu() } } @objc func findPhase(key: String, val: [String:NSString], start: Double, end: Double, curr: Double) -> Bool { if ((curr >= start) && (curr <= end)) { return true } else { return false } } // quit application @objc func quitApp() { NSApp.terminate(self) } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } func themeListener() { DistributedNotificationCenter.default.addObserver( self, selector: #selector(updateIcon), name: .AppleInterfaceThemeChangedNotification, object: nil ) } } extension Notification.Name { static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification") }