redesign around termui

This commit is contained in:
Kevin Schoon 2018-01-26 23:07:38 +08:00
parent 79a7382a07
commit 55f2a85378
5 changed files with 228 additions and 53 deletions

View File

@ -31,7 +31,9 @@ func start(path *string) func(*cli.Cmd) {
}
runner, err := NewTaskRunner(task, db)
maybe(err)
maybe(runner.Run())
runner.Start()
startUI(runner)
//maybe(runner.Run())
}
}
}

117
task.go
View File

@ -1,19 +1,19 @@
package main
import (
"fmt"
"github.com/gosuri/uilive"
"time"
)
type TaskRunner struct {
count int
state State
task *Task
store *Store
writer *uilive.Writer
timer *time.Timer
ticker *time.Ticker
started time.Time
pause chan bool
toggle chan bool
notifier Notifier
duration time.Duration
}
func NewTaskRunner(task *Task, store *Store) (*TaskRunner, error) {
@ -25,22 +25,98 @@ func NewTaskRunner(task *Task, store *Store) (*TaskRunner, error) {
tr := &TaskRunner{
task: task,
store: store,
state: State(0),
pause: make(chan bool),
toggle: make(chan bool),
notifier: NewLibNotifier(),
writer: uilive.New(),
timer: time.NewTimer(task.Duration),
ticker: time.NewTicker(RefreshInterval),
duration: task.Duration,
}
tr.writer.Start()
return tr, nil
}
func (t *TaskRunner) Start() {
go t.run()
}
func (t *TaskRunner) TimeRemaining() time.Duration {
return (t.duration - time.Since(t.started)).Truncate(time.Second)
}
func (t *TaskRunner) run() error {
for t.count < t.task.NPomodoros {
// Create a new pomodoro where we
// track the start / end time of
// of this session.
pomodoro := &Pomodoro{}
// Start this pomodoro
pomodoro.Start = time.Now()
// Set state to RUNNING
t.state = RUNNING
// Create a new timer
timer := time.NewTimer(t.duration)
// Record our started time
t.started = pomodoro.Start
loop:
select {
case <-timer.C:
// Timer ended so now we set the
// state as BREAKING
t.state = BREAKING
case <-t.toggle:
// Catch any toggles when we
// are not expecting them
goto loop
case <-t.pause:
timer.Stop()
// Record the remaining time of this pomodoro
remaining := t.TimeRemaining()
// Change state to PAUSED
t.state = PAUSED
// Wait for the user to press [p]
<-t.pause
// Resume the timer at previous
// remaining time
timer.Reset(remaining)
t.started = time.Now()
t.duration = remaining
// Restore state to RUNNING
t.state = RUNNING
goto loop
}
if t.count+1 == t.task.NPomodoros {
break
}
// User concludes the break
<-t.toggle
pomodoro.End = time.Now()
err := t.store.CreatePomodoro(t.task.ID, *pomodoro)
if err != nil {
return err
}
t.duration = t.task.Duration
t.count++
}
t.state = COMPLETE
return nil
}
func (t *TaskRunner) Toggle() {
t.toggle <- true
}
func (t *TaskRunner) Pause() {
t.pause <- true
}
/*
func (t *TaskRunner) Run() error {
for t.count < t.task.NPomodoros {
// ASCII spinner
wheel := &Wheel{}
// This pomodoro
pomodoro := &Pomodoro{}
prompt("press enter to begin")
//prompt("press enter to begin")
// Emit a desktop notification
// that the task is beginning.
t.notifier.Begin(t.count, *t.task)
@ -53,21 +129,22 @@ func (t *TaskRunner) Run() error {
loop:
select {
case <-t.ticker.C:
t.updateUI(Message{
t.msgCh <- Message{
Start: pomodoro.Start,
Duration: t.task.Duration,
Pomodoros: t.task.NPomodoros,
Wheel: wheel,
CurrentPomodoro: t.count,
})
State: RUNNING,
}
goto loop
case <-t.timer.C:
// Send a notification for the
// user to take a break. We record
// how long it actually takes for
// them to initiate the break.
t.notifier.Break(*t.task)
prompt("press enter to take a break")
//t.notifier.Break(*t.task)
//prompt("press enter to take a break")
// Record the task as complete
pomodoro.End = time.Now()
// Record the session in the db
@ -81,14 +158,4 @@ func (t *TaskRunner) Run() error {
}
return nil
}
func (t *TaskRunner) updateUI(msg Message) {
fmt.Fprintf(
t.writer,
"%s %s remaining [ pomodoro %d/%d ]\n",
msg.Wheel,
(msg.Duration - time.Since(msg.Start)).Truncate(time.Second),
msg.CurrentPomodoro,
msg.Pomodoros,
)
}
*/

36
task_test.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"fmt"
"io/ioutil"
"testing"
"time"
)
func TestTaskRunner(t *testing.T) {
path, _ := ioutil.TempDir("/tmp", "")
store, err := NewStore(path)
if err != nil {
t.Error(err)
}
err = initDB(store)
if err != nil {
t.Error(err)
}
runner, err := NewTaskRunner(&Task{
Duration: time.Second * 2,
NPomodoros: 2,
Message: fmt.Sprint("Test Task"),
}, store)
if err != nil {
t.Error(err)
}
runner.Start()
runner.Toggle()
runner.Toggle()
runner.Toggle()
runner.Toggle()
}

