A MacOS menu bar applet that displays the current lunar phase.
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.

193 lines
5.6 KiB

//
// AppDelegate.swift
// LunaMac
//
// Created by Claire Davis on 9/21/22.
// Copyright © 2022 A Better Geek. All rights reserved.
//
import Cocoa
import Foundation
import CoreFoundation
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// make sure applet stays in memory
var statusBarItem: NSStatusItem?
// 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" : "🌙"
]
]
func applicationDidFinishLaunching(_ aNotification: Notification) {
updateIcon()
}
@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? ?? "🌙"
// 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(
withTitle: "Update moon phase",
action: #selector(AppDelegate.updateIcon),
keyEquivalent: ""
)
statusBarMenu.addItem(
withTitle: "Quit LunaMac",
action: #selector(AppDelegate.quitApp),
keyEquivalent: ""
)
}
// 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
}
}