commit 7116500

Hugo Osvaldo Barrera  ·  2023-07-03 17:32:03 +0000 UTC
parent f1ca0dc
Show an "unread" indicator bar

When switching to a channel, show a ruler at the last unread position.

Keep the previous "last read" ruler for the focused buffer, since the
"last read" timestamp is updated as soon as the buffer is focused (which
would result in the ruler always rendering at the bottom).

Also add a special case in `SetRead` for when the "last read" timestamp
is received from the server _after_ the buffer was focused. This happens
basically for the buffer that was focused at start-up.

Fixes: https://todo.sr.ht/~taiite/senpai/76
Co-authored-by: delthas <delthas@dille.cc>
1 files changed,  +70, -12
+70, -12
  1@@ -28,6 +28,14 @@ const (
  2 	NotifyHighlight
  3 )
  4 
  5+type optional int
  6+
  7+const (
  8+	optionalUnset optional = iota
  9+	optionalFalse
 10+	optionalTrue
 11+)
 12+
 13 type Line struct {
 14 	At        time.Time
 15 	Head      string
 16@@ -189,6 +197,17 @@ type buffer struct {
 17 	read       time.Time
 18 	openedOnce bool
 19 
 20+	// This is the "last read" timestamp when the buffer was last focused.
 21+	// If the "last read" timestamp changes while the buffer is focused,
 22+	// the ruler should not move.
 23+	unreadRuler time.Time
 24+	// Whether to draw the unread bar for the current buffer.
 25+	// The goal is to draw the unread bar iff there was at least one unread
 26+	// message when the buffer was opened.
 27+	// The unreadSkip value starts off as optionalUnset, then gets set to
 28+	// either optionalFalse or optionalTrue when a message is received.
 29+	unreadSkip optional
 30+
 31 	lines []Line
 32 	topic string
 33 
 34@@ -256,8 +275,21 @@ func (bs *BufferList) To(i int) bool {
 35 		if len(bs.list) <= bs.current {
 36 			bs.current = len(bs.list) - 1
 37 		}
 38-		bs.list[bs.current].highlights = 0
 39-		bs.list[bs.current].unread = false
 40+		b := bs.list[bs.current]
 41+		b.highlights = 0
 42+		b.unread = false
 43+		b.unreadRuler = b.read
 44+		if len(b.lines) > 0 {
 45+			l := b.lines[len(b.lines)-1]
 46+			if !l.At.After(b.unreadRuler) {
 47+				b.unreadSkip = optionalTrue
 48+			} else {
 49+				b.unreadSkip = optionalFalse
 50+			}
 51+		} else {
 52+			b.unreadSkip = optionalUnset
 53+		}
 54+		bs.list[bs.current] = b
 55 		return true
 56 	}
 57 	return false
 58@@ -268,19 +300,13 @@ func (bs *BufferList) ShowBufferNumbers(enabled bool) {
 59 }
 60 
 61 func (bs *BufferList) Next() {
 62-	bs.overlay = nil
 63-	bs.current = (bs.current + 1) % len(bs.list)
 64-	b := bs.cur()
 65-	b.highlights = 0
 66-	b.unread = false
 67+	c := (bs.current + 1) % len(bs.list)
 68+	bs.To(c)
 69 }
 70 
 71 func (bs *BufferList) Previous() {
 72-	bs.overlay = nil
 73-	bs.current = (bs.current - 1 + len(bs.list)) % len(bs.list)
 74-	b := bs.cur()
 75-	b.highlights = 0
 76-	b.unread = false
 77+	c := (bs.current - 1 + len(bs.list)) % len(bs.list)
 78+	bs.To(c)
 79 }
 80 
 81 func (bs *BufferList) NextUnread() {
 82@@ -421,6 +447,13 @@ func (bs *BufferList) AddLine(netID, title string, line Line) {
 83 	if line.Notify == NotifyHighlight && b != current {
 84 		b.highlights++
 85 	}
 86+	if b == current && b.unreadSkip == optionalUnset && len(b.lines) > 0 {
 87+		if b.unreadRuler.IsZero() || !b.lines[len(b.lines)-1].At.After(b.unreadRuler) {
 88+			b.unreadSkip = optionalTrue
 89+		} else {
 90+			b.unreadSkip = optionalFalse
 91+		}
 92+	}
 93 }
 94 
 95 func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
 96@@ -459,6 +492,13 @@ func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
 97 		}
 98 	}
 99 	b.lines = lines
100+	if b == bs.cur() && b.unreadSkip == optionalUnset && len(b.lines) > 0 {
101+		if b.unreadRuler.IsZero() || !b.lines[len(b.lines)-1].At.After(b.unreadRuler) {
102+			b.unreadSkip = optionalTrue
103+		} else {
104+			b.unreadSkip = optionalFalse
105+		}
106+	}
107 }
108 
109 func (bs *BufferList) SetTopic(netID, title string, topic string) {
110@@ -491,6 +531,10 @@ func (bs *BufferList) SetRead(netID, title string, timestamp time.Time) {
111 	}
112 	if b.read.Before(timestamp) {
113 		b.read = timestamp
114+		// For buffers that were focused _before_ we receive any "last read" date.
115+		if b.unreadRuler.IsZero() {
116+			b.unreadRuler = b.read
117+		}
118 	}
119 }
120 
121@@ -783,6 +827,7 @@ func (bs *BufferList) DrawTimeline(screen tcell.Screen, x0, y0, nickColWidth int
122 	}
123 
124 	yi := b.scrollAmt + y0 + bs.tlHeight
125+	rulerDrawn := b.unreadSkip != optionalFalse || b.unreadRuler.IsZero() || b.title == ""
126 	for i := len(b.lines) - 1; 0 <= i; i-- {
127 		if yi < y0 {
128 			break
129@@ -792,6 +837,19 @@ func (bs *BufferList) DrawTimeline(screen tcell.Screen, x0, y0, nickColWidth int
130 
131 		line := &b.lines[i]
132 		nls := line.NewLines(bs.textWidth)
133+
134+		if !rulerDrawn {
135+			isRead := !line.At.After(b.unreadRuler)
136+			if isRead && yi > y0 {
137+				yi--
138+				st := tcell.StyleDefault.Foreground(tcell.ColorRed)
139+				for x := x0; x < x0+bs.tlInnerWidth+nickColWidth+9; x++ {
140+					screen.SetContent(x, yi, 0x254C, nil, st)
141+				}
142+				rulerDrawn = true
143+			}
144+		}
145+
146 		yi -= len(nls) + 1
147 		if y0+bs.tlHeight <= yi {
148 			continue