From b31d96881432ebb1d4918ae970fabfd6362e1186 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Mar 2018 17:22:47 +0300 Subject: Show notifications and highlights in room list. Fixes #8 --- interface/ui.go | 3 +++ matrix/matrix.go | 25 ++++--------------------- matrix/rooms/room.go | 17 +++++++++++++++++ ui/view-main.go | 43 ++++++++++++++++++++++++++++++++++++++++++- ui/widget/message-view.go | 12 ++++++------ ui/widget/room-list.go | 34 +++++++++++++++++++++++----------- ui/widget/util.go | 16 ++++++++++++++-- 7 files changed, 109 insertions(+), 41 deletions(-) diff --git a/interface/ui.go b/interface/ui.go index 43e214b..c0ddf53 100644 --- a/interface/ui.go +++ b/interface/ui.go @@ -18,6 +18,8 @@ package ifc import ( "maunium.net/go/gomatrix" + "maunium.net/go/gomuks/matrix/pushrules" + "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/ui/types" "maunium.net/go/gomuks/ui/widget" "maunium.net/go/tview" @@ -51,6 +53,7 @@ type MainView interface { AddServiceMessage(roomID *widget.RoomView, message string) ProcessMessageEvent(roomView *widget.RoomView, evt *gomatrix.Event) *types.Message ProcessMembershipEvent(roomView *widget.RoomView, evt *gomatrix.Event) *types.Message + NotifyMessage(room *rooms.Room, message *types.Message, should pushrules.PushActionArrayShould) } type LoginView interface { diff --git a/matrix/matrix.go b/matrix/matrix.go index b72db7d..d706b95 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -22,13 +22,11 @@ import ( "strings" "time" - "github.com/gdamore/tcell" "maunium.net/go/gomatrix" "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/matrix/pushrules" "maunium.net/go/gomuks/matrix/rooms" - "maunium.net/go/gomuks/notification" "maunium.net/go/gomuks/ui/debug" "maunium.net/go/gomuks/ui/widget" ) @@ -214,33 +212,18 @@ func (c *Container) Start() { } } -// NotifyMessage sends a desktop notification of the message with the given details. -func (c *Container) NotifyMessage(room *rooms.Room, sender, text string, critical bool) { - if room.GetTitle() != sender { - sender = fmt.Sprintf("%s (%s)", sender, room.GetTitle()) - } - notification.Send(sender, text, critical) -} - // HandleMessage is the event handler for the m.room.message timeline event. func (c *Container) HandleMessage(evt *gomatrix.Event) { - roomView := c.ui.MainView().GetRoom(evt.RoomID) + mainView := c.ui.MainView() + roomView := mainView.GetRoom(evt.RoomID) if roomView == nil { return } - message := c.ui.MainView().ProcessMessageEvent(roomView, evt) + message := mainView.ProcessMessageEvent(roomView, evt) if message != nil { pushRules := c.PushRules().GetActions(roomView.Room, evt).Should() - if (pushRules.Notify || !pushRules.NotifySpecified) && evt.Sender != c.config.Session.UserID { - c.NotifyMessage(roomView.Room, message.Sender, message.Text, pushRules.Highlight) - } - if pushRules.Highlight { - message.TextColor = tcell.ColorYellow - } - if pushRules.PlaySound { - // TODO play sound - } + mainView.NotifyMessage(roomView.Room, message, pushRules) roomView.AddMessage(message, widget.AppendMessage) c.ui.Render() } diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go index c24b6db..7dd2af4 100644 --- a/matrix/rooms/room.go +++ b/matrix/rooms/room.go @@ -32,6 +32,16 @@ type Room struct { PrevBatch string // The MXID of the user whose session this room was created for. SessionUserID string + + // The number of unread messages that were notified about. + UnreadMessages int + // Whether or not any of the unread messages were highlights. + Highlighted bool + // Whether or not the room contains any new messages. + // This can be true even when UnreadMessages is zero if there's + // a notificationless message like bot notices. + HasNewMessages bool + // MXID -> Member cache calculated from membership events. memberCache map[string]*Member // The first non-SessionUserID member in the room. Calculated at @@ -65,6 +75,13 @@ func (room *Room) UnlockHistory() { } } +// MarkRead clears the new message statuses on this room. +func (room *Room) MarkRead() { + room.UnreadMessages = 0 + room.Highlighted = false + room.HasNewMessages = false +} + // UpdateState updates the room's current state with the given Event. This will clobber events based // on the type/state_key combination. func (room *Room) UpdateState(event *gomatrix.Event) { diff --git a/ui/view-main.go b/ui/view-main.go index 94d09ab..a037ea0 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -28,6 +28,9 @@ import ( "maunium.net/go/gomatrix" "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/interface" + "maunium.net/go/gomuks/matrix/pushrules" + "maunium.net/go/gomuks/matrix/rooms" + "maunium.net/go/gomuks/notification" "maunium.net/go/gomuks/ui/debug" "maunium.net/go/gomuks/ui/types" "maunium.net/go/gomuks/ui/widget" @@ -239,6 +242,10 @@ func (view *MainView) MouseEventHandler(roomView *widget.RoomView, event *tcell. msgView.AddScrollOffset(-WheelScrollOffsetDiff) view.parent.Render() + + if msgView.ScrollOffset == 0 { + roomView.Room.MarkRead() + } default: debug.Print("Mouse event received:", event.Buttons(), event.Modifiers(), x, y) return event @@ -263,7 +270,11 @@ func (view *MainView) SwitchRoom(roomIndex int) { } view.currentRoomIndex = roomIndex % len(view.roomIDs) view.roomView.SwitchToPage(view.CurrentRoomID()) - view.roomList.SetSelected(view.rooms[view.CurrentRoomID()].Room) + roomView := view.rooms[view.CurrentRoomID()] + if roomView.MessageView().ScrollOffset == 0 { + roomView.Room.MarkRead() + } + view.roomList.SetSelected(roomView.Room) view.gmx.App().SetFocus(view) view.parent.Render() } @@ -367,6 +378,36 @@ func (view *MainView) SetTyping(room string, users []string) { } } +func sendNotification(room *rooms.Room, sender, text string, critical bool) { + if room.GetTitle() != sender { + sender = fmt.Sprintf("%s (%s)", sender, room.GetTitle()) + } + notification.Send(sender, text, critical) +} + +func (view *MainView) NotifyMessage(room *rooms.Room, message *types.Message, should pushrules.PushActionArrayShould) { + isCurrent := room.ID == view.CurrentRoomID() + if !isCurrent { + room.HasNewMessages = true + } + shouldNotify := (should.Notify || !should.NotifySpecified) && message.Sender != view.config.Session.UserID + if shouldNotify { + sendNotification(room, message.Sender, message.Text, should.Highlight) + if !isCurrent { + room.UnreadMessages++ + } + } + if should.Highlight { + message.TextColor = tcell.ColorYellow + if !isCurrent { + room.Highlighted = true + } + } + if should.PlaySound { + // TODO play sound + } +} + func (view *MainView) AddServiceMessage(roomView *widget.RoomView, text string) { message := roomView.NewMessage("", "*", "gomuks.service", text, time.Now()) message.TextColor = tcell.ColorGray diff --git a/ui/widget/message-view.go b/ui/widget/message-view.go index 332af94..e48dddf 100644 --- a/ui/widget/message-view.go +++ b/ui/widget/message-view.go @@ -276,11 +276,11 @@ func getScrollbarStyle(scrollbarHere, isTop, isBottom bool) (char rune, style tc func (view *MessageView) Draw(screen tcell.Screen) { view.Box.Draw(screen) - x, y, width, height := view.GetInnerRect() + x, y, _, height := view.GetInnerRect() view.recalculateBuffers() if len(view.textBuffer) == 0 { - writeLine(screen, tview.AlignLeft, "It's quite empty in here.", x, y+height, width, tcell.ColorDefault) + writeLineSimple(screen, "It's quite empty in here.", x, y+height) return } @@ -294,7 +294,7 @@ func (view *MessageView) Draw(screen tcell.Screen) { if view.LoadingMessages { message = "Loading more messages..." } - writeLine(screen, tview.AlignLeft, message, messageX, y, width, tcell.ColorGreen) + writeLineSimpleColor(screen, message, messageX, y, tcell.ColorGreen) } if len(view.textBuffer) != len(view.metaBuffer) { @@ -339,16 +339,16 @@ func (view *MessageView) Draw(screen tcell.Screen) { text, meta := view.textBuffer[index], view.metaBuffer[index] if meta != prevMeta { if len(meta.GetTimestamp()) > 0 { - writeLine(screen, tview.AlignLeft, meta.GetTimestamp(), x, y+line, width, meta.GetTimestampColor()) + writeLineSimpleColor(screen, meta.GetTimestamp(), x, y+line, meta.GetTimestampColor()) } if prevMeta == nil || meta.GetSender() != prevMeta.GetSender() { - writeLine( + writeLineColor( screen, tview.AlignRight, meta.GetSender(), usernameX, y+line, view.widestSender, meta.GetSenderColor()) } prevMeta = meta } - writeLine(screen, tview.AlignLeft, text, messageX, y+line, width, meta.GetTextColor()) + writeLineSimpleColor(screen, text, messageX, y+line, meta.GetTextColor()) } } diff --git a/ui/widget/room-list.go b/ui/widget/room-list.go index b908510..d2fb543 100644 --- a/ui/widget/room-list.go +++ b/ui/widget/room-list.go @@ -17,6 +17,9 @@ package widget import ( + "fmt" + "strconv" + "github.com/gdamore/tcell" "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/tview" @@ -104,22 +107,31 @@ func (list *RoomList) Draw(screen tcell.Screen) { text := item.GetTitle() - writeLine(screen, tview.AlignLeft, text, x, y, width, list.mainTextColor) + lineWidth := width - // Background color of selected text. + style := tcell.StyleDefault.Foreground(list.mainTextColor) if item == list.selected { - textWidth := tview.StringWidth(text) - for bx := 0; bx < textWidth && bx < width; bx++ { - m, c, style, _ := screen.GetContent(x+bx, y) - fg, _, _ := style.Decompose() - if fg == list.mainTextColor { - fg = list.selectedTextColor - } - style = style.Background(list.selectedBackgroundColor).Foreground(fg) - screen.SetContent(x+bx, y, m, c, style) + style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor) + } + if item.HasNewMessages { + style = style.Bold(true) + } + + if item.UnreadMessages > 0 { + unreadMessageCount := "99+" + if item.UnreadMessages < 100 { + unreadMessageCount = strconv.Itoa(item.UnreadMessages) + } + if item.Highlighted { + unreadMessageCount += "!" } + unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount) + writeLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-6, y, 6, style) + lineWidth -= len(unreadMessageCount) + 1 } + writeLine(screen, tview.AlignLeft, text, x, y, lineWidth, style) + y++ if y >= bottomLimit { break diff --git a/ui/widget/util.go b/ui/widget/util.go index 5bde263..0888210 100644 --- a/ui/widget/util.go +++ b/ui/widget/util.go @@ -22,7 +22,19 @@ import ( "maunium.net/go/tview" ) -func writeLine(screen tcell.Screen, align int, line string, x, y, maxWidth int, color tcell.Color) { +func writeLineSimple(screen tcell.Screen, line string, x, y int) { + writeLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault) +} + +func writeLineSimpleColor(screen tcell.Screen, line string, x, y int, color tcell.Color) { + writeLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault.Foreground(color)) +} + +func writeLineColor(screen tcell.Screen, align int, line string, x, y, maxWidth int, color tcell.Color) { + writeLine(screen, align, line, x, y, maxWidth, tcell.StyleDefault.Foreground(color)) +} + +func writeLine(screen tcell.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) { offsetX := 0 if align == tview.AlignRight { offsetX = maxWidth - runewidth.StringWidth(line) @@ -37,7 +49,7 @@ func writeLine(screen tcell.Screen, align int, line string, x, y, maxWidth int, } for localOffset := 0; localOffset < chWidth; localOffset++ { - screen.SetContent(x+offsetX+localOffset, y, ch, nil, tcell.StyleDefault.Foreground(color)) + screen.SetContent(x+offsetX+localOffset, y, ch, nil, style) } offsetX += chWidth if offsetX > maxWidth { -- cgit v1.2.3