180 lines
5.4 KiB
Swift
180 lines
5.4 KiB
Swift
import AppKit
|
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
// Configuration
|
|
var isDebugMode: Bool = false
|
|
var workTimerDuration: Int {
|
|
return isDebugMode ? 60 : 1500 // Debug: 1 min, Normal: 25 min
|
|
}
|
|
var countdownDuration: Int = 300 // 5 minutes of escalating flashes
|
|
|
|
// Status bar
|
|
var statusItem: NSStatusItem!
|
|
var statusMenuItem: NSMenuItem!
|
|
var debugMenuItem: NSMenuItem!
|
|
var launchAtLoginMenuItem: NSMenuItem!
|
|
var settingsMenuBuilder: SettingsMenuBuilder!
|
|
|
|
// Overlay windows
|
|
var overlayWindows: [NSWindow] = []
|
|
var countdownWindows: [NSWindow] = []
|
|
var countdownLabels: [NSTextField] = []
|
|
|
|
// Timers
|
|
var countdownTimer: Timer?
|
|
var breakTimer: Timer?
|
|
|
|
// State
|
|
var remainingSeconds: Int = 0
|
|
var isSolid = false
|
|
var isPaused = false
|
|
var currentSuggestion = ""
|
|
var breakRemainingSeconds: Int = 300
|
|
var breakCountdownLabels: [NSTextField] = []
|
|
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
remainingSeconds = workTimerDuration
|
|
setupStatusBar()
|
|
setupWindows()
|
|
startCountdown()
|
|
NotificationManager.shared.requestPermission()
|
|
NotificationManager.shared.postCycleStarted(durationMinutes: workTimerDuration / 60)
|
|
|
|
NotificationCenter.default.addObserver(
|
|
self, selector: #selector(settingsDidChange),
|
|
name: Settings.didChangeNotification, object: nil
|
|
)
|
|
}
|
|
|
|
@objc private func settingsDidChange() {
|
|
let color = Settings.shared.gradientColor
|
|
for label in countdownLabels {
|
|
label.textColor = color
|
|
}
|
|
for window in overlayWindows {
|
|
guard let view = window.contentView as? EdgeGradientView else { continue }
|
|
view.color = color
|
|
view.needsDisplay = true
|
|
}
|
|
}
|
|
|
|
func startCountdown() {
|
|
updateStatusBar()
|
|
|
|
countdownTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
|
|
guard let self = self else { timer.invalidate(); return }
|
|
if self.isPaused { return }
|
|
self.remainingSeconds -= 1
|
|
self.updateStatusBar()
|
|
|
|
if self.remainingSeconds <= self.countdownDuration && self.remainingSeconds > 0 {
|
|
let s = self.remainingSeconds
|
|
|
|
if s > 60 {
|
|
let shouldBlink: Bool
|
|
if s > 180 {
|
|
shouldBlink = (s % 60 == 0)
|
|
} else {
|
|
shouldBlink = (s % 30 == 0)
|
|
}
|
|
if shouldBlink { self.triggerBlink() }
|
|
} else if s > 5 {
|
|
let shouldBlink: Bool
|
|
if s > 30 {
|
|
shouldBlink = (s % 10 == 0)
|
|
} else if s > 10 {
|
|
shouldBlink = (s % 5 == 0)
|
|
} else {
|
|
shouldBlink = (s % 2 == 0)
|
|
}
|
|
if shouldBlink { self.triggerBlink() }
|
|
} else {
|
|
if !self.isSolid { self.makeSolid() }
|
|
}
|
|
|
|
if s <= 10 {
|
|
self.showCountdownPills()
|
|
self.updateCountdownLabels()
|
|
}
|
|
}
|
|
|
|
if self.remainingSeconds <= 0 {
|
|
self.showFullOverlay()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func togglePause() {
|
|
isPaused.toggle()
|
|
updateStatusBar()
|
|
}
|
|
|
|
@objc func pauseFromOverlay() {
|
|
isPaused = true
|
|
breakTimer?.invalidate()
|
|
updateStatusBar()
|
|
resetOverlayToGradient()
|
|
breakCountdownLabels.removeAll()
|
|
}
|
|
|
|
@objc func toggleDebugMode() {
|
|
isDebugMode.toggle()
|
|
debugMenuItem.state = isDebugMode ? .on : .off
|
|
|
|
countdownTimer?.invalidate()
|
|
breakTimer?.invalidate()
|
|
breakCountdownLabels.removeAll()
|
|
remainingSeconds = workTimerDuration
|
|
isSolid = false
|
|
isPaused = false
|
|
|
|
resetOverlayToGradient()
|
|
hideCountdownPills()
|
|
for window in countdownWindows { window.orderFrontRegardless() }
|
|
|
|
startCountdown()
|
|
}
|
|
|
|
@objc func toggleLaunchAtLogin() {
|
|
if LaunchAgent.isInstalled() {
|
|
LaunchAgent.uninstall()
|
|
launchAtLoginMenuItem.state = .off
|
|
} else {
|
|
LaunchAgent.install()
|
|
launchAtLoginMenuItem.state = .on
|
|
}
|
|
}
|
|
|
|
@objc func closePommedoro() {
|
|
countdownTimer?.invalidate()
|
|
breakTimer?.invalidate()
|
|
for window in overlayWindows { window.orderOut(nil) }
|
|
for window in countdownWindows { window.orderOut(nil) }
|
|
NSApp.terminate(nil)
|
|
}
|
|
|
|
@objc func resumePommedoro() {
|
|
isPaused = false
|
|
remainingSeconds = workTimerDuration
|
|
isSolid = false
|
|
breakTimer?.invalidate()
|
|
breakCountdownLabels.removeAll()
|
|
|
|
resetOverlayToGradient()
|
|
hideCountdownPills()
|
|
for window in countdownWindows { window.orderFrontRegardless() }
|
|
|
|
startCountdown()
|
|
NotificationManager.shared.postCycleStarted(durationMinutes: workTimerDuration / 60)
|
|
}
|
|
|
|
@objc func dismissSuggestion() {
|
|
SuggestionManager.shared.dismiss(suggestion: currentSuggestion)
|
|
resumePommedoro()
|
|
}
|
|
|
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
|
return false
|
|
}
|
|
}
|