Commit 3ca9583

Karn Wong <karn@karnwong.me>
2024-06-08 14:41:39
WIP
1 parent c0e031a
Changed files (3)
cmd/misc/timer.go
@@ -1,140 +1,177 @@
 package misc
 
 import (
-	"fmt"
-	"os"
-	"strconv"
+	"log"
 	"strings"
 	"time"
 
-	"github.com/charmbracelet/bubbles/help"
-	"github.com/charmbracelet/bubbles/key"
-	"github.com/charmbracelet/bubbles/timer"
+	"github.com/charmbracelet/bubbles/progress"
+	"github.com/charmbracelet/lipgloss"
+
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/spf13/cobra"
 )
 
-type model struct {
-	timeout  time.Duration
-	timer    timer.Model
-	keymap   keymap
-	help     help.Model
+const (
+	focusColor = "#2EF8BB"
+	breakColor = "#FF5F87"
+)
+
+var (
+	focusTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(focusColor)).MarginRight(1).SetString("Focus Mode")
+	breakTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(breakColor)).MarginRight(1).SetString("Break Mode")
+	pausedStyle     = lipgloss.NewStyle().Foreground(lipgloss.Color(breakColor)).MarginRight(1).SetString("Continue?")
+	helpStyle       = lipgloss.NewStyle().Foreground(lipgloss.Color("240")).MarginTop(2)
+)
+
+var baseTimerStyle = lipgloss.NewStyle().Padding(1, 2)
+
+type mode int
+
+const (
+	Initial mode = iota
+	Focusing
+	Paused
+	Breaking
+)
+
+type Model struct {
 	quitting bool
+
+	startTime time.Time
+
+	mode mode
+
+	focusTime time.Duration
+	breakTime time.Duration
+
+	progress progress.Model
 }
 
