Lucky manta ray

This commit is contained in:
2026-02-02 19:38:02 +02:00
parent c24ef1cc2f
commit d1307a75b9
5 changed files with 363 additions and 25 deletions

View File

@@ -19,6 +19,7 @@ const (
viewMain viewMode = iota
viewForm
viewConfirmDelete
viewConfig
)
// Messages
@@ -53,17 +54,55 @@ type Model struct {
// Form state
form *formModel
// Config state
config *configModel
settings db.Settings
// Error/status
statusMsg string
}
func NewModel(store *db.Store, sched *scheduler.Scheduler, pl *player.Player) Model {
return Model{
m := Model{
store: store,
scheduler: sched,
player: pl,
now: time.Now(),
settings: store.LoadSettings(),
}
m.applySettings()
return m
}
// applySettings updates runtime styles and durations from current settings.
func (m *Model) applySettings() {
ClockStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorClock)).
Bold(true)
ClockAlarmStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorAlarm)).
Bold(true)
AlarmEnabledStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorClock))
TitleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorClock)).
Bold(true).
Underline(true)
FormActiveStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorClock)).
Bold(true)
AlarmFiringStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorAlarm)).
Bold(true).
Blink(true)
FormErrorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color(m.settings.ColorAlarm))
}
func (m Model) Init() tea.Cmd {
@@ -123,7 +162,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.refreshAlarms()
return m, tea.Batch(
listenForAlarms(m.scheduler),
autoTimeoutCmd(),
autoTimeoutCmd(m.autoTimeoutDuration()),
)
case snoozeFireMsg:
@@ -132,12 +171,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.snoozeUntil = time.Time{}
m.snoozeName = ""
m.startFiring(&alarm)
return m, autoTimeoutCmd()
return m, autoTimeoutCmd(m.autoTimeoutDuration())
case autoTimeoutMsg:
if m.firingAlarm != nil {
m.player.Stop()
m.statusMsg = "Alarm auto-dismissed after 5 minutes"
m.statusMsg = fmt.Sprintf("Alarm auto-dismissed after %d minutes", m.settings.TimeoutMinutes)
m.firingAlarm = nil
m.snoozeCount = 0
}
@@ -175,11 +214,12 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case "s":
m.player.Stop()
alarm := *m.firingAlarm
dur := m.snoozeDuration()
m.firingAlarm = nil
m.snoozeUntil = time.Now().Add(snoozeDuration)
m.snoozeUntil = time.Now().Add(dur)
m.snoozeName = alarm.Name
m.statusMsg = fmt.Sprintf("Snoozed '%s' for 5 minutes", alarm.Name)
return m, snoozeCmd(alarm, snoozeDuration)
m.statusMsg = fmt.Sprintf("Snoozed '%s' for %d minutes", alarm.Name, m.settings.SnoozeMinutes)
return m, snoozeCmd(alarm, dur)
}
return m, nil
}
@@ -189,6 +229,11 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m.form.HandleKey(msg, &m)
}
// Config mode
if m.mode == viewConfig && m.config != nil {
return m.config.HandleKey(msg, &m)
}
// Confirm delete mode
if m.mode == viewConfirmDelete {
switch key {
@@ -249,18 +294,31 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.mode = viewConfirmDelete
m.statusMsg = fmt.Sprintf("Delete '%s'? (y/n)", m.alarms[m.cursor].Name)
}
case "c":
m.config = newConfigModel(m.settings)
m.mode = viewConfig
m.statusMsg = ""
}
return m, nil
}
const snoozeDuration = 5 * time.Minute
const autoTimeoutDuration = 5 * time.Minute
func (m *Model) snoozeDuration() time.Duration {
return time.Duration(m.settings.SnoozeMinutes) * time.Minute
}
func (m *Model) autoTimeoutDuration() time.Duration {
return time.Duration(m.settings.TimeoutMinutes) * time.Minute
}
func (m *Model) startFiring(alarm *db.Alarm) {
m.firingAlarm = alarm
m.firingBlink = true
m.firingStart = time.Now()
m.player.PlayLoop(alarm.SoundPath)
sound := alarm.SoundPath
if sound == "default" || sound == "" {
sound = m.settings.DefaultSound
}
m.player.PlayLoop(sound)
}
func snoozeCmd(alarm db.Alarm, after time.Duration) tea.Cmd {
@@ -270,9 +328,9 @@ func snoozeCmd(alarm db.Alarm, after time.Duration) tea.Cmd {
}
}
func autoTimeoutCmd() tea.Cmd {
func autoTimeoutCmd(dur time.Duration) tea.Cmd {
return func() tea.Msg {
time.Sleep(autoTimeoutDuration)
time.Sleep(dur)
return autoTimeoutMsg{}
}
}
@@ -292,7 +350,7 @@ func (m Model) View() string {
var sections []string
// Big clock
clockStr := RenderBigClock(m.now)
clockStr := RenderBigClock(m.now, m.settings.BlinkOnMs, m.settings.BlinkOffMs)
if m.firingAlarm != nil && m.firingBlink {
clockStr = ClockAlarmStyle.Render(clockStr)
} else {
@@ -326,7 +384,7 @@ func (m Model) View() string {
if m.snoozeCount > 0 {
firingText += fmt.Sprintf("\n(snoozed %d time(s))", m.snoozeCount)
}
firingText += "\n\n[Enter/Space/d] Dismiss [s] Snooze 5min"
firingText += fmt.Sprintf("\n\n[Enter/Space/d] Dismiss [s] Snooze %dmin", m.settings.SnoozeMinutes)
styled := AlarmFiringStyle.Render(firingText)
styled = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, styled)
sections = append(sections, "", styled)
@@ -337,8 +395,12 @@ func (m Model) View() string {
divider = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, divider)
sections = append(sections, "", divider, "")
// Form view
if m.mode == viewForm && m.form != nil {
// Form / Config view
if m.mode == viewConfig && m.config != nil {
configView := m.config.View()
configView = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, configView)
sections = append(sections, configView)
} else if m.mode == viewForm && m.form != nil {
formView := m.form.View()
formView = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, formView)
sections = append(sections, formView)
@@ -438,13 +500,14 @@ func (m Model) renderAlarmList() string {
}
func (m Model) renderHelp() string {
if m.mode == viewForm {
switch m.mode {
case viewForm, viewConfig:
return HelpStyle.Render("Tab: next field Shift+Tab: prev field Enter: save Esc: cancel")
}
if m.mode == viewConfirmDelete {
case viewConfirmDelete:
return HelpStyle.Render("y: confirm delete n: cancel")
default:
return HelpStyle.Render("j/k: navigate a: add e: edit d: delete space: toggle c: config q: quit")
}
return HelpStyle.Render("j/k: navigate a: add e: edit d: delete space: toggle q: quit")
}
func min(a, b int) int {