Skip to content

Commit

Permalink
feat(reader): Initial population of feeds pane
Browse files Browse the repository at this point in the history
  • Loading branch information
bow committed Dec 26, 2023
1 parent bb7b676 commit 4c69bb0
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 26 deletions.
49 changes: 49 additions & 0 deletions internal/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ func (f *Feed) Outline() (*opml.Outline, error) {
return &outl, nil
}

func FromFeedPb(pb *api.Feed) *Feed {
if pb == nil {
return nil
}
return &Feed{
ID: pb.GetId(),
Title: pb.GetTitle(),
Description: pb.Description,
FeedURL: pb.GetFeedUrl(),
SiteURL: pb.SiteUrl,
Subscribed: *FromTimestampPb(pb.GetSubTime()),
LastPulled: *FromTimestampPb(pb.GetLastPullTime()),
Updated: FromTimestampPb(pb.GetUpdateTime()),
IsStarred: pb.GetIsStarred(),
Tags: pb.GetTags(),
Entries: fromEntryPbs(pb.GetEntries()),
}
}

type FeedEditOp struct {
ID ID
Title *string
Expand All @@ -168,6 +187,36 @@ type Entry struct {
URL *string
}

func FromEntryPb(pb *api.Entry) *Entry {
if pb == nil {
return nil
}
return &Entry{
ID: pb.GetId(),
FeedID: pb.GetFeedId(),
Title: pb.GetTitle(),
IsRead: pb.GetIsRead(),
IsBookmarked: pb.GetIsBookmarked(),
ExtID: pb.GetExtId(),
Updated: FromTimestampPb(pb.GetUpdateTime()),
Published: FromTimestampPb(pb.GetUpdateTime()),
Description: pb.Description,
Content: pb.Content,
URL: pb.Url,
}
}

func fromEntryPbs(pbs []*api.Entry) []*Entry {
entries := make([]*Entry, 0)
for _, pb := range pbs {
if pb == nil {
continue
}
entries = append(entries, FromEntryPb(pb))
}
return entries
}

type EntryEditOp struct {
ID ID
IsRead *bool
Expand Down
146 changes: 126 additions & 20 deletions internal/reader/feeds_pane.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,93 @@ package reader

import (
"fmt"
"time"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"

"github.com/bow/lens/internal"
)

type feedsPane struct {
*tview.TreeView

theme *Theme

groupOrder []*tview.TreeNode
groupNodes map[feedUpdatedGroup]*tview.TreeNode
feedNodes map[string]*tview.TreeNode

feeds <-chan *internal.Feed
}

func newFeedsPane(theme *Theme) *feedsPane {
func newFeedsPane(theme *Theme, feeds <-chan *internal.Feed) *feedsPane {

fp := feedsPane{theme: theme}
fp.setupNavTree()
fp := feedsPane{
theme: theme,
feeds: feeds,
groupOrder: make([]*tview.TreeNode, 0),
groupNodes: make(map[feedUpdatedGroup]*tview.TreeNode),
feedNodes: make(map[string]*tview.TreeNode),
}

fp.initTree()

focusf, unfocusf := fp.makeDrawFuncs()
fp.SetDrawFunc(unfocusf)
fp.SetFocusFunc(func() { fp.SetDrawFunc(focusf) })
fp.SetBlurFunc(func() { fp.SetDrawFunc(unfocusf) })

go fp.listenForUpdates()

return &fp
}

// TODO: How to handle feeds being removed altogether?
func (fp *feedsPane) listenForUpdates() {
root := fp.GetRoot()
for feed := range fp.feeds {
fnode, exists := fp.feedNodes[feed.FeedURL]
newGroup := whenUpdated(feed)
if exists {
oldGroup := whenUpdated(fnode.GetReference().(*internal.Feed))
if oldGroup != newGroup {
fp.groupNodes[oldGroup].RemoveChild(fnode)
}
} else {
fnode = feedNode(feed, fp.theme)
fp.feedNodes[feed.FeedURL] = fnode
}
fp.groupNodes[newGroup].AddChild(fnode)

root.ClearChildren()
for _, gnode := range fp.groupOrder {
if len(gnode.GetChildren()) > 0 {
root.AddChild(gnode)
}
}
}
}

func (fp *feedsPane) initTree() {

root := tview.NewTreeNode("")

tree := tview.NewTreeView().
SetRoot(root).
SetCurrentNode(root).
SetTopLevel(1)

fp.TreeView = tree

for i := uint8(0); i < uint8(updatedUnknown); i++ {
ug := feedUpdatedGroup(i)
gnode := groupNode(ug, fp.theme)
fp.groupNodes[ug] = gnode
fp.groupOrder = append(fp.groupOrder, gnode)
}
}

func (fp *feedsPane) makeDrawFuncs() (focusf, unfocusf drawFunc) {

var titleUF, titleF string
Expand Down Expand Up @@ -83,29 +146,72 @@ func (fp *feedsPane) makeDrawFuncs() (focusf, unfocusf drawFunc) {
return focusf, unfocusf
}

func (fp *feedsPane) setupNavTree() {

root := tview.NewTreeNode("")
func (fp *feedsPane) refreshColors() {
for _, node := range fp.TreeView.GetRoot().GetChildren() {
node.SetColor(fp.theme.FeedsGroup)
}
}

tree := tview.NewTreeView().
SetRoot(root).
SetCurrentNode(root).
SetTopLevel(1)
type feedUpdatedGroup uint8

updateGroups := []string{"Today", "This Week", "This Month", "This Year"}
const (
updatedToday feedUpdatedGroup = iota
updatedThisWeek
updatedThisMonth
updatedEarlier
updatedUnknown
)

for _, ug := range updateGroups {
node := tview.NewTreeNode(ug).
SetSelectable(true).
SetColor(fp.theme.FeedsGroup)
root.AddChild(node)
func (ug feedUpdatedGroup) Text(theme *Theme) string {
switch ug {
case updatedToday:
return theme.UpdatedTodayText
case updatedThisWeek:
return theme.UpdatedThisWeekText
case updatedThisMonth:
return theme.UpdatedThisMonthText
case updatedEarlier:
return theme.UpdatedEarlier
case updatedUnknown:
return theme.UpdatedUnknownText
default:
return theme.UpdatedUnknownText
}
}

fp.TreeView = tree
func feedNode(feed *internal.Feed, _ *Theme) *tview.TreeNode {
return tview.NewTreeNode(feed.Title).
SetReference(feed.FeedURL).
SetColor(tcell.ColorWhite).
SetSelectable(true)
}

func (fp *feedsPane) refreshColors() {
for _, node := range fp.TreeView.GetRoot().GetChildren() {
node.SetColor(fp.theme.FeedsGroup)
func groupNode(ug feedUpdatedGroup, theme *Theme) *tview.TreeNode {
return tview.NewTreeNode(ug.Text(theme)).
SetReference(ug).
SetColor(theme.FeedsGroup).
SetSelectable(false)
}

func whenUpdated(feed *internal.Feed) feedUpdatedGroup {
if feed.Updated == nil {
return updatedUnknown
}

now := time.Now()
yesterday := now.AddDate(0, 0, -1)
lastWeek := now.AddDate(0, 0, -7)
lastMonth := now.AddDate(0, -1, 0)

ft := *feed.Updated
switch {
case ft.Before(lastMonth):
return updatedEarlier
case ft.Before(lastWeek):
return updatedThisMonth
case ft.Before(yesterday):
return updatedThisWeek
default:
return updatedToday
}
}
24 changes: 18 additions & 6 deletions internal/reader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type Reader struct {
readingPane *tview.Box
bar *statusBar

feedsCh chan *internal.Feed
statsCache *internal.Stats
focusStack tview.Primitive
}
Expand Down Expand Up @@ -154,11 +155,12 @@ func (b *Builder) Build() (*Reader, error) {
}

rdr := Reader{
ctx: b.ctx,
client: client,
addr: b.addr,
screen: screen,
theme: b.theme,
ctx: b.ctx,
client: client,
addr: b.addr,
screen: screen,
theme: b.theme,
feedsCh: make(chan *internal.Feed),
}
rdr.setupLayout()

Expand Down Expand Up @@ -217,6 +219,15 @@ To close this message, press [yellow]<Esc>[-].
defer r.initialize()
}

rsp, err := r.client.ListFeeds(r.ctx, &api.ListFeedsRequest{})
if err != nil {
panic(err)
}
for _, feed := range rsp.GetFeeds() {
feed := feed
go func() { r.feedsCh <- internal.FromFeedPb(feed) }()
}

stop := r.bar.startEventPoll()
defer stop()

Expand All @@ -225,7 +236,7 @@ To close this message, press [yellow]<Esc>[-].

func (r *Reader) setupMainPage() {

feedsPane := newFeedsPane(r.theme)
feedsPane := newFeedsPane(r.theme, r.feedsCh)
feedsPane.SetInputCapture(r.feedsPaneKeyHandler())

entriesPane := r.newPane(r.theme.EntriesPaneTitle, false)
Expand Down Expand Up @@ -528,6 +539,7 @@ func (r *Reader) feedsPaneKeyHandler() func(event *tcell.EventKey) *tcell.EventK
r.bar.errEventf("Failed to pull %s: %s", rsp.GetUrl(), serr)
errCount++
} else {
rsp.GetFeed()
okCount++
}
totalCount++
Expand Down
7 changes: 7 additions & 0 deletions internal/reader/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ func setupReaderTest(
r := require.New(t)

client := NewMockLensClient(gomock.NewController(t))
// Needed since we call the list feeds endpoint prior to Show.
client.EXPECT().
ListFeeds(gomock.Any(), gomock.Any()).
Return(
&api.ListFeedsResponse{Feeds: nil},
nil,
)
// Needed since we call the stats endpoint prior to Show.
client.EXPECT().
GetStats(gomock.Any(), gomock.Any()).
Expand Down
12 changes: 12 additions & 0 deletions internal/reader/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ type Theme struct {
AboutPopupTitle string
WelcomePopupTitle string

UpdatedTodayText string
UpdatedThisWeekText string
UpdatedThisMonthText string
UpdatedEarlier string
UpdatedUnknownText string

Background tcell.Color

BorderForeground tcell.Color
Expand Down Expand Up @@ -86,6 +92,12 @@ var DarkTheme = &Theme{
AboutPopupTitle: "About",
WelcomePopupTitle: "Welcome",

UpdatedTodayText: "Today",
UpdatedThisWeekText: "This Week",
UpdatedThisMonthText: "This Month",
UpdatedEarlier: "Earlier",
UpdatedUnknownText: "Unknown",

Background: tcell.ColorBlack,

BorderForeground: tcell.ColorWhite,
Expand Down

0 comments on commit 4c69bb0

Please sign in to comment.