pommedoro/Sources/Pommedoro/AppDelegate.swift

164 lines
4.9 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!
// 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)
}
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 {
// 5:00 to 3:00 -> every 60 seconds, 3:00 to 1:00 -> every 30 seconds
let shouldBlink: Bool
if s > 180 {
shouldBlink = (s % 60 == 0)
} else {
shouldBlink = (s % 30 == 0)
}
if shouldBlink { self.triggerBlink() }
} else if s > 5 {
// Last minute: same as debug mode escalation
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
}
}