/* Pominal Copyright (C) 2020 Daniel Anglin This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "time" "github.com/rivo/tview" ) type Pominal struct { // workSession is a counter for work sessions. workSession int // maxWorkSessions sets the number of work sessions to complete // before running the countdown timer for each long break. maxWorkSessions int // cycle represents a Pominal cycle. A Pominal cycle increments after // every long break. cycle int // countdown is the decrementing counter for Pominal. countdown float64 // work is the time (represented in seconds) for the work sessions. work float64 // shortBreak is the time (represented in seconds) // for the short break sessions. shortBreak float64 // longBreak is the time (represented in seconds) // for the long break sessions. longBreak float64 // workNotificationMsg is the desktop notification // message when a work session has started. workNotificationMsg string // shortBreakNotificationMsg is the desktop notification // message when a short break session has started. shortBreakNotificationMsg string // longBreakNotificationMsg is the desktop notification // message when a long break session has started. longBreakNotificationMsg string // currentNotificationMsg is used for desktop notifications. // This will be equal to workNotificationMsg, shortBreakNotificationMsg // or longBreakNotificationMsg depending on the session in progress. currentNotificationMsg string // label labels Pominal based on the session. label string // stopChan is a no-data channel used to stop Pominal. stopChan chan struct{} } // NewPominal creates a new pominal instance func newPominal(config PominalConfig) (Pominal, error) { initWorkSession := 1 initCycle := 1 maxWorkSessions := setMaxWorkSessions(config.MaxWorkSessions) work, err := setSessionTime(config.Sessions.Work.Duration, defaultWorkTime) if err != nil { return Pominal{}, err } shortBreak, err := setSessionTime(config.Sessions.ShortBreak.Duration, defaultShortBreakTime) if err != nil { return Pominal{}, err } longBreak, err := setSessionTime(config.Sessions.LongBreak.Duration, defaultLongBreakTime) if err != nil { return Pominal{}, err } p := Pominal{ workSession: initWorkSession, maxWorkSessions: maxWorkSessions, cycle: initCycle, work: work, shortBreak: shortBreak, longBreak: longBreak, workNotificationMsg: config.Sessions.Work.NotificationMessage, shortBreakNotificationMsg: config.Sessions.ShortBreak.NotificationMessage, longBreakNotificationMsg: config.Sessions.LongBreak.NotificationMessage, stopChan: nil, } return p, nil } // Run Pominal func (p *Pominal) Run(infoUI, timerUI *tview.TextView) { p.stopChan = make(chan struct{}) p.UpdateSession() drawInfo(infoUI, p.cycle, p.workSession, p.maxWorkSessions, p.label) // notifier is used for desktop notifications notifier := initNotifier() t := time.NewTicker(time.Second) infinite: for { select { case <-p.stopChan: t.Stop() break infinite case <-t.C: p.countdown-- if p.countdown < 0 { t.Stop() p.UpdateSession() drawInfo( infoUI, p.cycle, p.workSession, p.maxWorkSessions, p.label, ) desktopAlert(p.label, p.currentNotificationMsg, notifier) time.Sleep(time.Second) t = time.NewTicker(time.Second) } else { drawTimer(timerUI, p.countdown) } } } } // Stop sends a signal to the Run method to stop the timer. // Afterwards the process returns from the Run method. func (p *Pominal) Stop() { close(p.stopChan) } // UpdateSession resets Pominal's countdown // based on the next session. If a 'short break' // session is over the work session counter is incremented. // If a 'long break' session is over then the Pominal cycle // is incremented and the work session counter is reset // to 1. func (p *Pominal) UpdateSession() { switch p.label { case workTimerLabel: if p.workSession >= p.maxWorkSessions { p.countdown = p.longBreak p.label = longBreakTimerLabel p.currentNotificationMsg = p.longBreakNotificationMsg } else { p.countdown = p.shortBreak p.label = shortBreakTimerLabel p.currentNotificationMsg = p.shortBreakNotificationMsg } case shortBreakTimerLabel: p.workSession++ p.countdown = p.work p.label = workTimerLabel p.currentNotificationMsg = p.workNotificationMsg case longBreakTimerLabel: p.cycle++ p.workSession = 1 p.countdown = p.work p.label = workTimerLabel p.currentNotificationMsg = p.workNotificationMsg default: p.countdown = p.work p.label = workTimerLabel p.currentNotificationMsg = p.workNotificationMsg } } // setSessionTime parses the configured session time and // returns the session time represented in seconds. // If the session time is unconfigured then the default for // that particular session is returned. // If the value is less than the minimum session time // then the minimum is returned instead. // An error is returned if the configured session time cannot be parsed. func setSessionTime(s, defaultSessionTime string) (float64, error) { if len(s) == 0 { s = defaultSessionTime } sTime, err := time.ParseDuration(s) if err != nil { return float64(0), err } sTimeInSecs := sTime.Seconds() if sTimeInSecs < minimumSessionTime { sTimeInSecs = minimumSessionTime } return sTimeInSecs, nil } // setMaxWorkSessions checks and returns the configured // number of work session per Pominal cycle. // The default value is returned if the argument is 0 // or less. func setMaxWorkSessions(w int) int { if w <= 0 { w = defaultMaxWorkSessions } return w }