View File

@ -10,6 +10,29 @@ import (
"github.com/kevinschoon/pomo/libnotify"
)
type State int
func (s State) String() string {
switch s {
case RUNNING:
return "RUNNING"
case BREAKING:
return "BREAKING"
case COMPLETE:
return "COMPLETE"
case PAUSED:
return "PAUSED"
}
return ""
}
const (
RUNNING State = iota + 1
BREAKING
COMPLETE
PAUSED
)
// RefreshInterval is the frequency at which
// the display is updated.
const RefreshInterval = 800 * time.Millisecond
@ -21,6 +44,7 @@ type Message struct {
Duration time.Duration
Pomodoros int
CurrentPomodoro int
State State
Wheel *Wheel
}
@ -125,9 +149,7 @@ func (p Pomodoro) Duration() time.Duration {
// notification. On Linux this libnotify.
// TODO: OSX, Windows(?)
type Notifier interface {
Begin(int, Task) error
Break(Task) error
Finish(Task) error
Notify(string, string) error
}
// LibNotifier implements a Linux
@ -150,28 +172,12 @@ func NewLibNotifier() Notifier {
return ln
}
func (ln LibNotifier) Begin(count int, t Task) error {
return ln.client.Notify(libnotify.Notification{
Title: t.Message,
Body: fmt.Sprintf("Task is starting (%d/%d pomodoros)", count, t.NPomodoros),
Icon: ln.iconPath,
})
}
func (ln LibNotifier) Break(t Task) error {
return ln.client.Notify(libnotify.Notification{
Title: t.Message,
Urgency: "critical",
Body: fmt.Sprintf("Time to take a break!\nPress enter at the console to initiate the break."),
Icon: ln.iconPath,
})
}
func (ln LibNotifier) Finish(t Task) error {
return ln.client.Notify(libnotify.Notification{
Title: t.Message,
Urgency: "critical",
Body: fmt.Sprintf("This task session is complete!"),
Icon: ln.iconPath,
})
func (ln LibNotifier) Notify(title, body string) error {
return ln.client.Notify(
libnotify.Notification{
Title: title,
Body: body,
Icon: ln.iconPath,
},
)
}

64
ui.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"fmt"
"github.com/gizak/termui"
)
func getText(runner *TaskRunner) *termui.Par {
par := termui.NewPar("")
switch runner.state {
case RUNNING:
par.Text = fmt.Sprintf(
"%s %s remaining [ pomodoro %d/%d ]",
"X",
runner.TimeRemaining(),
runner.count,
runner.task.NPomodoros,
)
case BREAKING:
par.Text = "Time to take a break.\nPress [enter] to begin the next Pomodoro!"
case PAUSED:
par.Text = "Press p to resume"
case COMPLETE:
par.Text = "Press q to quit"
}
par.Height = 8
par.Width = 55
par.BorderLabel = fmt.Sprintf("Pomo - %s", runner.state)
par.BorderLabelFg = termui.ColorWhite
par.BorderFg = termui.ColorRed
if runner.state == RUNNING {
par.BorderFg = termui.ColorGreen
}
return par
}
func startUI(runner *TaskRunner) {
err := termui.Init()
if err != nil {
panic(err)
}
defer termui.Close()
termui.Handle("/timer/1s", func(termui.Event) {
termui.Render(getText(runner))
})
termui.Handle("/sys/kbd/<enter>", func(termui.Event) {
runner.Toggle()
termui.Render(getText(runner))
})
termui.Handle("/sys/kbd/p", func(termui.Event) {
runner.Pause()
termui.Render(getText(runner))
})
termui.Handle("/sys/kbd/q", func(termui.Event) {
termui.StopLoop()
})
termui.Loop()
}