140 lines
4.2 KiB
Go
140 lines
4.2 KiB
Go
package mujica
|
||
|
||
import (
|
||
"log/slog"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"mujicaui/internal"
|
||
"mujicaui/theme"
|
||
)
|
||
|
||
// SnoozePickerOptions configure a SnoozePicker.
|
||
type SnoozePickerOptions struct {
|
||
// Location is the user's time zone; nil means local.
|
||
Location *time.Location
|
||
// MorningHour defaults to 9; valid values are 0–23.
|
||
MorningHour int
|
||
// Now defaults to the frame clock.
|
||
Now time.Time
|
||
Disabled bool
|
||
}
|
||
|
||
// SnoozeChoice pairs a message key with a reminder time.
|
||
type SnoozeChoice struct {
|
||
Key string
|
||
At time.Time
|
||
}
|
||
|
||
// SnoozeChoices returns localized-day shortcuts using morning hours.
|
||
func SnoozeChoices(now time.Time, morning int) []SnoozeChoice {
|
||
if morning < 0 || morning > 23 {
|
||
panic("mujica: SnoozeChoices morning outside 0..23")
|
||
}
|
||
day := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||
at := func(days int) time.Time {
|
||
return time.Date(day.Year(), day.Month(), day.Day()+days, morning, 0, 0, 0, now.Location())
|
||
}
|
||
var out []SnoozeChoice
|
||
if later := now.Add(3 * time.Hour).Truncate(time.Hour); later.Day() == now.Day() {
|
||
out = append(out, SnoozeChoice{"snooze_picker.later", later})
|
||
}
|
||
out = append(out, SnoozeChoice{"snooze_picker.tomorrow", at(1)})
|
||
if wd := now.Weekday(); wd != time.Saturday && wd != time.Sunday {
|
||
out = append(out, SnoozeChoice{"snooze_picker.weekend", at(int(time.Saturday - wd))})
|
||
}
|
||
monday := (8 - int(now.Weekday())) % 7
|
||
if monday == 0 {
|
||
monday = 7
|
||
}
|
||
return append(out, SnoozeChoice{"snooze_picker.nextWeek", at(monday)})
|
||
}
|
||
|
||
type snoozeState struct {
|
||
custom bool
|
||
pick time.Time
|
||
}
|
||
|
||
type snoozeKey struct{}
|
||
|
||
// SnoozePicker offers shortcuts and custom reminder times.
|
||
func SnoozePicker(c *ui.Context, until *time.Time, opts SnoozePickerOptions) ChoiceResult {
|
||
if opts.MorningHour < 0 || opts.MorningHour > 23 {
|
||
panic("mujica: SnoozePicker MorningHour outside 0..23")
|
||
}
|
||
loc := opts.Location
|
||
if loc == nil {
|
||
loc = time.Local
|
||
}
|
||
morning := opts.MorningHour
|
||
if morning == 0 {
|
||
morning = 9
|
||
}
|
||
k := Tokens(c)
|
||
d := Density(c)
|
||
u := d.Unit()
|
||
now := opts.Now
|
||
if now.IsZero() {
|
||
now = c.Now()
|
||
}
|
||
now = now.In(loc)
|
||
old := *until
|
||
col := ui.Column(c).Width(300).Padding(u).FocusGroup(ui.Vertical).Role(ui.RoleList).Label(msg(c, "snooze_picker.label")).
|
||
Disabled(opts.Disabled)
|
||
st := ui.Local(col, snoozeKey{}, func() *snoozeState { return &snoozeState{} })
|
||
s := *st
|
||
col.Children(func() {
|
||
row := func(name, when string, on bool) bool {
|
||
b := ui.ButtonBase(c).MinHeight(d.ControlHeight()).Justify(ui.Start).Gap(u*2).Padding(0, u*3).Radius(theme.ControlRadius).
|
||
Role(ui.RoleListItem).Label(name + ", " + when).Selected(on).Background(ui.Transparent).Transition(colorFade)
|
||
internal.Ring(b, k.Focus, theme.ControlRadius)
|
||
switch {
|
||
case on:
|
||
b.Background(k.Selection).BorderWidth(0, 0, 0, 3).BorderColor(k.AccentText)
|
||
case b.Hovered():
|
||
b.Background(k.SurfaceHover)
|
||
}
|
||
b.Children(func() {
|
||
ui.Text(c, name).Grow(1).SingleLine()
|
||
ui.Text(c, when).FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted).FontFeatures("tnum").SingleLine()
|
||
})
|
||
return b.Clicked() && !b.IsDisabled()
|
||
}
|
||
for _, ch := range SnoozeChoices(now, morning) {
|
||
ui.Box(c).Key(ch.Key).Children(func() {
|
||
if row(msg(c, ch.Key), snoozeWhen(c, now, ch.At), until.Equal(ch.At)) {
|
||
*until, s.custom = ch.At, false
|
||
}
|
||
})
|
||
}
|
||
ui.Box(c).Height(1).Margin(u, 0).Background(k.Border)
|
||
if row(msg(c, "snooze_picker.custom"), "", s.custom) {
|
||
s.custom = !s.custom
|
||
if s.pick.IsZero() {
|
||
s.pick = SnoozeChoices(now, morning)[0].At
|
||
}
|
||
}
|
||
if s.custom {
|
||
ui.Column(c).Gap(u * 2).Padding(u * 2).Children(func() {
|
||
DateTimePicker(c, &s.pick, DateTimePickerOptions{Location: loc, Min: now, Label: msg(c, "snooze_picker.custom"), Disabled: opts.Disabled})
|
||
if Button(c, msg(c, "snooze_picker.set"), ButtonOptions{Disabled: !s.pick.After(now)}).Clicked() {
|
||
*until = s.pick
|
||
}
|
||
})
|
||
}
|
||
})
|
||
changed := !until.Equal(old)
|
||
if changed {
|
||
slog.Debug("mujica: snooze_picker set", "until", *until)
|
||
}
|
||
return ChoiceResult{Element: col, changed: changed}
|
||
}
|
||
|
||
func snoozeWhen(c *ui.Context, now, t time.Time) string {
|
||
if t.Year() == now.Year() && t.YearDay() == now.YearDay() {
|
||
return t.Format("15:04")
|
||
}
|
||
return messagingDate(c, t, "snooze_picker.when")
|
||
}
|