-type keymap struct {
-	start key.Binding
-	stop  key.Binding
-	reset key.Binding
-	quit  key.Binding
+func (m Model) Init() tea.Cmd {
+	return tea.Tick(tickInterval, tickCmd)
 }
 
-func (m model) Init() tea.Cmd {
-	return m.timer.Init()
+const tickInterval = time.Second / 2
+
+type tickMsg time.Time
+
+func tickCmd(t time.Time) tea.Msg {
+	return tickMsg(t)
 }
 
-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	switch msg := msg.(type) {
-	case timer.TickMsg:
-		var cmd tea.Cmd
-		m.timer, cmd = m.timer.Update(msg)
-		return m, cmd
-
-	case timer.StartStopMsg:
-		var cmd tea.Cmd
-		m.timer, cmd = m.timer.Update(msg)
-		m.keymap.stop.SetEnabled(m.timer.Running())
-		m.keymap.start.SetEnabled(!m.timer.Running())
-		return m, cmd
-
-	case timer.TimeoutMsg:
-		m.quitting = true
-		return m, tea.Quit
+func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	var cmds []tea.Cmd
 
+	switch msg := msg.(type) {
+	case tickMsg:
+		cmds = append(cmds, tea.Tick(tickInterval, tickCmd))
 	case tea.KeyMsg:
-		switch {
-		case key.Matches(msg, m.keymap.quit):
+		switch msg.String() {
+		case "q":
+			switch m.mode {
+			case Focusing:
+				m.mode = Paused
+				m.startTime = time.Now()
+				m.progress.FullColor = breakColor
+			case Paused:
+				m.mode = Breaking
+				m.startTime = time.Now()
+			case Breaking:
+				m.quitting = true
+				return m, tea.Quit
+			}
+		case "ctrl+c":
 			m.quitting = true
 			return m, tea.Quit
-		case key.Matches(msg, m.keymap.reset):
-			m.timer.Timeout = m.timeout
-		case key.Matches(msg, m.keymap.start, m.keymap.stop):
-			return m, m.timer.Toggle()
+		default:
+			if m.mode == Paused {
+				m.mode = Breaking
+				m.startTime = time.Now()
+			}
 		}
 	}
 
-	return m, nil
-}
+	// Update timer
+	if m.startTime.IsZero() {
+		m.startTime = time.Now()
+		m.mode = Focusing
+		cmds = append(cmds, tea.Tick(tickInterval, tickCmd))
+	}
 
-func (m model) helpView() string {
-	return "\n" + m.help.ShortHelpView([]key.Binding{
-		m.keymap.start,
-		m.keymap.stop,
-		m.keymap.reset,
-		m.keymap.quit,
-	})
-}
+	switch m.mode {
+	case Focusing:
+		if time.Since(m.startTime) > m.focusTime {
+			m.mode = Paused
+			m.startTime = time.Now()
+			m.progress.FullColor = breakColor
+		}
+	case Breaking:
+		if time.Since(m.startTime) > m.breakTime {
+			m.quitting = true
+			return m, tea.Quit
+		}
+	}
 
-func (m model) View() string {
-	// For a more detailed timer view you could read m.timer.Timeout to get
-	// the remaining time as a time.Duration and skip calling m.timer.View()
-	// entirely.
-	s := m.timer.View()
+	return m, tea.Batch(cmds...)
+}
 
-	if m.timer.Timedout() {
-		s = "All done!"
+func (m Model) View() string {
+	if m.quitting {
+		return ""
 	}
-	s += "\n"
-	if !m.quitting {
-		s = "Exiting in " + s
-		s += m.helpView()
+
+	var s strings.Builder
+
+	elapsed := time.Since(m.startTime)
+
+	var percent float64
+	switch m.mode {
+	case Focusing:
+		percent = float64(elapsed) / float64(m.focusTime)
+		s.WriteString(focusTitleStyle.String())
+		s.WriteString(elapsed.Round(time.Second).String())
+		s.WriteString("\n\n")
+		s.WriteString(m.progress.ViewAs(percent))
+		s.WriteString(helpStyle.Render("Press 'q' to skip"))
+	case Paused:
+		s.WriteString(pausedStyle.String())
+		s.WriteString("\n\nFocus time is done, time to take a break.")
+		s.WriteString(helpStyle.Render("press any key to continue.\n"))
+	case Breaking:
+		percent = float64(elapsed) / float64(m.breakTime)
+		s.WriteString(breakTitleStyle.String())
+		s.WriteString(elapsed.Round(time.Second).String())
+		s.WriteString("\n\n")
+		s.WriteString(m.progress.ViewAs(percent))
+		s.WriteString(helpStyle.Render("press 'q' to quit"))
 	}
-	return s
+
+	return baseTimerStyle.Render(s.String())
 }
 
-func createTimer(timeout time.Duration) model {
-	m := model{
-		timeout: timeout,
-		timer:   timer.NewWithInterval(timeout, time.Millisecond),
-		keymap: keymap{
-			start: key.NewBinding(
-				key.WithKeys("s"),
-				key.WithHelp("s", "start"),
-			),
-			stop: key.NewBinding(
-				key.WithKeys("s"),
-				key.WithHelp("s", "stop"),
-			),
-			reset: key.NewBinding(
-				key.WithKeys("r"),
-				key.WithHelp("r", "reset"),
-			),
-			quit: key.NewBinding(
-				key.WithKeys("q", "ctrl+c"),
-				key.WithHelp("q", "quit"),
-			),
-		},
-		help: help.New(),
-	}
-	m.keymap.start.SetEnabled(false)
+func NewModel() Model {
+	progressBar := progress.New()
+	progressBar.FullColor = focusColor
+	progressBar.SetSpringOptions(1, 1)
 
-	return m
+	return Model{
+		progress: progressBar,
+	}
 }
 
 var TimerCmd = &cobra.Command{
 	Use:   "timer",
 	Short: "Create a timer",
 	Run: func(cmd *cobra.Command, args []string) {
-		// [TODO] validate args
-		timeoutFloat64, err := strconv.ParseFloat(strings.TrimSpace(args[0]), 64)
-		if err != nil {
-			fmt.Println("Error converting string to int:", err)
-		}
-		timeout := time.Duration(timeoutFloat64 * float64(time.Second))
+		m := NewModel()
 
-		if _, err := tea.NewProgram(createTimer(timeout)).Run(); err != nil {
-			fmt.Println("Uh oh, we encountered an error:", err)
-			os.Exit(1)
+		m.focusTime = time.Duration(5 * float64(time.Second))
+		m.breakTime = time.Duration(5 * float64(time.Second))
+
+		_, err := tea.NewProgram(&m).Run()
+		if err != nil {
+			log.Fatal(err)
 		}
 	},
 }
go.mod
@@ -5,6 +5,7 @@ go 1.20
 require (
 	github.com/charmbracelet/bubbles v0.18.0
 	github.com/charmbracelet/bubbletea v0.25.0
+	github.com/charmbracelet/lipgloss v0.9.1
 	github.com/fatih/color v1.16.0
 	github.com/libp2p/go-netroute v0.2.1
 	github.com/sethvargo/go-diceware v0.3.0
@@ -17,7 +18,7 @@ require (
 
 require (
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
-	github.com/charmbracelet/lipgloss v0.9.1 // indirect
+	github.com/charmbracelet/harmonica v0.2.0 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 	github.com/google/gopacket v1.1.19 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
go.sum
@@ -5,6 +5,8 @@ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/
 github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
 github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
 github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
+github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
+github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
 github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
 github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=