diff --git a/cmd/cmd.go b/cmd/cmd.go index fb1e9b3..2348bbd 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -3,14 +3,11 @@ package cmd import ( "context" "fmt" - "slices" "time" - "github.com/blang/mpv" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/simonhege/timeago" miniflux "miniflux.app/client" ) @@ -23,124 +20,91 @@ var ( quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) ) -type Test uint +type ViewType uint const ( - ViewListCategories = 10 + ViewListFeedEntries ViewType = iota + ViewPlayer + ViewFeeds ) -type feedEntry struct { - Name string - Feed string - Link string - PublishedAt time.Time -} - -type MsgFetchedEntries []feedEntry -type MsgFetchedEntriesError error - -func (fe feedEntry) Title() string { return fe.Name } -func (fe feedEntry) Description() string { - formattedTimeAgo := timeago.WithMax(timeago.English, time.Hour*7*24, timeago.English.DefaultLayout).Format(fe.PublishedAt) - return fmt.Sprintf("%s - Published %s", fe.Feed, formattedTimeAgo) -} -func (fe feedEntry) FilterValue() string { return fmt.Sprintf("%s - %s", fe.Name, fe.Feed) } +type MsgChangeView ViewType +type MsgSelectedItem feedEntry +type MsgTickInternal time.Time +type MsgTick uint8 type MinifluxPlayer struct { MinifluxClient *miniflux.Client DefaultCategory string MpvPath string - // feedEntriesList []feedEntry - feedEntriesList list.Model - selectedEntry *feedEntry -} + CurrentView ViewType -func (mp *MinifluxPlayer) fetchEntries() tea.Msg { - entriesResult, err := mp.MinifluxClient.CategoryEntries(4, nil) - if err != nil { - return MsgFetchedEntriesError(err) - } - - var feedEntries []feedEntry - for _, e := range entriesResult.Entries { - feedEntries = append(feedEntries, feedEntry{ - Name: e.Title, - Feed: e.Feed.Title, - Link: e.URL, - PublishedAt: e.Date, - }) - } - slices.Reverse(feedEntries) - - return MsgFetchedEntries(feedEntries) + feedEntries feedEntriesModel + player playerModel } func (mp *MinifluxPlayer) Init() tea.Cmd { - mp.feedEntriesList = list.New([]list.Item{feedEntry{}}, list.NewDefaultDelegate(), 100, 50) - mp.feedEntriesList.Title = "Feed" - return mp.fetchEntries + mp.feedEntries = feedEntriesModel{ + minifluxClient: mp.MinifluxClient, + } + + mp.player = playerModel{} + + return tea.Sequence( + tickEvery(), + mp.feedEntries.Init(), + mp.player.Init(), + ) } func (mp *MinifluxPlayer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case tea.WindowSizeMsg: - mp.feedEntriesList.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return mp, tea.Quit - case "enter": - i, ok := mp.feedEntriesList.SelectedItem().(feedEntry) - if ok { - mp.selectedEntry = &i - } - return mp, tea.Quit } - break - - case MsgFetchedEntries: - var items []list.Item - for _, e := range msg { - items = append(items, e) - } - mp.feedEntriesList.SetItems(items) - case MsgFetchedEntriesError: - break + case MsgTickInternal: + fnSendTick := func() tea.Msg { return MsgTick(0) } + return mp, tea.Batch(tickEvery(), fnSendTick) + case MsgChangeView: + mp.CurrentView = ViewType(msg) } - var cmd tea.Cmd - mp.feedEntriesList, cmd = mp.feedEntriesList.Update(msg) - return mp, cmd + if mp.CurrentView == ViewListFeedEntries { + _, c := mp.feedEntries.Update(msg) + return mp, c + } else if mp.CurrentView == ViewPlayer { + _, c := mp.player.Update(msg) + return mp, c + } + + return mp, nil } func (mp *MinifluxPlayer) View() string { - if mp.selectedEntry != nil { - ipcc := mpv.NewIPCClient("/tmp/mpvsocket") // Lowlevel client - c := mpv.NewClient(ipcc) // Highlevel client, can also use RPCClient - - c.Loadfile(mp.selectedEntry.Link, mpv.LoadFileModeReplace) - // c.SetPause(true) - // c.Seek(600, mpv.SeekModeAbsolute) - // c.SetFullscreen(true) - c.SetPause(false) - time.Sleep(10 * time.Second) - for { - pos, _ := c.Position() - dur, _ := c.Duration() - fmt.Printf("%f/%f\n", pos, dur) - - if pos >= dur { - break - } - - } - return quitTextStyle.Render(fmt.Sprintf("Selected entry %s", mp.selectedEntry.Link)) + if mp.CurrentView == ViewPlayer { + return mp.player.View() } - return mp.feedEntriesList.View() + if mp.player.currentlyPlaying == true { + return lipgloss.JoinVertical(0.2, mp.feedEntries.View(), "", fmt.Sprintf("Currently Playing: %s (%v/%v)", + mp.player.selectedEntry.Name, + mp.player.videoPosition, + mp.player.videoDuration, + )) + } + + return mp.feedEntries.View() } func (mp *MinifluxPlayer) FetchCategories(ctx context.Context) ([]*miniflux.Category, error) { return mp.MinifluxClient.Categories() } + +func tickEvery() tea.Cmd { + return tea.Every(time.Second, func(t time.Time) tea.Msg { + return MsgTickInternal(t) + }) +} diff --git a/cmd/feed_entries.go b/cmd/feed_entries.go index c4623d0..f20014d 100644 --- a/cmd/feed_entries.go +++ b/cmd/feed_entries.go @@ -1,8 +1,100 @@ -// package cmd +package cmd -// import "github.com/charmbracelet/bubbles/list" +import ( + "fmt" + "slices" + "time" -// type feedEntriesModel struct { -// entries list.Model -// selectedEntry -// } + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/simonhege/timeago" + miniflux "miniflux.app/client" +) + +type feedEntry struct { + Name string + Feed string + Link string + PublishedAt time.Time +} + +func (fe feedEntry) Title() string { return fe.Name } +func (fe feedEntry) Description() string { + formattedTimeAgo := timeago.WithMax(timeago.English, time.Hour*7*24, timeago.English.DefaultLayout).Format(fe.PublishedAt) + return fmt.Sprintf("%s - Published %s", fe.Feed, formattedTimeAgo) +} +func (fe feedEntry) FilterValue() string { return fmt.Sprintf("%s - %s", fe.Name, fe.Feed) } + +type feedEntriesModel struct { + minifluxClient *miniflux.Client + entries list.Model + selectedEntry *feedEntry +} + +type MsgFetchedEntries []feedEntry +type MsgFetchedEntriesError error + +func (m *feedEntriesModel) fetchEntries() tea.Msg { + entriesResult, err := m.minifluxClient.CategoryEntries(4, nil) + if err != nil { + return MsgFetchedEntriesError(err) + } + + var feedEntries []feedEntry + for _, e := range entriesResult.Entries { + feedEntries = append(feedEntries, feedEntry{ + Name: e.Title, + Feed: e.Feed.Title, + Link: e.URL, + PublishedAt: e.Date, + }) + } + slices.Reverse(feedEntries) + + return MsgFetchedEntries(feedEntries) +} + +func (m *feedEntriesModel) Init() tea.Cmd { + m.entries = list.New([]list.Item{feedEntry{}}, list.NewDefaultDelegate(), 100, 50) + m.entries.Title = "Feed" + return m.fetchEntries +} + +func (m *feedEntriesModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "enter": + fnChangeView := func() tea.Msg { return MsgChangeView(ViewPlayer) } + + fnGetSelected := func() tea.Msg { + i, ok := m.entries.SelectedItem().(feedEntry) + if ok { + m.selectedEntry = &i + } + + return MsgSelectedItem(i) + } + + return m, tea.Sequence(fnChangeView, fnGetSelected) + } + break + + case MsgFetchedEntries: + var items []list.Item + for _, e := range msg { + items = append(items, e) + } + m.entries.SetItems(items) + case MsgFetchedEntriesError: + break + } + + var cmd tea.Cmd + m.entries, cmd = m.entries.Update(msg) + return m, cmd +} + +func (m *feedEntriesModel) View() string { + return m.entries.View() +}