Refactor channel loading
* LoadChannel has been split off into LoadChannel and UnloadChannel
* "Switch to last Channel" has been hugely simplified
* window.previosuGuild reference is completly gone now, as it's
infered from window.previousChannel
* Simplified some names and methods in the components for DMs, Guilds
and Channels
MODIFY
ui/channeltree.go
ui/channeltree.go
@@ -294,35 +294,28 @@ func (channelTree *ChannelTree) removeNode(node, parent *tview.TreeNode, channel
}
}
-// MarkChannelAsUnread marks a channel as unread.
-func (channelTree *ChannelTree) MarkChannelAsUnread(channelID string) {
- channelTree.GetRoot().Walk(func(node, parent *tview.TreeNode) bool {
- referenceChannelID, ok := node.GetReference().(string)
- if ok && referenceChannelID == channelID {
+// MarkAsUnread marks a channel as unread.
+func (channelTree *ChannelTree) MarkAsUnread(channelID string) {
+ node := tviewutil.GetNodeByReference(channelID, channelTree.TreeView)
+ if node != nil {
channelTree.channelStates[node] = channelUnread
if vtxxx {
node.SetAttributes(tcell.AttrBlink)
} else {
node.SetColor(config.GetTheme().AttentionColor)
}
- return false
}
-
- return true
- })
}
-// MarkChannelAsRead marks a channel as read if it's not loaded already.
-func (channelTree *ChannelTree) MarkChannelAsRead(channelID string) {
- channelTree.GetRoot().Walk(func(node, parent *tview.TreeNode) bool {
- referenceChannelID, ok := node.GetReference().(string)
- if ok && referenceChannelID == channelID {
+// MarkAsRead marks a channel as read.
+func (channelTree *ChannelTree) MarkAsRead(channelID string) {
+ node := tviewutil.GetNodeByReference(channelID, channelTree.TreeView)
+ if node != nil {
channel, stateError := channelTree.state.Channel(channelID)
if stateError == nil {
node.SetText(tviewutil.Escape(channel.Name))
}
- if channelTree.channelStates[node] != channelLoaded {
channelTree.channelStates[node] = channelRead
if vtxxx {
node.SetAttributes(tcell.AttrNone)
@@ -330,25 +323,14 @@ func (channelTree *ChannelTree) MarkChannelAsRead(channelID string) {
node.SetColor(config.GetTheme().PrimaryTextColor)
}
}
-
- return false
}
- return true
- })
-}
-
-// MarkChannelAsMentioned marks a channel as mentioned.
-func (channelTree *ChannelTree) MarkChannelAsMentioned(channelID string) {
- channelTree.GetRoot().Walk(func(node, parent *tview.TreeNode) bool {
- referenceChannelID, ok := node.GetReference().(string)
- if ok && referenceChannelID == channelID {
+// MarkAsMentioned marks a channel as mentioned.
+func (channelTree *ChannelTree) MarkAsMentioned(channelID string) {
+ node := tviewutil.GetNodeByReference(channelID, channelTree.TreeView)
+ if node != nil {
channelTree.markNodeAsMentioned(node, channelID)
- return false
}
-
- return true
- })
}
func (channelTree *ChannelTree) markNodeAsMentioned(node *tview.TreeNode, channelID string) {
@@ -364,9 +346,9 @@ func (channelTree *ChannelTree) markNodeAsMentioned(node *tview.TreeNode, channe
}
}
-// MarkChannelAsLoaded marks a channel as loaded and therefore marks all other
+// MarkAsLoaded marks a channel as loaded and therefore marks all other
// channels as either unread, read or mentioned.
-func (channelTree *ChannelTree) MarkChannelAsLoaded(channelID string) {
+func (channelTree *ChannelTree) MarkAsLoaded(channelID string) {
for node, state := range channelTree.channelStates {
if state == channelLoaded {
channelTree.channelStates[node] = channelRead

MODIFY
ui/guildlist.go
ui/guildlist.go
@@ -166,3 +166,16 @@ func (g *GuildList) amountOfUnreadGuilds() int {
func (g *GuildList) UpdateUnreadGuildCount() {
g.setNotificationCount(g.amountOfUnreadGuilds())
}
+
+// MarkAsLoaded selects the guild and marks it as loaded.
+func (g *GuildList) MarkAsLoaded(guildID string) {
+ guildNode := tviewutil.GetNodeByReference(guildID, g.TreeView)
+ if guildNode != nil {
+ g.SetCurrentNode(guildNode)
+ if vtxxx {
+ guildNode.SetAttributes(tcell.AttrUnderline)
+ } else {
+ guildNode.SetColor(tview.Styles.ContrastBackgroundColor)
+ }
+ }
+}

MODIFY
ui/privatechats.go
ui/privatechats.go
@@ -197,7 +197,6 @@ func (privateList *PrivateChatList) RemoveFriend(userID string) {
// RemoveChannel removes a channel node if present.
func (privateList *PrivateChatList) RemoveChannel(channel *discordgo.Channel) {
newChildren := make([]*tview.TreeNode, 0)
-
channelID := channel.ID
for _, node := range privateList.chatsNode.GetChildren() {
@@ -222,11 +221,10 @@ func (privateList *PrivateChatList) RemoveChannel(channel *discordgo.Channel) {
privateList.chatsNode.SetChildren(newChildren)
}
-// MarkChannelAsUnread marks the channel as unread, coloring it red.
-func (privateList *PrivateChatList) MarkChannelAsUnread(channel *discordgo.Channel) {
- for _, node := range privateList.chatsNode.GetChildren() {
- referenceChannelID, ok := node.GetReference().(string)
- if ok && referenceChannelID == channel.ID {
+// MarkAsUnread marks the channel as unread, coloring it red.
+func (privateList *PrivateChatList) MarkAsUnread(channelID string) {
+ node := tviewutil.GetNodeByReference(channelID, privateList.internalTreeView)
+ if node != nil {
privateList.privateChannelStates[node] = unread
privateList.setNotificationCount(privateList.amountOfUnreadChannels())
if vtxxx {
@@ -234,8 +232,6 @@ func (privateList *PrivateChatList) MarkChannelAsUnread(channel *discordgo.Chann
} else {
node.SetColor(config.GetTheme().AttentionColor)
}
- break
- }
}
}
@@ -250,12 +246,10 @@ func (privateList *PrivateChatList) amountOfUnreadChannels() int {
return amount
}
-// MarkChannelAsRead marks a channel as read if it isn't loaded already
-func (privateList *PrivateChatList) MarkChannelAsRead(channelID string) {
- for _, node := range privateList.chatsNode.GetChildren() {
- referenceChannelID, ok := node.GetReference().(string)
- if ok && referenceChannelID == channelID {
- if privateList.privateChannelStates[node] != loaded {
+// MarkAsRead marks a channel as read.
+func (privateList *PrivateChatList) MarkAsRead(channelID string) {
+ node := tviewutil.GetNodeByReference(channelID, privateList.internalTreeView)
+ if node != nil {
privateList.setNotificationCount(privateList.amountOfUnreadChannels())
privateList.privateChannelStates[node] = read
if vtxxx {
@@ -264,14 +258,11 @@ func (privateList *PrivateChatList) MarkChannelAsRead(channelID string) {
node.SetColor(config.GetTheme().PrimaryTextColor)
}
}
- break
- }
- }
}
-// ReorderChannelList resorts the list of private chats according to their last
+// Reorder resorts the list of private chats according to their last
// message times.
-func (privateList *PrivateChatList) ReorderChannelList() {
+func (privateList *PrivateChatList) Reorder() {
children := privateList.chatsNode.GetChildren()
sort.Slice(children, func(a, b int) bool {
nodeA := children[a]
@@ -299,9 +290,9 @@ func (privateList *PrivateChatList) ReorderChannelList() {
})
}
-// MarkChannelAsLoaded marks a channel as loaded, coloring it blue. If
+// MarkAsLoaded marks a channel as loaded, coloring it blue. If
// a different channel had loaded before, it's set to read.
-func (privateList *PrivateChatList) MarkChannelAsLoaded(channel *discordgo.Channel) {
+func (privateList *PrivateChatList) MarkAsLoaded(channelID string) {
for node, state := range privateList.privateChannelStates {
if state == loaded {
privateList.privateChannelStates[node] = read
@@ -316,7 +307,7 @@ func (privateList *PrivateChatList) MarkChannelAsLoaded(channel *discordgo.Chann
for _, node := range privateList.chatsNode.GetChildren() {
referenceChannelID, ok := node.GetReference().(string)
- if ok && referenceChannelID == channel.ID {
+ if ok && referenceChannelID == channelID {
privateList.privateChannelStates[node] = loaded
if vtxxx {
node.SetAttributes(tcell.AttrUnderline)

MODIFY
ui/window.go
ui/window.go
@@ -77,7 +77,6 @@ type Window struct {
session *discordgo.Session
selectedGuild *discordgo.Guild
- previousGuild *discordgo.Guild
selectedChannel *discordgo.Channel
previousChannel *discordgo.Channel
@@ -160,8 +159,8 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
defer window.chatView.Unlock()
window.QueueUpdateDrawSynchronized(func() {
loadError := window.LoadChannel(channel)
- if loadError == nil {
- channelTree.MarkChannelAsLoaded(channelID)
+ if loadError != nil {
+ window.ShowErrorDialog(loadError.Error())
}
})
}()
@@ -185,16 +184,7 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
return
}
- // previousGuild and previousGuildNode should be set initially.
- // If going from first guild -> private chat, SwitchToPreviousChannel would crash.
- if window.selectedGuild == nil {
- window.previousGuild = guild
- } else if window.previousGuild != window.selectedGuild {
- window.previousGuild = window.selectedGuild
- }
-
window.selectedGuild = guild
-
window.updateServerReadStatus(guild.ID, true)
//FIXME Request presences as soon as that stuff remotely works?
@@ -900,10 +890,14 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
channel, stateError := s.State.Channel(event.ChannelID)
if stateError == nil && event.MessageID == channel.LastMessageID {
if channel.GuildID == "" {
- window.privateList.MarkChannelAsRead(channel.ID)
+ if window.selectedChannel != nil || window.selectedChannel.ID != channel.ID {
+ window.privateList.MarkAsRead(channel.ID)
+ }
} else {
if window.selectedGuild != nil && channel.GuildID == window.selectedGuild.ID {
- window.channelTree.MarkChannelAsRead(channel.ID)
+ if window.selectedChannel != nil || window.selectedChannel.ID != channel.ID {
+ window.channelTree.MarkAsRead(channel.ID)
+ }
} else {
window.updateServerReadStatus(channel.GuildID, false)
}
@@ -1740,10 +1734,10 @@ func (window *Window) startMessageHandlerRoutines(input, edit, delete chan *disc
//FIXME Useful to use locking here?
if window.activeView == Dms {
window.app.QueueUpdateDraw(func() {
- window.privateList.ReorderChannelList()
+ window.privateList.Reorder()
})
} else {
- window.privateList.ReorderChannelList()
+ window.privateList.Reorder()
}
}
@@ -1763,7 +1757,7 @@ func (window *Window) startMessageHandlerRoutines(input, edit, delete chan *disc
if channel.Type == discordgo.ChannelTypeDM || channel.Type == discordgo.ChannelTypeGroupDM {
if !readstate.IsPrivateChannelMuted(channel) {
window.app.QueueUpdateDraw(func() {
- window.privateList.MarkChannelAsUnread(channel)
+ window.privateList.MarkAsUnread(channel.ID)
})
}
} else if channel.Type == discordgo.ChannelTypeGuildText {
@@ -1772,11 +1766,11 @@ func (window *Window) startMessageHandlerRoutines(input, edit, delete chan *disc
window.app.QueueUpdateDraw(func() {
isCurrentGuild := window.selectedGuild != nil && window.selectedGuild.ID == channel.GuildID
window.updateServerReadStatus(channel.GuildID, isCurrentGuild)
- window.channelTree.MarkChannelAsMentioned(channel.ID)
+ window.channelTree.MarkAsMentioned(channel.ID)
})
} else if !readstate.IsGuildChannelMuted(channel) {
window.app.QueueUpdateDraw(func() {
- window.channelTree.MarkChannelAsUnread(channel.ID)
+ window.channelTree.MarkAsUnread(channel.ID)
})
}
}
@@ -1908,8 +1902,7 @@ func (window *Window) registerGuildHandlers() {
continue
}
- if window.previousGuild != nil && window.previousGuild.ID == guildRemove.ID {
- window.previousGuild = nil
+ if window.previousChannel != nil && window.previousChannel.GuildID == guildRemove.ID {
window.previousChannel = nil
}
@@ -2053,7 +2046,6 @@ func (window *Window) registerGuildChannelHandler() {
window.session.AddHandler(func(s *discordgo.Session, event *discordgo.ChannelDelete) {
if window.isChannelEventRelevant(event.Channel) {
if window.previousChannel != nil && window.previousChannel.ID == event.ID {
- window.previousGuild = nil
window.previousChannel = nil
}
@@ -2508,41 +2500,43 @@ func (window *Window) SwitchToFriendsPage() {
// SwitchToPreviousChannel loads the previously loaded channel and focuses it
// in it's respective UI primitive.
func (window *Window) SwitchToPreviousChannel() error {
- if window.previousChannel == nil || window.previousChannel == window.selectedChannel {
+ previousChannel := window.previousChannel
+ if previousChannel == nil || previousChannel == window.selectedChannel {
// No previous channel.
return nil
}
- _, err := window.session.State.Channel(window.previousChannel.ID)
- if err != nil {
+ _, channelStateError := window.session.State.Channel(previousChannel.ID)
+ if channelStateError != nil {
window.previousChannel = nil
- return fmt.Errorf("Channel %s not found", window.previousChannel.Name)
+ return fmt.Errorf("channel %s not found", previousChannel.Name)
}
- // Switch to appropriate layout.
- switch window.previousChannel.Type {
- case discordgo.ChannelTypeDM, discordgo.ChannelTypeGroupDM:
+ if previousChannel.GuildID == "" {
window.SwitchToFriendsPage()
window.privateList.onChannelSelect(window.previousChannel.ID)
- case discordgo.ChannelTypeGuildText:
- _, err := window.session.State.Guild(window.previousGuild.ID)
- if err != nil {
- window.previousGuild = nil
- return fmt.Errorf("Unable to load guild: %s", window.previousGuild.Name)
+ } else {
+ _, guildStateError := window.session.State.Guild(previousChannel.GuildID)
+ if guildStateError != nil {
+ window.previousChannel = nil
+ return fmt.Errorf("Unable to load guild: %s", previousChannel.GuildID)
}
- if !discordutil.HasReadMessagesPermission(window.previousChannel.ID, window.session.State) {
- return fmt.Errorf("No read permissions for channel: %s", window.previousChannel.Name)
+
+ if !discordutil.HasReadMessagesPermission(previousChannel.ID, window.session.State) {
+ window.previousChannel = nil
+ return fmt.Errorf("No read permissions for channel: %s", previousChannel.Name)
}
+
window.SwitchToGuildsPage()
- previousGuildNode := tviewutil.GetNodeByReference(window.previousGuild.ID, window.guildList.TreeView)
- previousChannelNode := tviewutil.GetNodeByReference(window.previousChannel.ID, window.channelTree.TreeView)
+ window.guildList.onGuildSelect(previousChannel.GuildID)
+ window.channelTree.onChannelSelect(previousChannel.ID)
+
+ previousGuildNode := tviewutil.GetNodeByReference(previousChannel.GuildID, window.guildList.TreeView)
+ previousChannelNode := tviewutil.GetNodeByReference(previousChannel.ID, window.channelTree.TreeView)
window.guildList.SetCurrentNode(previousGuildNode)
- window.guildList.onGuildSelect(window.previousGuild.ID)
window.channelTree.SetCurrentNode(previousChannelNode)
- window.channelTree.onChannelSelect(window.previousChannel.ID)
- default:
- return fmt.Errorf("Invalid channel type: %v", window.previousChannel.Type)
}
+
window.app.SetFocus(window.messageInput.internalTextView)
return nil
}
@@ -2566,64 +2560,70 @@ func (window *Window) RefreshLayout() {
window.app.ForceDraw()
}
-//LoadChannel eagerly loads the channels messages.
-func (window *Window) LoadChannel(channel *discordgo.Channel) error {
- messages, loadError := window.messageLoader.LoadMessages(channel)
- if loadError != nil {
- return loadError
+// UnloadChannel resets the windows to the state at which no channel has
+// been loaded yet.
+func (window *Window) UnloadChannel() {
+ currentChannel := window.selectedChannel
+ //NO-OP, nothing to do here.
+ if currentChannel == nil {
+ return
}
- discordutil.SortMessagesByTimestamp(messages)
-
- window.chatView.SetMessages(messages)
- window.chatView.ClearSelection()
- window.chatView.internalTextView.ScrollToEnd()
-
- window.UpdateChatHeader(channel)
+ //window.selectedGuild is kept, as just the chatview is unloaded, but not
+ //the channel tree.
+ window.selectedChannel = nil
+ window.previousChannel = currentChannel
- if window.selectedChannel == nil {
- window.previousChannel = channel
- } else if channel != window.selectedChannel {
- window.previousChannel = window.selectedChannel
+ window.chatView.ClearViewAndCache()
+ window.UpdateChatHeader(nil)
+ window.exitMessageEditModeAndKeepText()
- // When switching to a channel in the same guild, the previousGuild must be set.
- if window.previousChannel.GuildID == channel.GuildID {
- window.previousGuild = window.selectedGuild
+ hasBeenRead := readstate.HasBeenRead(currentChannel, currentChannel.LastMessageID)
+ if currentChannel.Type == discordgo.ChannelTypeDM || currentChannel.Type == discordgo.ChannelTypeGroupDM {
+ if hasBeenRead {
+ window.privateList.MarkAsRead(currentChannel.ID)
+ } else {
+ window.privateList.MarkAsUnread(currentChannel.ID)
}
+ } else {
+ if hasBeenRead {
+ window.channelTree.MarkAsRead(currentChannel.ID)
+ } else {
+ window.channelTree.MarkAsUnread(currentChannel.ID)
}
-
- //If there is a currently loaded guild channel and it isn't the same as
- //the new one we assume it must be read and mark it white.
- if window.selectedChannel != nil && channel.ID != window.selectedChannel.ID {
- //FIXME Designflaw! We need to manually reset the primary text
- //color of the selected channels, this really sucks.
- selectedChannelID := window.selectedChannel.ID
- selectedChannelNode := tviewutil.GetNodeByReference(selectedChannelID, window.channelTree.TreeView)
- if selectedChannelNode == nil {
- selectedChannelNode = tviewutil.GetNodeByReference(selectedChannelID, window.privateList.internalTreeView)
- }
-
- if selectedChannelNode != nil {
- selectedChannelNode.SetColor(tview.Styles.PrimaryTextColor)
}
}
- window.selectedChannel = channel
+//LoadChannel eagerly loads the channels messages.
+func (window *Window) LoadChannel(channel *discordgo.Channel) error {
+ window.UnloadChannel()
- //Unlike with the channel, where we can assume it is read, we gotta check
- //whether there is still an unread channel and mark the server accordingly.
- wasSelectedGuild := window.selectedGuild != nil && window.selectedGuild.ID == channel.GuildID
+ var guild *discordgo.Guild
+ if channel.GuildID != "" {
+ var cacheError error
+ guild, cacheError = window.session.State.Guild(channel.GuildID)
+ if cacheError != nil {
+ return cacheError
+ }
+ }
- if channel.GuildID == "" {
- window.selectedGuild = nil
+ messages, loadError := window.messageLoader.LoadMessages(channel)
+ if loadError != nil {
+ return loadError
}
+ discordutil.SortMessagesByTimestamp(messages)
+
+ window.chatView.SetMessages(messages)
+ window.chatView.internalTextView.ScrollToEnd()
+ window.UpdateChatHeader(channel)
+ window.selectedChannel = channel
if channel.Type == discordgo.ChannelTypeDM || channel.Type == discordgo.ChannelTypeGroupDM {
- window.privateList.MarkChannelAsLoaded(channel)
+ window.privateList.MarkAsLoaded(channel.ID)
+ } else {
+ window.channelTree.MarkAsLoaded(channel.ID)
}
- window.exitMessageEditModeAndKeepText()
-
if config.Current.FocusMessageInputAfterChannelSelection {
window.app.SetFocus(window.messageInput.internalTextView)
}
@@ -2633,26 +2633,12 @@ func (window *Window) LoadChannel(channel *discordgo.Channel) error {
// Here we make the assumption that the channel we are loading must be part
// of the currently loaded guild, since we don't allow loading a channel of
// a guild otherwise.
- if channel.GuildID != "" {
- guild, cacheError := window.session.State.Guild(channel.GuildID)
-
+ if guild != nil {
window.app.QueueUpdateDraw(func() {
- window.updateServerReadStatus(channel.GuildID, wasSelectedGuild)
-
- if cacheError == nil {
- window.selectedGuild = guild
- for _, guildNode := range window.guildList.GetRoot().GetChildren() {
- if guildNode.GetReference() == channel.GuildID {
- window.guildList.SetCurrentNode(guildNode)
- if vtxxx {
- guildNode.SetAttributes(tcell.AttrUnderline)
- } else {
- guildNode.SetColor(tview.Styles.ContrastBackgroundColor)
- }
- break
- }
- }
- }
+ //Since we are in a background thread, we assume that the
+ //selected guild could have technically changed in the meantime.
+ selectedGuild := window.selectedGuild
+ window.updateServerReadStatus(channel.GuildID, selectedGuild != nil && window.selectedGuild.ID == channel.GuildID)
})
}
}()
@@ -2666,6 +2652,7 @@ func (window *Window) LoadChannel(channel *discordgo.Channel) error {
// in a group dm channel. If the channel has a nickname, that is chosen.
func (window *Window) UpdateChatHeader(channel *discordgo.Channel) {
if channel == nil {
+ window.chatView.SetTitle("")
return
}

Refactor channel loading

* LoadChannel has been split off into LoadChannel and UnloadChannel
* "Switch to last Channel" has been hugely simplified
  * window.previosuGuild reference is completly gone now, as it's
  infered from window.previousChannel
* Simplified some names and methods in the components for DMs, Guilds
and Channels