diff --git a/main.go b/main.go index d855906..6f9339f 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "os/exec" "os/signal" "strconv" + "strings" "time" "github.com/charmbracelet/bubbles/progress" @@ -44,6 +45,7 @@ type model struct { fullscreen bool width int height int + err error } func initialModel(fullscreen bool, colorLeft string, colorRight string) model { @@ -53,14 +55,28 @@ func initialModel(fullscreen bool, colorLeft string, colorRight string) model { for i := range inputs { inputs[i] = textinput.New() inputs[i].CharLimit = 10 // Increase char limit to allow duration strings + if i == 0 { inputs[i].Focus() // Start focus on first input } } + validateDuration := func(text string) error { + _, err := parseDuration(text) + return err + } + + validateInt := func(text string) error { + _, err := strconv.Atoi(text) + return fmt.Errorf("invalid int input: %w", err) + } + inputs[0].Placeholder = "Interval length (minutes or duration)" + inputs[0].Validate = validateDuration inputs[1].Placeholder = "Break length (minutes or duration)" + inputs[1].Validate = validateDuration inputs[2].Placeholder = "Number of intervals" + inputs[2].Validate = validateInt return model{ inputs: inputs, @@ -79,6 +95,7 @@ func (m model) Init() tea.Cmd { // Handle Ctrl+C for graceful exit c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) + go func() { <-c fmt.Println("\nExiting...") @@ -114,15 +131,22 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.focusIndex == len(m.inputs)-1 { focusTime, err := parseDuration(m.inputs[0].Value()) if err != nil { - focusTime, _ = time.ParseDuration(m.inputs[0].Value() + "m") + m.err = fmt.Errorf("error parsing focus time duration: %w", err) + return m, nil } breakTime, err := parseDuration(m.inputs[1].Value()) if err != nil { - breakTime, _ = time.ParseDuration(m.inputs[1].Value() + "m") + m.err = fmt.Errorf("error parsing break time duration: %w", err) + return m, nil + } + + m.intervals, err = strconv.Atoi(m.inputs[2].Value()) + if err != nil { + m.err = fmt.Errorf("error parsing interval: %w", err) + return m, nil } - m.intervals = atoi(m.inputs[2].Value()) m.focusTime = focusTime m.breakTime = breakTime m.remaining = focusTime @@ -203,24 +227,41 @@ func tick() tea.Cmd { // View rendering for input and timer screens func (m model) View() string { + var s strings.Builder + switch m.currentScreen { case inputScreen: - return m.inputScreenView() + s.WriteString(m.inputScreenView()) case timerScreen: - return m.timerScreenView() + s.WriteString(m.timerScreenView()) } - return "" + + if m.err != nil { + s.WriteString(fmt.Sprintf("\n\nError: %v", m.err)) + } + + return s.String() } // View for input screen func (m model) inputScreenView() string { - var builder string - builder = "Enter your Pomodoro settings:\n\n" + var builder strings.Builder + + builder.WriteString("Enter your Pomodoro settings:\n\n") + for i := range m.inputs { - builder += m.inputs[i].View() + "\n" + builder.WriteString(m.inputs[i].View()) + + if m.inputs[i].Value() != "" && m.inputs[i].Err != nil { + builder.WriteString(fmt.Sprintf("Error: %v", m.inputs[i].Err)) + } + + builder.WriteString("\n") } - builder += "\nUse TAB to navigate, ENTER to start." - return builder + + builder.WriteString("\nUse TAB to navigate, ENTER to start.") + + return builder.String() } // View for timer screen with optional fullscreen centering @@ -236,6 +277,7 @@ func (m model) timerScreenView() string { if m.fullscreen { return lipgloss.NewStyle().Width(m.width).Height(m.height).Align(lipgloss.Center, lipgloss.Center).Render(timerView) } + return timerView } @@ -256,6 +298,7 @@ func (m *model) runCommands(commands []string) { cmd := exec.Command("sh", "-c", cmdStr) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { fmt.Printf("Error running command: %v\n", err) } @@ -267,13 +310,13 @@ func parseDuration(input string) (time.Duration, error) { if minutes, err := strconv.Atoi(input); err == nil { return time.Duration(minutes) * time.Minute, nil } - return time.ParseDuration(input) -} -// Helper to convert string to int -func atoi(s string) int { - n, _ := strconv.Atoi(s) - return n + d, err := time.ParseDuration(input) + if err != nil { + return d, fmt.Errorf("error parsing duration: %w", err) + } + + return d, nil } func main() { @@ -299,26 +342,29 @@ func main() { Usage: "Enable fullscreen mode", }, &cli.DurationFlag{ - Name: "focus", - Usage: "Focus time duration (default prompt for input)", + Name: "focus", + Usage: "Focus time duration", + DefaultText: "prompt for input", }, &cli.DurationFlag{ - Name: "break", - Usage: "Break time duration (default prompt for input)", + Name: "break", + Usage: "Break time duration", + DefaultText: "prompt for input", }, &cli.IntFlag{ - Name: "intervals", - Usage: "Number of intervals (default prompt for input)", + Name: "intervals", + Usage: "Number of intervals", + DefaultText: "prompt for input", }, &cli.StringFlag{ - Name: "color-left", - Usage: "Left color for progress bar", - DefaultText: "#ffdd57", + Name: "color-left", + Usage: "Left color for progress bar", + Value: "#ffdd57", }, &cli.StringFlag{ - Name: "color-right", - Usage: "Right color for progress bar", - DefaultText: "#57ddff", + Name: "color-right", + Usage: "Right color for progress bar", + Value: "#57ddff", }, &cli.BoolFlag{ Name: "version", @@ -352,6 +398,7 @@ func main() { // Start tea program p := tea.NewProgram(m, tea.WithAltScreen()) _, err := p.Run() + return err }, }