@@ -35,6 +35,7 @@ type MatrixContainer interface {
 | 
				
			|||||||
	SendMessage(roomID, msgtype, message string) (string, error)
 | 
						SendMessage(roomID, msgtype, message string) (string, error)
 | 
				
			||||||
	SendMarkdownMessage(roomID, msgtype, message string) (string, error)
 | 
						SendMarkdownMessage(roomID, msgtype, message string) (string, error)
 | 
				
			||||||
	SendTyping(roomID string, typing bool)
 | 
						SendTyping(roomID string, typing bool)
 | 
				
			||||||
 | 
						MarkRead(roomID, eventID string)
 | 
				
			||||||
	JoinRoom(roomID, server string) (*rooms.Room, error)
 | 
						JoinRoom(roomID, server string) (*rooms.Room, error)
 | 
				
			||||||
	LeaveRoom(roomID string) error
 | 
						LeaveRoom(roomID string) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,7 +46,7 @@ type MainView interface {
 | 
				
			|||||||
	SetRooms(rooms map[string]*rooms.Room)
 | 
						SetRooms(rooms map[string]*rooms.Room)
 | 
				
			||||||
	SaveAllHistory()
 | 
						SaveAllHistory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	UpdateTags(room *rooms.Room, newTags []rooms.RoomTag)
 | 
						UpdateTags(room *rooms.Room)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	SetTyping(roomID string, users []string)
 | 
						SetTyping(roomID string, users []string)
 | 
				
			||||||
	ParseEvent(roomView RoomView, evt *gomatrix.Event) Message
 | 
						ParseEvent(roomView RoomView, evt *gomatrix.Event) Message
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -177,7 +177,9 @@ func (c *Container) OnLogin() {
 | 
				
			|||||||
	c.syncer = NewGomuksSyncer(c.config.Session)
 | 
						c.syncer = NewGomuksSyncer(c.config.Session)
 | 
				
			||||||
	c.syncer.OnEventType("m.room.message", c.HandleMessage)
 | 
						c.syncer.OnEventType("m.room.message", c.HandleMessage)
 | 
				
			||||||
	c.syncer.OnEventType("m.room.member", c.HandleMembership)
 | 
						c.syncer.OnEventType("m.room.member", c.HandleMembership)
 | 
				
			||||||
 | 
						c.syncer.OnEventType("m.receipt", c.HandleReadReceipt)
 | 
				
			||||||
	c.syncer.OnEventType("m.typing", c.HandleTyping)
 | 
						c.syncer.OnEventType("m.typing", c.HandleTyping)
 | 
				
			||||||
 | 
						c.syncer.OnEventType("m.direct", c.HandleDirectChatInfo)
 | 
				
			||||||
	c.syncer.OnEventType("m.push_rules", c.HandlePushRules)
 | 
						c.syncer.OnEventType("m.push_rules", c.HandlePushRules)
 | 
				
			||||||
	c.syncer.OnEventType("m.tag", c.HandleTag)
 | 
						c.syncer.OnEventType("m.tag", c.HandleTag)
 | 
				
			||||||
	c.syncer.InitDoneCallback = func() {
 | 
						c.syncer.InitDoneCallback = func() {
 | 
				
			||||||
@@ -228,7 +230,7 @@ func (c *Container) Start() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// HandleMessage is the event handler for the m.room.message timeline event.
 | 
					// HandleMessage is the event handler for the m.room.message timeline event.
 | 
				
			||||||
func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) {
 | 
					func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) {
 | 
				
			||||||
	if source & EventSourceLeave != 0 {
 | 
						if source&EventSourceLeave != 0 {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mainView := c.ui.MainView()
 | 
						mainView := c.ui.MainView()
 | 
				
			||||||
@@ -253,6 +255,82 @@ func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent string) {
 | 
				
			||||||
 | 
						var largestTimestamp int64
 | 
				
			||||||
 | 
						for eventID, rawContent := range evt.Content {
 | 
				
			||||||
 | 
							content, ok := rawContent.(map[string]interface{})
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mRead, ok := content["m.read"].(map[string]interface{})
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							myInfo, ok := mRead[c.config.Session.UserID].(map[string]interface{})
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ts, ok := myInfo["ts"].(float64)
 | 
				
			||||||
 | 
							if int64(ts) > largestTimestamp {
 | 
				
			||||||
 | 
								largestTimestamp = int64(ts)
 | 
				
			||||||
 | 
								largestTimestampEvent = eventID
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) HandleReadReceipt(source EventSource, evt *gomatrix.Event) {
 | 
				
			||||||
 | 
						if source&EventSourceLeave != 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastReadEvent := c.parseReadReceipt(evt)
 | 
				
			||||||
 | 
						if len(lastReadEvent) == 0 {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						room := c.GetRoom(evt.RoomID)
 | 
				
			||||||
 | 
						room.MarkRead(lastReadEvent)
 | 
				
			||||||
 | 
						c.ui.Render()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) parseDirectChatInfo(evt *gomatrix.Event) (map[*rooms.Room]bool){
 | 
				
			||||||
 | 
						directChats := make(map[*rooms.Room]bool)
 | 
				
			||||||
 | 
						for _, rawRoomIDList := range evt.Content {
 | 
				
			||||||
 | 
							roomIDList, ok := rawRoomIDList.([]interface{})
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, rawRoomID := range roomIDList {
 | 
				
			||||||
 | 
								roomID, ok := rawRoomID.(string)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								room := c.GetRoom(roomID)
 | 
				
			||||||
 | 
								if room != nil && !room.HasLeft {
 | 
				
			||||||
 | 
									directChats[room] = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return directChats
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event) {
 | 
				
			||||||
 | 
						directChats := c.parseDirectChatInfo(evt)
 | 
				
			||||||
 | 
						for _, room := range c.config.Session.Rooms {
 | 
				
			||||||
 | 
							shouldBeDirect := directChats[room]
 | 
				
			||||||
 | 
							if shouldBeDirect != room.IsDirect {
 | 
				
			||||||
 | 
								room.IsDirect = shouldBeDirect
 | 
				
			||||||
 | 
								c.ui.MainView().UpdateTags(room)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HandlePushRules is the event handler for the m.push_rules account data event.
 | 
					// HandlePushRules is the event handler for the m.push_rules account data event.
 | 
				
			||||||
func (c *Container) HandlePushRules(source EventSource, evt *gomatrix.Event) {
 | 
					func (c *Container) HandlePushRules(source EventSource, evt *gomatrix.Event) {
 | 
				
			||||||
	debug.Print("Received updated push rules")
 | 
						debug.Print("Received updated push rules")
 | 
				
			||||||
@@ -285,7 +363,8 @@ func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mainView := c.ui.MainView()
 | 
						mainView := c.ui.MainView()
 | 
				
			||||||
	mainView.UpdateTags(room, newTags)
 | 
						room.RawTags = newTags
 | 
				
			||||||
 | 
						mainView.UpdateTags(room)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
 | 
					func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
 | 
				
			||||||
@@ -314,8 +393,8 @@ func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// HandleMembership is the event handler for the m.room.member state event.
 | 
					// HandleMembership is the event handler for the m.room.member state event.
 | 
				
			||||||
func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
 | 
					func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
 | 
				
			||||||
	isLeave := source & EventSourceLeave != 0
 | 
						isLeave := source&EventSourceLeave != 0
 | 
				
			||||||
	isTimeline := source & EventSourceTimeline != 0
 | 
						isTimeline := source&EventSourceTimeline != 0
 | 
				
			||||||
	isNonTimelineLeave := isLeave && !isTimeline
 | 
						isNonTimelineLeave := isLeave && !isTimeline
 | 
				
			||||||
	if !c.config.Session.InitialSyncDone && isNonTimelineLeave {
 | 
						if !c.config.Session.InitialSyncDone && isNonTimelineLeave {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -356,6 +435,11 @@ func (c *Container) HandleTyping(source EventSource, evt *gomatrix.Event) {
 | 
				
			|||||||
	c.ui.MainView().SetTyping(evt.RoomID, strUsers)
 | 
						c.ui.MainView().SetTyping(evt.RoomID, strUsers)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) MarkRead(roomID, eventID string) {
 | 
				
			||||||
 | 
						urlPath := c.client.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
 | 
				
			||||||
 | 
						c.client.MakeRequest("POST", urlPath, struct{}{}, nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SendMessage sends a message with the given text to the given room.
 | 
					// SendMessage sends a message with the given text to the given room.
 | 
				
			||||||
func (c *Container) SendMessage(roomID, msgtype, text string) (string, error) {
 | 
					func (c *Container) SendMessage(roomID, msgtype, text string) (string, error) {
 | 
				
			||||||
	defer debug.Recover()
 | 
						defer debug.Recover()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,12 @@ type RoomTag struct {
 | 
				
			|||||||
	Order string
 | 
						Order string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UnreadMessage struct {
 | 
				
			||||||
 | 
						EventID string
 | 
				
			||||||
 | 
						Counted bool
 | 
				
			||||||
 | 
						Highlight bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Room represents a single Matrix room.
 | 
					// Room represents a single Matrix room.
 | 
				
			||||||
type Room struct {
 | 
					type Room struct {
 | 
				
			||||||
	*gomatrix.Room
 | 
						*gomatrix.Room
 | 
				
			||||||
@@ -57,13 +63,11 @@ type Room struct {
 | 
				
			|||||||
	SessionUserID string
 | 
						SessionUserID string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The number of unread messages that were notified about.
 | 
						// The number of unread messages that were notified about.
 | 
				
			||||||
	UnreadMessages int
 | 
						UnreadMessages []UnreadMessage
 | 
				
			||||||
	// Whether or not any of the unread messages were highlights.
 | 
						unreadCountCache *int
 | 
				
			||||||
	Highlighted bool
 | 
						highlightCache *bool
 | 
				
			||||||
	// Whether or not the room contains any new messages.
 | 
						// Whether or not this room is marked as a direct chat.
 | 
				
			||||||
	// This can be true even when UnreadMessages is zero if there's
 | 
						IsDirect bool
 | 
				
			||||||
	// a notificationless message like bot notices.
 | 
					 | 
				
			||||||
	HasNewMessages bool
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// List of tags given to this room
 | 
						// List of tags given to this room
 | 
				
			||||||
	RawTags []RoomTag
 | 
						RawTags []RoomTag
 | 
				
			||||||
@@ -110,14 +114,74 @@ func (room *Room) UnlockHistory() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MarkRead clears the new message statuses on this room.
 | 
					// MarkRead clears the new message statuses on this room.
 | 
				
			||||||
func (room *Room) MarkRead() {
 | 
					func (room *Room) MarkRead(eventID string) {
 | 
				
			||||||
	room.UnreadMessages = 0
 | 
						readToIndex := -1
 | 
				
			||||||
	room.Highlighted = false
 | 
						for index, unreadMessage := range room.UnreadMessages {
 | 
				
			||||||
	room.HasNewMessages = false
 | 
							if unreadMessage.EventID == eventID {
 | 
				
			||||||
 | 
								readToIndex = index
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if readToIndex >= 0 {
 | 
				
			||||||
 | 
							room.UnreadMessages = room.UnreadMessages[readToIndex+1:]
 | 
				
			||||||
 | 
							room.highlightCache = nil
 | 
				
			||||||
 | 
							room.unreadCountCache = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) UnreadCount() int {
 | 
				
			||||||
 | 
						if room.unreadCountCache == nil {
 | 
				
			||||||
 | 
							room.unreadCountCache = new(int)
 | 
				
			||||||
 | 
							for _, unreadMessage := range room.UnreadMessages {
 | 
				
			||||||
 | 
								if unreadMessage.Counted {
 | 
				
			||||||
 | 
									*room.unreadCountCache++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return *room.unreadCountCache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) Highlighted() bool {
 | 
				
			||||||
 | 
						if room.highlightCache == nil {
 | 
				
			||||||
 | 
							room.highlightCache = new(bool)
 | 
				
			||||||
 | 
							for _, unreadMessage := range room.UnreadMessages {
 | 
				
			||||||
 | 
								if unreadMessage.Highlight {
 | 
				
			||||||
 | 
									*room.highlightCache = true
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return *room.highlightCache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) HasNewMessages() bool {
 | 
				
			||||||
 | 
						return len(room.UnreadMessages) > 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (room *Room) AddUnread(eventID string, counted, highlight bool) {
 | 
				
			||||||
 | 
						room.UnreadMessages = append(room.UnreadMessages, UnreadMessage{
 | 
				
			||||||
 | 
							EventID: eventID,
 | 
				
			||||||
 | 
							Counted: counted,
 | 
				
			||||||
 | 
							Highlight: highlight,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if counted {
 | 
				
			||||||
 | 
							if room.unreadCountCache == nil {
 | 
				
			||||||
 | 
								room.unreadCountCache = new(int)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							*room.unreadCountCache++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if highlight {
 | 
				
			||||||
 | 
							if room.highlightCache == nil {
 | 
				
			||||||
 | 
								room.highlightCache = new(bool)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							*room.highlightCache = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (room *Room) Tags() []RoomTag {
 | 
					func (room *Room) Tags() []RoomTag {
 | 
				
			||||||
	if len(room.RawTags) == 0 {
 | 
						if len(room.RawTags) == 0 {
 | 
				
			||||||
 | 
							if room.IsDirect {
 | 
				
			||||||
 | 
								return []RoomTag{{"net.maunium.gomuks.fake.direct", "0.5"}}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return []RoomTag{{"", "0.5"}}
 | 
							return []RoomTag{{"", "0.5"}}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return room.RawTags
 | 
						return room.RawTags
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -215,11 +215,19 @@ func TestRoom_GetTitle_Members_GroupChat(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestRoom_MarkRead(t *testing.T) {
 | 
					func TestRoom_MarkRead(t *testing.T) {
 | 
				
			||||||
	room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
 | 
						room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
 | 
				
			||||||
	room.UnreadMessages = 123
 | 
					
 | 
				
			||||||
	room.Highlighted = true
 | 
						room.AddUnread("foo", true, false)
 | 
				
			||||||
	room.HasNewMessages = true
 | 
						assert.Equal(t, 1, room.UnreadCount())
 | 
				
			||||||
	room.MarkRead()
 | 
						assert.False(t, room.Highlighted())
 | 
				
			||||||
	assert.Zero(t, room.UnreadMessages)
 | 
					
 | 
				
			||||||
	assert.False(t, room.Highlighted)
 | 
						room.AddUnread("bar", true, false)
 | 
				
			||||||
	assert.False(t, room.HasNewMessages)
 | 
						assert.Equal(t, 2, room.UnreadCount())
 | 
				
			||||||
 | 
						assert.False(t, room.Highlighted())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						room.AddUnread("asd", false, true)
 | 
				
			||||||
 | 
						assert.Equal(t, 2, room.UnreadCount())
 | 
				
			||||||
 | 
						assert.True(t, room.Highlighted())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						room.MarkRead("")
 | 
				
			||||||
 | 
						assert.Empty(t, room.UnreadMessages)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -177,14 +177,14 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
 | 
				
			|||||||
				Limit: 50,
 | 
									Limit: 50,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Ephemeral: gomatrix.FilterPart{
 | 
								Ephemeral: gomatrix.FilterPart{
 | 
				
			||||||
				Types: []string{"m.typing"},
 | 
									Types: []string{"m.typing", "m.receipt"},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			AccountData: gomatrix.FilterPart{
 | 
								AccountData: gomatrix.FilterPart{
 | 
				
			||||||
				Types: []string{"m.tag"},
 | 
									Types: []string{"m.tag"},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		AccountData: gomatrix.FilterPart{
 | 
							AccountData: gomatrix.FilterPart{
 | 
				
			||||||
			Types: []string{"m.push_rules"},
 | 
								Types: []string{"m.push_rules", "m.direct"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		Presence: gomatrix.FilterPart{
 | 
							Presence: gomatrix.FilterPart{
 | 
				
			||||||
			Types: []string{},
 | 
								Types: []string{},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -265,6 +265,7 @@ func (list *RoomList) Contains(roomID string) bool {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (list *RoomList) Add(room *rooms.Room) {
 | 
					func (list *RoomList) Add(room *rooms.Room) {
 | 
				
			||||||
 | 
						debug.Print("Adding room to list", room.ID, room.GetTitle(), room.IsDirect, room.Tags())
 | 
				
			||||||
	for _, tag := range room.Tags() {
 | 
						for _, tag := range room.Tags() {
 | 
				
			||||||
		list.AddToTag(tag, room)
 | 
							list.AddToTag(tag, room)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -289,10 +290,6 @@ func (list *RoomList) CheckTag(tag string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) {
 | 
					func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) {
 | 
				
			||||||
	if tag.Tag == "" && len(room.GetMembers()) == 2 {
 | 
					 | 
				
			||||||
		tag.Tag = "net.maunium.gomuks.fake.direct"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tagRoomList, ok := list.items[tag.Tag]
 | 
						tagRoomList, ok := list.items[tag.Tag]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		list.items[tag.Tag] = newTagRoomList(convertRoom(room))
 | 
							list.items[tag.Tag] = newTagRoomList(convertRoom(room))
 | 
				
			||||||
@@ -304,8 +301,8 @@ func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (list *RoomList) Remove(room *rooms.Room) {
 | 
					func (list *RoomList) Remove(room *rooms.Room) {
 | 
				
			||||||
	for _, tag := range room.Tags() {
 | 
						for _, tag := range list.tags {
 | 
				
			||||||
		list.RemoveFromTag(tag.Tag, room)
 | 
							list.RemoveFromTag(tag, room)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -707,21 +704,22 @@ func (list *RoomList) Draw(screen tcell.Screen) {
 | 
				
			|||||||
			if tag == list.selectedTag && item.Room == list.selected {
 | 
								if tag == list.selectedTag && item.Room == list.selected {
 | 
				
			||||||
				style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor)
 | 
									style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if item.HasNewMessages {
 | 
								if item.HasNewMessages() {
 | 
				
			||||||
				style = style.Bold(true)
 | 
									style = style.Bold(true)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if item.UnreadMessages > 0 {
 | 
								unreadCount := item.UnreadCount()
 | 
				
			||||||
 | 
								if unreadCount > 0 {
 | 
				
			||||||
				unreadMessageCount := "99+"
 | 
									unreadMessageCount := "99+"
 | 
				
			||||||
				if item.UnreadMessages < 100 {
 | 
									if unreadCount < 100 {
 | 
				
			||||||
					unreadMessageCount = strconv.Itoa(item.UnreadMessages)
 | 
										unreadMessageCount = strconv.Itoa(unreadCount)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if item.Highlighted {
 | 
									if item.Highlighted() {
 | 
				
			||||||
					unreadMessageCount += "!"
 | 
										unreadMessageCount += "!"
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
 | 
									unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
 | 
				
			||||||
				widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-6, y, 6, style)
 | 
									widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-7, y, 7, style)
 | 
				
			||||||
				lineWidth -= len(unreadMessageCount) + 1
 | 
									lineWidth -= len(unreadMessageCount)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			widget.WriteLinePadded(screen, tview.AlignLeft, text, x, y, lineWidth, style)
 | 
								widget.WriteLinePadded(screen, tview.AlignLeft, text, x, y, lineWidth, style)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,15 +67,25 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
 | 
				
			|||||||
	mainView.AddItem(mainView.roomList, 25, 0, false)
 | 
						mainView.AddItem(mainView.roomList, 25, 0, false)
 | 
				
			||||||
	mainView.AddItem(widget.NewBorder(), 1, 0, false)
 | 
						mainView.AddItem(widget.NewBorder(), 1, 0, false)
 | 
				
			||||||
	mainView.AddItem(mainView.roomView, 0, 1, true)
 | 
						mainView.AddItem(mainView.roomView, 0, 1, true)
 | 
				
			||||||
	mainView.BumpFocus()
 | 
						mainView.BumpFocus(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ui.mainView = mainView
 | 
						ui.mainView = mainView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return mainView
 | 
						return mainView
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) BumpFocus() {
 | 
					func (view *MainView) BumpFocus(roomView *RoomView) {
 | 
				
			||||||
	view.lastFocusTime = time.Now()
 | 
						view.lastFocusTime = time.Now()
 | 
				
			||||||
 | 
						view.MarkRead(roomView)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *MainView) MarkRead(roomView *RoomView) {
 | 
				
			||||||
 | 
						if roomView != nil && roomView.Room.HasNewMessages() && roomView.MessageView().ScrollOffset == 0 {
 | 
				
			||||||
 | 
							msgList := roomView.MessageView().messages
 | 
				
			||||||
 | 
							msg := msgList[len(msgList)-1]
 | 
				
			||||||
 | 
							roomView.Room.MarkRead(msg.ID())
 | 
				
			||||||
 | 
							view.matrix.MarkRead(roomView.Room.ID, msg.ID())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) InputChanged(roomView *RoomView, text string) {
 | 
					func (view *MainView) InputChanged(roomView *RoomView, text string) {
 | 
				
			||||||
@@ -182,7 +192,7 @@ func (view *MainView) HandleCommand(roomView *RoomView, command string, args []s
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) KeyEventHandler(roomView *RoomView, key *tcell.EventKey) *tcell.EventKey {
 | 
					func (view *MainView) KeyEventHandler(roomView *RoomView, key *tcell.EventKey) *tcell.EventKey {
 | 
				
			||||||
	view.BumpFocus()
 | 
						view.BumpFocus(roomView)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k := key.Key()
 | 
						k := key.Key()
 | 
				
			||||||
	if key.Modifiers() == tcell.ModCtrl || key.Modifiers() == tcell.ModAlt {
 | 
						if key.Modifiers() == tcell.ModCtrl || key.Modifiers() == tcell.ModAlt {
 | 
				
			||||||
@@ -232,7 +242,7 @@ func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMo
 | 
				
			|||||||
	if event.Buttons() == tcell.ButtonNone || event.HasMotion() {
 | 
						if event.Buttons() == tcell.ButtonNone || event.HasMotion() {
 | 
				
			||||||
		return event
 | 
							return event
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	view.BumpFocus()
 | 
						view.BumpFocus(roomView)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	msgView := roomView.MessageView()
 | 
						msgView := roomView.MessageView()
 | 
				
			||||||
	x, y := event.Position()
 | 
						x, y := event.Position()
 | 
				
			||||||
@@ -251,12 +261,8 @@ func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMo
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		case tcell.WheelDown:
 | 
							case tcell.WheelDown:
 | 
				
			||||||
			msgView.AddScrollOffset(-WheelScrollOffsetDiff)
 | 
								msgView.AddScrollOffset(-WheelScrollOffsetDiff)
 | 
				
			||||||
 | 
					 | 
				
			||||||
			view.parent.Render()
 | 
								view.parent.Render()
 | 
				
			||||||
 | 
								view.MarkRead(roomView)
 | 
				
			||||||
			if msgView.ScrollOffset == 0 {
 | 
					 | 
				
			||||||
				roomView.Room.MarkRead()
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			if msgView.HandleClick(x-mx, y-my, event.Buttons()) {
 | 
								if msgView.HandleClick(x-mx, y-my, event.Buttons()) {
 | 
				
			||||||
				view.parent.Render()
 | 
									view.parent.Render()
 | 
				
			||||||
@@ -293,9 +299,12 @@ func (view *MainView) SwitchRoom(tag string, room *rooms.Room) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	view.roomView.SwitchToPage(room.ID)
 | 
						view.roomView.SwitchToPage(room.ID)
 | 
				
			||||||
	roomView := view.rooms[room.ID]
 | 
						roomView := view.rooms[room.ID]
 | 
				
			||||||
	if roomView.MessageView().ScrollOffset == 0 {
 | 
						if roomView == nil {
 | 
				
			||||||
		roomView.Room.MarkRead()
 | 
							debug.Print("Tried to switch to non-nil room with nil roomView!")
 | 
				
			||||||
 | 
							debug.Print(tag, room)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						view.MarkRead(roomView)
 | 
				
			||||||
	view.roomList.SetSelected(tag, room)
 | 
						view.roomList.SetSelected(tag, room)
 | 
				
			||||||
	view.parent.app.SetFocus(view)
 | 
						view.parent.app.SetFocus(view)
 | 
				
			||||||
	view.parent.Render()
 | 
						view.parent.Render()
 | 
				
			||||||
@@ -353,10 +362,10 @@ func (view *MainView) GetRoom(roomID string) ifc.RoomView {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (view *MainView) AddRoom(room *rooms.Room) {
 | 
					func (view *MainView) AddRoom(room *rooms.Room) {
 | 
				
			||||||
	if view.roomList.Contains(room.ID) {
 | 
						if view.roomList.Contains(room.ID) {
 | 
				
			||||||
		debug.Print("Add aborted", room.ID)
 | 
							debug.Print("Add aborted (room exists)", room.ID, room.GetTitle())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	debug.Print("Adding", room.ID)
 | 
						debug.Print("Adding", room.ID, room.GetTitle())
 | 
				
			||||||
	view.roomList.Add(room)
 | 
						view.roomList.Add(room)
 | 
				
			||||||
	view.addRoomPage(room)
 | 
						view.addRoomPage(room)
 | 
				
			||||||
	if !view.roomList.HasSelected() {
 | 
						if !view.roomList.HasSelected() {
 | 
				
			||||||
@@ -367,10 +376,10 @@ func (view *MainView) AddRoom(room *rooms.Room) {
 | 
				
			|||||||
func (view *MainView) RemoveRoom(room *rooms.Room) {
 | 
					func (view *MainView) RemoveRoom(room *rooms.Room) {
 | 
				
			||||||
	roomView := view.GetRoom(room.ID)
 | 
						roomView := view.GetRoom(room.ID)
 | 
				
			||||||
	if roomView == nil {
 | 
						if roomView == nil {
 | 
				
			||||||
		debug.Print("Remove aborted", room.ID)
 | 
							debug.Print("Remove aborted (not found)", room.ID, room.GetTitle())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	debug.Print("Removing", room.ID)
 | 
						debug.Print("Removing", room.ID, room.GetTitle())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	view.roomList.Remove(room)
 | 
						view.roomList.Remove(room)
 | 
				
			||||||
	view.SwitchRoom(view.roomList.Selected())
 | 
						view.SwitchRoom(view.roomList.Selected())
 | 
				
			||||||
@@ -395,38 +404,12 @@ func (view *MainView) SetRooms(rooms map[string]*rooms.Room) {
 | 
				
			|||||||
	view.SwitchRoom(view.roomList.First())
 | 
						view.SwitchRoom(view.roomList.First())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) UpdateTags(room *rooms.Room, newTags []rooms.RoomTag) {
 | 
					func (view *MainView) UpdateTags(room *rooms.Room) {
 | 
				
			||||||
	if len(newTags) == 0 {
 | 
						if !view.roomList.Contains(room.ID) {
 | 
				
			||||||
		for _, tag := range room.RawTags {
 | 
							return
 | 
				
			||||||
			view.roomList.RemoveFromTag(tag.Tag, room)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
		view.roomList.AddToTag(rooms.RoomTag{Tag: "", Order: "0.5"}, room)
 | 
						view.roomList.Remove(room)
 | 
				
			||||||
	} else if len(room.RawTags) == 0 {
 | 
						view.roomList.Add(room)
 | 
				
			||||||
		view.roomList.RemoveFromTag("", room)
 | 
					 | 
				
			||||||
		for _, tag := range newTags {
 | 
					 | 
				
			||||||
			view.roomList.AddToTag(tag, room)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
	NewTags:
 | 
					 | 
				
			||||||
		for _, newTag := range newTags {
 | 
					 | 
				
			||||||
			for _, oldTag := range room.RawTags {
 | 
					 | 
				
			||||||
				if newTag.Tag == oldTag.Tag {
 | 
					 | 
				
			||||||
					continue NewTags
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			view.roomList.AddToTag(newTag, room)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	OldTags:
 | 
					 | 
				
			||||||
		for _, oldTag := range room.RawTags {
 | 
					 | 
				
			||||||
			for _, newTag := range newTags {
 | 
					 | 
				
			||||||
				if newTag.Tag == oldTag.Tag {
 | 
					 | 
				
			||||||
					continue OldTags
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			view.roomList.RemoveFromTag(oldTag.Tag, room)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	room.RawTags = newTags
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) SetTyping(room string, users []string) {
 | 
					func (view *MainView) SetTyping(room string, users []string) {
 | 
				
			||||||
@@ -449,21 +432,20 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
 | 
				
			|||||||
	// Whether or not the room where the message came is the currently shown room.
 | 
						// Whether or not the room where the message came is the currently shown room.
 | 
				
			||||||
	isCurrent := room == view.roomList.SelectedRoom()
 | 
						isCurrent := room == view.roomList.SelectedRoom()
 | 
				
			||||||
	// Whether or not the terminal window is focused.
 | 
						// Whether or not the terminal window is focused.
 | 
				
			||||||
	isFocused := time.Now().Add(-30 * time.Second).Before(view.lastFocusTime)
 | 
						recentlyFocused := time.Now().Add(-30 * time.Second).Before(view.lastFocusTime)
 | 
				
			||||||
 | 
						isFocused := time.Now().Add(-5 * time.Second).Before(view.lastFocusTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Whether or not the push rules say this message should be notified about.
 | 
						// Whether or not the push rules say this message should be notified about.
 | 
				
			||||||
	shouldNotify := (should.Notify || !should.NotifySpecified) && message.Sender() != view.config.Session.UserID
 | 
						shouldNotify := (should.Notify || !should.NotifySpecified) && message.Sender() != view.config.Session.UserID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !isCurrent {
 | 
						if !isCurrent || !isFocused {
 | 
				
			||||||
		// The message is not in the current room, show new message status in room list.
 | 
							// The message is not in the current room, show new message status in room list.
 | 
				
			||||||
		room.HasNewMessages = true
 | 
							room.AddUnread(message.ID(), shouldNotify, should.Highlight)
 | 
				
			||||||
		room.Highlighted = should.Highlight || room.Highlighted
 | 
						} else {
 | 
				
			||||||
		if shouldNotify {
 | 
							view.matrix.MarkRead(room.ID, message.ID())
 | 
				
			||||||
			room.UnreadMessages++
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if shouldNotify && !isFocused {
 | 
						if shouldNotify && !recentlyFocused {
 | 
				
			||||||
		// Push rules say notify and the terminal is not focused, send desktop notification.
 | 
							// Push rules say notify and the terminal is not focused, send desktop notification.
 | 
				
			||||||
		shouldPlaySound := should.PlaySound && should.SoundName == "default"
 | 
							shouldPlaySound := should.PlaySound && should.SoundName == "default"
 | 
				
			||||||
		sendNotification(room, message.Sender(), message.NotificationContent(), should.Highlight, shouldPlaySound)
 | 
							sendNotification(room, message.Sender(), message.NotificationContent(), should.Highlight, shouldPlaySound)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user