This commit is contained in:
eyjhb 2025-01-26 16:25:44 +01:00
parent 0d87346089
commit 32c5cae17a
Signed by: eyjhb
GPG key ID: 609F508E3239F920
2 changed files with 151 additions and 95 deletions

View file

@ -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)
})
}

View file

@ -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()
}