You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

670 lines
15 KiB

package main
import (
"fmt"
"time"
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-mastodon"
"github.com/rivo/tview"
)
func NewStatusView(app *App, tl TimelineType) *StatusView {
t := &StatusView{
app: app,
list: tview.NewList(),
iconList: tview.NewList(),
text: tview.NewTextView(),
controls: tview.NewTextView(),
focus: LeftPaneFocus,
lastList: LeftPaneFocus,
loadingNewer: false,
loadingOlder: false,
feedIndex: 0,
}
if t.app.Config.General.NotificationFeed {
t.notificationView = NewNotificationView(app)
}
if app.Config.General.MaxWidth > 0 {
mw := app.Config.General.MaxWidth
t.text.SetDrawFunc(func(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) {
rWidth := width
if rWidth > mw {
rWidth = mw
}
return x, y, rWidth, height
})
}
t.flex = tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(t.text, 0, 9, false).
AddItem(t.controls, 1, 0, false)
t.list.SetMainTextColor(app.Config.Style.Text)
t.list.SetBackgroundColor(app.Config.Style.Background)
t.list.SetSelectedTextColor(app.Config.Style.ListSelectedText)
t.list.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground)
t.list.ShowSecondaryText(false)
t.list.SetHighlightFullLine(true)
t.iconList.SetMainTextColor(app.Config.Style.Text)
t.iconList.SetBackgroundColor(app.Config.Style.Background)
t.iconList.SetSelectedTextColor(app.Config.Style.ListSelectedText)
t.iconList.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground)
t.iconList.ShowSecondaryText(false)
t.iconList.SetHighlightFullLine(true)
t.text.SetWordWrap(true).SetDynamicColors(true)
t.text.SetBackgroundColor(app.Config.Style.Background)
t.text.SetTextColor(app.Config.Style.Text)
t.controls.SetDynamicColors(true)
t.controls.SetBackgroundColor(app.Config.Style.Background)
if app.Config.General.AutoLoadNewer {
go func() {
d := time.Second * time.Duration(app.Config.General.AutoLoadSeconds)
ticker := time.NewTicker(d)
for range ticker.C {
t.loadNewer()
}
}()
if app.Config.General.NotificationFeed {
go func() {
d := time.Second * time.Duration(app.Config.General.AutoLoadSeconds)
ticker := time.NewTicker(d)
for range ticker.C {
t.notificationView.loadNewer()
}
}()
}
}
return t
}
type ListItem struct {
Text string
Icons string
}
type StatusView struct {
app *App
list *tview.List
iconList *tview.List
flex *tview.Flex
text *tview.TextView
controls *tview.TextView
feeds []Feed
feedIndex int
focus FocusAt
lastList FocusAt
loadingNewer bool
loadingOlder bool
notificationView *NotificationView
}
func (t *StatusView) AddFeed(f Feed) {
t.feeds = append(t.feeds, f)
t.feedIndex = len(t.feeds) - 1
f.DrawList()
t.list.SetCurrentItem(f.GetSavedIndex())
t.iconList.SetCurrentItem(f.GetSavedIndex())
f.DrawToot()
t.drawDesc()
if t.focus == NotificationPaneFocus {
t.app.UI.SetFocus(LeftPaneFocus)
t.focus = LeftPaneFocus
t.lastList = NotificationPaneFocus
} else {
t.lastList = LeftPaneFocus
}
}
func (t *StatusView) CycleDraw() {
feed := t.feeds[t.feedIndex]
feed.DrawList()
t.list.SetCurrentItem(feed.GetSavedIndex())
t.iconList.SetCurrentItem(feed.GetSavedIndex())
if t.lastList == NotificationPaneFocus {
t.app.UI.SetFocus(NotificationPaneFocus)
t.focus = NotificationPaneFocus
t.lastList = NotificationPaneFocus
t.notificationView.feed.DrawToot()
} else {
t.app.UI.SetFocus(LeftPaneFocus)
t.focus = LeftPaneFocus
t.lastList = LeftPaneFocus
feed.DrawToot()
}
t.drawDesc()
if feed.GetSavedIndex() < 4 {
t.loadNewer()
}
}
func (t *StatusView) CyclePreviousFeed() {
if t.feedIndex != 0 {
t.feedIndex = t.feedIndex - 1
}
t.CycleDraw()
}
func (t *StatusView) CycleNextFeed() {
if t.feedIndex+1 < len(t.feeds) {
t.feedIndex = t.feedIndex + 1
}
t.CycleDraw()
}
func (t *StatusView) RemoveCurrentFeed() {
t.feeds[t.feedIndex] = nil
t.feeds = t.feeds[:t.feedIndex+copy(t.feeds[t.feedIndex:], t.feeds[t.feedIndex+1:])]
if t.feedIndex == len(t.feeds) {
t.feedIndex = len(t.feeds) - 1
}
feed := t.feeds[t.feedIndex]
feed.DrawList()
t.list.SetCurrentItem(feed.GetSavedIndex())
t.iconList.SetCurrentItem(feed.GetSavedIndex())
if t.lastList == NotificationPaneFocus {
t.app.UI.SetFocus(NotificationPaneFocus)
t.focus = NotificationPaneFocus
t.lastList = NotificationPaneFocus
t.notificationView.feed.DrawToot()
} else {
t.app.UI.SetFocus(LeftPaneFocus)
t.focus = LeftPaneFocus
t.lastList = LeftPaneFocus
feed.DrawToot()
}
t.drawDesc()
}
func (t *StatusView) GetLeftView() tview.Primitive {
if len(t.feeds) > 0 {
feed := t.feeds[t.feedIndex]
feed.DrawList()
feed.DrawToot()
}
iw := 3
if !t.app.Config.General.ShowIcons {
iw = 0
}
v := tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(t.list, 0, 1, false).
AddItem(t.iconList, iw, 0, false)
return v
}
func (t *StatusView) GetNotificationView() tview.Primitive {
iw := 3
if !t.app.Config.General.ShowIcons {
iw = 0
}
if t.notificationView != nil {
t.notificationView.feed.DrawList()
v := tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(t.notificationView.list, 0, 1, false).
AddItem(t.notificationView.iconList, iw, 0, false)
return v
}
return nil
}
func (t *StatusView) GetRightView() tview.Primitive {
return t.flex
}
func (t *StatusView) GetTextWidth() int {
_, _, width, _ := t.text.GetInnerRect()
return width
}
func (t *StatusView) GetCurrentItem() int {
return t.list.GetCurrentItem()
}
func (t *StatusView) GetCurrentStatus() *mastodon.Status {
if len(t.feeds) == 0 {
return nil
}
return t.feeds[t.feedIndex].GetCurrentStatus()
}
func (t *StatusView) GetCurrentUser() *mastodon.Account {
if len(t.feeds) == 0 {
return nil
}
return t.feeds[t.feedIndex].GetCurrentUser()
}
func (t *StatusView) ScrollToBeginning() {
t.text.ScrollToBeginning()
}
func (t *StatusView) inputBoth(event *tcell.EventKey) {
if event.Key() == tcell.KeyRune {
switch event.Rune() {
case 'g':
t.home()
case 'G':
t.end()
}
} else {
switch event.Key() {
case tcell.KeyCtrlC:
t.app.UI.Root.Stop()
case tcell.KeyHome:
t.home()
case tcell.KeyEnd:
t.end()
}
}
allowedLeft := t.lastList == LeftPaneFocus || t.focus == LeftPaneFocus
allowedNotification := t.lastList == NotificationPaneFocus || t.focus == NotificationPaneFocus
if t.focus == RightPaneFocus {
switch event.Rune() {
case 'g', 'G', 'j', 'k', 'h', 'l', 'q', 'Q':
allowedLeft = false
}
switch event.Key() {
case tcell.KeyEsc:
allowedLeft = false
}
}
if len(t.feeds) > 0 && allowedLeft {
feed := t.feeds[t.feedIndex]
feed.Input(event)
} else if allowedNotification {
t.notificationView.feed.Input(event)
}
}
func (t *StatusView) inputBack(q bool) {
if t.app.UI.Focus == LeftPaneFocus && len(t.feeds) > 1 {
t.RemoveCurrentFeed()
} else if t.app.UI.Focus == LeftPaneFocus && q {
t.app.UI.Root.Stop()
} else if t.app.UI.Focus == NotificationPaneFocus {
t.app.UI.SetFocus(LeftPaneFocus)
t.focus = LeftPaneFocus
t.lastList = LeftPaneFocus
t.feeds[t.feedIndex].DrawToot()
}
}
func (t *StatusView) inputLeft(event *tcell.EventKey) {
if event.Key() == tcell.KeyRune {
switch event.Rune() {
case 'v', 'V':
t.app.UI.SetFocus(RightPaneFocus)
t.focus = RightPaneFocus
t.app.UI.StatusBar.Text.SetBackgroundColor(
t.app.Config.Style.StatusBarViewBackground,
)
t.app.UI.StatusBar.Text.SetTextColor(
t.app.Config.Style.StatusBarViewText,
)
case 'k', 'K':
t.prev()
case 'j', 'J':
t.next()
case 'n', 'N':
if t.app.Config.General.NotificationFeed {
t.app.UI.SetFocus(NotificationPaneFocus)
t.focus = NotificationPaneFocus
t.lastList = NotificationPaneFocus
t.notificationView.feed.DrawToot()
}
case 'q', 'Q':
t.inputBack(true)
case 'h', 'H':
t.CyclePreviousFeed()
case 'l', 'L':
t.CycleNextFeed()
}
} else {
switch event.Key() {
case tcell.KeyUp:
t.prev()
case tcell.KeyDown:
t.next()
case tcell.KeyLeft:
t.CyclePreviousFeed()
case tcell.KeyRight:
t.CycleNextFeed()
case tcell.KeyPgUp, tcell.KeyCtrlB:
t.pgup()
case tcell.KeyPgDn, tcell.KeyCtrlF:
t.pgdown()
case tcell.KeyEsc:
t.inputBack(false)
}
}
}
func (t *StatusView) inputRightQuit() {
if t.lastList == LeftPaneFocus {
t.app.UI.SetFocus(LeftPaneFocus)
t.focus = LeftPaneFocus
} else if t.lastList == NotificationPaneFocus {
t.app.UI.SetFocus(NotificationPaneFocus)
t.focus = NotificationPaneFocus
}
t.app.UI.StatusBar.Text.SetBackgroundColor(
t.app.Config.Style.StatusBarBackground,
)
t.app.UI.StatusBar.Text.SetTextColor(
t.app.Config.Style.StatusBarText,
)
}
func (t *StatusView) inputRight(event *tcell.EventKey) {
if event.Key() == tcell.KeyRune {
switch event.Rune() {
case 'q', 'Q':
t.inputRightQuit()
}
} else {
switch event.Key() {
case tcell.KeyEsc:
t.inputRightQuit()
}
}
}
func (t *StatusView) Input(event *tcell.EventKey) *tcell.EventKey {
t.inputBoth(event)
if len(t.feeds) == 0 {
return event
}
switch t.focus {
case LeftPaneFocus:
t.inputLeft(event)
return nil
case NotificationPaneFocus:
t.inputLeft(event)
return nil
default:
t.inputRight(event)
}
return event
}
func (t *StatusView) SetList(items <-chan ListItem) {
t.list.Clear()
t.iconList.Clear()
for s := range items {
t.list.AddItem(s.Text, "", 0, nil)
t.iconList.AddItem(s.Icons, "", 0, nil)
}
}
func (t *StatusView) SetText(text string) {
t.text.SetText(text)
}
func (t *StatusView) SetControls(text string) {
t.controls.SetText(text)
}
func (t *StatusView) drawDesc() {
if len(t.feeds) == 0 {
t.app.UI.SetTopText("")
return
}
i := t.feedIndex + 1
l := len(t.feeds)
f := t.feeds[t.feedIndex]
t.app.UI.SetTopText(
fmt.Sprintf("%s (%d/%d)", f.GetDesc(), i, l),
)
}
func (t *StatusView) prev() {
var current int
var list *tview.List
var iList *tview.List
var feed Feed
if t.app.UI.Focus == LeftPaneFocus {
current = t.GetCurrentItem()
list = t.list
iList = t.iconList
feed = t.feeds[t.feedIndex]
} else {
current = t.notificationView.list.GetCurrentItem()
list = t.notificationView.list
iList = t.notificationView.iconList
feed = t.notificationView.feed
}
if current-1 >= 0 {
current--
}
list.SetCurrentItem(current)
iList.SetCurrentItem(current)
feed.DrawToot()
if current < 4 {
switch t.app.UI.Focus {
case LeftPaneFocus:
t.loadNewer()
case NotificationPaneFocus:
t.notificationView.loadNewer()
}
}
}
func (t *StatusView) next() {
var list *tview.List
var iList *tview.List
var feed Feed
if t.app.UI.Focus == LeftPaneFocus {
list = t.list
iList = t.iconList
feed = t.feeds[t.feedIndex]
} else {
list = t.notificationView.list
iList = t.notificationView.iconList
feed = t.notificationView.feed
}
list.SetCurrentItem(list.GetCurrentItem() + 1)
iList.SetCurrentItem(iList.GetCurrentItem() + 1)
feed.DrawToot()
count := list.GetItemCount()
current := list.GetCurrentItem()
if (count - current + 1) < 5 {
switch t.app.UI.Focus {
case LeftPaneFocus:
t.loadOlder()
case NotificationPaneFocus:
t.notificationView.loadOlder()
}
}
}
func (t *StatusView) pgdown() {
var list *tview.List
var iList *tview.List
var feed Feed
if t.app.UI.Focus == LeftPaneFocus {
list = t.list
iList = t.iconList
feed = t.feeds[t.feedIndex]
} else {
list = t.notificationView.list
iList = t.notificationView.iconList
feed = t.notificationView.feed
}
_, _, _, height := list.GetInnerRect()
i := list.GetCurrentItem() + height - 1
list.SetCurrentItem(i)
iList.SetCurrentItem(i)
feed.DrawToot()
count := list.GetItemCount()
current := list.GetCurrentItem()
if (count - current + 1) < 5 {
switch t.app.UI.Focus {
case LeftPaneFocus:
t.loadOlder()
case NotificationPaneFocus:
t.notificationView.loadOlder()
}
}
}
func (t *StatusView) pgup() {
var list *tview.List
var iList *tview.List
var feed Feed
if t.app.UI.Focus == LeftPaneFocus {
list = t.list
iList = t.iconList
feed = t.feeds[t.feedIndex]
} else {
list = t.notificationView.list
iList = t.notificationView.iconList
feed = t.notificationView.feed
}
_, _, _, height := list.GetInnerRect()
i := list.GetCurrentItem() - height + 1
if i < 0 {
i = 0
}
list.SetCurrentItem(i)
iList.SetCurrentItem(i)
feed.DrawToot()
current := list.GetCurrentItem()
if current < 4 {
switch t.app.UI.Focus {
case LeftPaneFocus:
t.loadNewer()
case NotificationPaneFocus:
t.notificationView.loadNewer()
}
}
}
func (t *StatusView) home() {
if t.focus == RightPaneFocus {
t.text.ScrollToBeginning()
return
}
var list *tview.List
var iList *tview.List
var feed Feed
if t.app.UI.Focus == LeftPaneFocus {
list = t.list
iList = t.iconList
feed = t.feeds[t.feedIndex]
} else {
list = t.notificationView.list
iList = t.notificationView.iconList
feed = t.notificationView.feed
}
list.SetCurrentItem(0)
iList.SetCurrentItem(0)
feed.DrawToot()
switch t.app.UI.Focus {
case LeftPaneFocus:
t.loadNewer()
case NotificationPaneFocus:
t.notificationView.loadNewer()
}
}
func (t *StatusView) end() {
if t.focus == RightPaneFocus {
t.text.ScrollToEnd()
return
}
var list *tview.List
var iList *tview.List
var feed Feed
if t.app.UI.Focus == LeftPaneFocus {
list = t.list
iList = t.iconList
feed = t.feeds[t.feedIndex]
} else {
list = t.notificationView.list
iList = t.notificationView.iconList
feed = t.notificationView.feed
}
list.SetCurrentItem(-1)
iList.SetCurrentItem(-1)
feed.DrawToot()
switch t.app.UI.Focus {
case LeftPaneFocus:
t.loadOlder()
case NotificationPaneFocus:
t.notificationView.loadOlder()
}
}
func (t *StatusView) loadNewer() {
feedIndex := t.feedIndex
if t.loadingNewer || feedIndex < 0 {
return
}
t.loadingNewer = true
go func() {
new := t.feeds[feedIndex].LoadNewer()
if new == 0 {
t.loadingNewer = false
return
}
t.app.UI.Root.QueueUpdateDraw(func() {
index := t.list.GetCurrentItem()
t.feeds[feedIndex].DrawList()
newIndex := index + new
if index == 0 && t.feeds[feedIndex].FeedType() == UserFeedType {
newIndex = 0
}
t.list.SetCurrentItem(newIndex)
t.iconList.SetCurrentItem(newIndex)
t.loadingNewer = false
})
}()
}
func (t *StatusView) loadOlder() {
feedIndex := t.feedIndex
if t.loadingOlder || feedIndex < 0 {
return
}
t.loadingOlder = true
go func() {
new := t.feeds[feedIndex].LoadOlder()
if new == 0 {
t.loadingOlder = false
return
}
t.app.UI.Root.QueueUpdateDraw(func() {
index := t.list.GetCurrentItem()
t.feeds[feedIndex].DrawList()
t.list.SetCurrentItem(index)
t.iconList.SetCurrentItem(index)
t.loadingOlder = false
})
}()
}