Thursday, March 30, 2017

PomodoroTimer

Recently I heard about Pomodoro technique and decided to try it out. Basically it's a method for increasing productivity by focusing on work for 25 minutes, having a break, and then repeating.

There is a ton of different ways to measure those 25 mins and breaks. If you type "25 min timer" in Google, you will see countdown above search results. So thoughtful.

I wanted a light and discrete timer but noticeable when it dings. Something like Thyme + ding. I'm sure that there are plenty of nice timers on Internet but till I find the right one, I can make my own and learn something in the process.

Few tomatoes later, and there it is, ticking in navigation bar: PomodoroTimer


When time reaches zero, a short sound plays and notification pops up:


The timer starts when the app is open (cmd + space > PomodoroTimer). And it's killed when tapped on it.

In first tomato I created a new Xcode project from macOS > Cocoa Application template. By default, template app shows blank app window. One way of hiding it is to just delete it from MainMenu.xib. Standard app menu is also unwanted. To disable app menu, new entry Application is agent (UIElement) = YES must be added to Info.plist.

"Blow" is one of sounds located in /System/Library/Sounds.The rest of the code is pretty self explanatory:
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    
    private var timer: Timer?
    private let statusItem = NSStatusBar.system().statusItem(withLength: 42)

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        statusItem.action = #selector(terminate)
        
        let startDate = Date()
        let pomodoroInterval: Double = 25 * 60
        
        self.statusItem.title = formatTime(seconds: pomodoroInterval)
        
        timer = Timer.scheduledTimer(withTimeInterval: 0.5,
                                     repeats: true,
                                     block: { [weak self] timer in
            guard let `self` = self else { return }
                                        
            let elapsedTime = Date().timeIntervalSince(startDate)
            let remainingTime = pomodoroInterval - elapsedTime
            
            if remainingTime < 0 {
                timer.invalidate()
                let notification = NSUserNotification()
                notification.title = "Pomodoro!"
                notification.soundName = "Blow"
                NSUserNotificationCenter.default.deliver(notification)
            }
            else {
                self.statusItem.title = self.formatTime(seconds: remainingTime)
            }
        })
    }
    
    func formatTime(seconds: TimeInterval) -> String {
        let minutes = Int(seconds / 60)
        let remainingSeconds = Int(seconds) % 60
        return String(format:"%02d:%02d", minutes, remainingSeconds)
    }

    func terminate() {
        timer?.invalidate()
        NSApp.terminate(self)
    }
}
github   -   binary