From 7fbccec227492c9c9f5188e1088e80304e1101fd Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 1 Jul 2021 21:44:51 +0200 Subject: [PATCH] fix(activity_center): Mark as read does not clear @ symbol in chat Mark all mention notifications as read is fixed. Also mark as read one by one notification removes "@" from the appropriate channel along with the marking as read last mention notification for that channel. hasMention field which was bool is switched with mentionsCount field which is int, so we have evidention how many mentions were for each channel. Fixes: #2788 --- src/app/chat/event_handling.nim | 11 ++- .../chat/views/activity_notification_list.nim | 39 +++++----- src/app/chat/views/channel.nim | 5 +- src/app/chat/views/channels_list.nim | 60 +++++++++++---- src/app/chat/views/chat_item.nim | 6 +- src/app/chat/views/communities.nim | 73 ++++++++++++++++--- src/app/chat/views/community_list.nim | 45 ++++++++++++ src/status/chat.nim | 21 +++++- src/status/chat/chat.nim | 2 +- src/status/signals/messages.nim | 6 +- .../Chat/ContactsColumn/Channel.qml | 6 +- .../Chat/ContactsColumn/ChannelList.qml | 2 +- 12 files changed, 219 insertions(+), 57 deletions(-) diff --git a/src/app/chat/event_handling.nim b/src/app/chat/event_handling.nim index 4a5949a6a6..d49a43481f 100644 --- a/src/app/chat/event_handling.nim +++ b/src/app/chat/event_handling.nim @@ -18,7 +18,9 @@ proc handleChatEvents(self: ChatController) = self.view.pushPinnedMessages(MsgsLoadedArgs(e).messages) self.status.events.on("activityCenterNotificationsLoaded") do(e:Args): - self.view.pushActivityCenterNotifications(ActivityCenterNotificationsArgs(e).activityCenterNotifications) + let notifications = ActivityCenterNotificationsArgs(e).activityCenterNotifications + self.view.pushActivityCenterNotifications(notifications) + self.view.communities.updateNotifications(notifications) self.status.events.on("contactUpdate") do(e: Args): var evArgs = ContactUpdateArgs(e) @@ -56,6 +58,7 @@ proc handleChatEvents(self: ChatController) = self.view.addPinnedMessages(evArgs.pinnedMessages) if (evArgs.activityCenterNotifications.len > 0): self.view.addActivityCenterNotification(evArgs.activityCenterNotifications) + self.view.communities.updateNotifications(evArgs.activityCenterNotifications) self.status.events.on("channelUpdate") do(e: Args): var evArgs = ChatUpdateArgs(e) @@ -139,6 +142,12 @@ proc handleChatEvents(self: ChatController) = else: self.view.stickers.resetBuyAttempt(tx.data.parseInt) + self.status.events.on("markNotificationsAsRead") do(e:Args): + let markAsReadProps = MarkAsReadNotificationProperties(e) + + #Notifying communities about this change. + self.view.communities.markNotificationsAsRead(markAsReadProps) + proc handleMailserverEvents(self: ChatController) = let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] # TODO: test mailserver topics when joining chat diff --git a/src/app/chat/views/activity_notification_list.nim b/src/app/chat/views/activity_notification_list.nim index fbc4e1e642..948be280f3 100644 --- a/src/app/chat/views/activity_notification_list.nim +++ b/src/app/chat/views/activity_notification_list.nim @@ -128,35 +128,36 @@ QtObject: let topLeft = self.createIndex(0, 0, nil) let bottomRight = self.createIndex(self.activityCenterNotifications.len - 1, 0, nil) self.dataChanged(topLeft, bottomRight, @[NotifRoles.Read.int]) - + proc reduceUnreadCount(self: ActivityNotificationList, numberNotifs: int) = self.nbUnreadNotifications = self.nbUnreadNotifications - numberNotifs if (self.nbUnreadNotifications < 0): self.nbUnreadNotifications = 0 self.unreadCountChanged() - proc markActivityCenterNotificationsRead(self: ActivityNotificationList, idsJson: string): string {.slot.} = - let ids = map(parseJson(idsJson).getElems(), proc(x:JsonNode):string = x.getStr()) + proc markActivityCenterNotificationRead(self: ActivityNotificationList, notificationId: string, + communityId: string, channelId: string, nType: int): void {.slot.} = - let error = self.status.chat.markActivityCenterNotificationsRead(ids) + let notificationType = ActivityCenterNotificationType(nType) + let markAsReadProps = MarkAsReadNotificationProperties(communityId: communityId, + channelId: channelId, notificationTypes: @[notificationType]) + + let error = self.status.chat.markActivityCenterNotificationRead(notificationId, markAsReadProps) if (error != ""): - return error - - self.reduceUnreadCount(ids.len) + return + + self.nbUnreadNotifications = self.nbUnreadNotifications - 1 + if (self.nbUnreadNotifications < 0): + self.nbUnreadNotifications = 0 + self.unreadCountChanged() var i = 0 - for activityCenterNotification in self.activityCenterNotifications: - for id in ids: - if (activityCenterNotification.id == id): - activityCenterNotification.read = true - let topLeft = self.createIndex(i, 0, nil) - let bottomRight = self.createIndex(i, 0, nil) - self.dataChanged(topLeft, bottomRight, @[NotifRoles.Read.int]) - break - i = i + 1 - - proc markActivityCenterNotificationRead(self: ActivityNotificationList, id: string): string {.slot.} = - self.markActivityCenterNotificationsRead(fmt"[""{id}""]") + for acnViewItem in self.activityCenterNotifications: + if (acnViewItem.id == notificationId): + acnViewItem.read = true + let index = self.createIndex(i, 0, nil) + self.dataChanged(index, index, @[NotifRoles.Read.int]) + i.inc proc removeNotifications(self: ActivityNotificationList, ids: seq[string]) = var i = 0 diff --git a/src/app/chat/views/channel.nim b/src/app/chat/views/channel.nim index eb5e34a11b..af2b455d1a 100644 --- a/src/app/chat/views/channel.nim +++ b/src/app/chat/views/channel.nim @@ -91,10 +91,11 @@ QtObject: discard self.status.chat.markAllChannelMessagesRead(selectedChannel.id) proc clearUnreadIfNeeded*(self: ChannelView, channel: var Chat) = - if (not channel.isNil and (channel.unviewedMessagesCount > 0 or channel.hasMentions)): + if (not channel.isNil and (channel.unviewedMessagesCount > 0 or channel.mentionsCount > 0)): var response = self.status.chat.markAllChannelMessagesRead(channel.id) if not response.hasKey("error"): - self.chats.clearUnreadMessagesCount(channel) + self.chats.clearUnreadMessages(channel.id) + self.chats.clearAllMentionsFromChannelWithId(channel.id) proc userNameOrAlias(self: ChannelView, pubKey: string): string = if self.status.chat.contacts.hasKey(pubKey): diff --git a/src/app/chat/views/channels_list.nim b/src/app/chat/views/channels_list.nim index b6e6757623..2735c84727 100644 --- a/src/app/chat/views/channels_list.nim +++ b/src/app/chat/views/channels_list.nim @@ -14,7 +14,7 @@ type Identicon = UserRole + 5 ChatType = UserRole + 6 Color = UserRole + 7 - HasMentions = UserRole + 8 + MentionsCount = UserRole + 8 ContentType = UserRole + 9 Muted = UserRole + 10 Id = UserRole + 11 @@ -74,7 +74,7 @@ QtObject: of ChannelsRoles.Identicon: result = newQVariant(chatItem.identicon) of ChannelsRoles.ChatType: result = newQVariant(chatItem.chatType.int) of ChannelsRoles.Color: result = newQVariant(chatItem.color) - of ChannelsRoles.HasMentions: result = newQVariant(chatItem.hasMentions) + of ChannelsRoles.MentionsCount: result = newQVariant(chatItem.mentionsCount.int) of ChannelsRoles.Muted: result = newQVariant(chatItem.muted.bool) of ChannelsRoles.Id: result = newQVariant($chatItem.id) of ChannelsRoles.CategoryId: result = newQVariant(chatItem.categoryId) @@ -89,7 +89,7 @@ QtObject: ChannelsRoles.Identicon.int: "identicon", ChannelsRoles.ChatType.int: "chatType", ChannelsRoles.Color.int: "color", - ChannelsRoles.HasMentions.int: "hasMentions", + ChannelsRoles.MentionsCount.int: "mentionsCount", ChannelsRoles.ContentType.int: "contentType", ChannelsRoles.Muted.int: "muted", ChannelsRoles.Id.int: "id", @@ -160,19 +160,53 @@ QtObject: self.chats[idx] = channel - self.dataChanged(topLeft, bottomRight, @[ChannelsRoles.Name.int, ChannelsRoles.Description.int, ChannelsRoles.ContentType.int, ChannelsRoles.LastMessage.int, ChannelsRoles.Timestamp.int, ChannelsRoles.UnreadMessages.int, ChannelsRoles.Identicon.int, ChannelsRoles.ChatType.int, ChannelsRoles.Color.int, ChannelsRoles.HasMentions.int, ChannelsRoles.Muted.int]) + self.dataChanged(topLeft, bottomRight, @[ChannelsRoles.Name.int, ChannelsRoles.Description.int, ChannelsRoles.ContentType.int, ChannelsRoles.LastMessage.int, ChannelsRoles.Timestamp.int, ChannelsRoles.UnreadMessages.int, ChannelsRoles.Identicon.int, ChannelsRoles.ChatType.int, ChannelsRoles.Color.int, ChannelsRoles.MentionsCount.int, ChannelsRoles.Muted.int]) - proc clearUnreadMessagesCount*(self: ChannelsList, channel: var Chat) = - let idx = self.chats.findIndexById(channel.id) - if idx == -1: return + proc clearUnreadMessages*(self: ChannelsList, channelId: string) = + let idx = self.chats.findIndexById(channelId) + if idx == -1: + return - let topLeft = self.createIndex(0, 0, nil) - let bottomRight = self.createIndex(self.chats.len, 0, nil) - channel.unviewedMessagesCount = 0 - channel.hasMentions = false - self.chats[idx] = channel + let index = self.createIndex(idx, 0, nil) + self.chats[idx].unviewedMessagesCount = 0 - self.dataChanged(topLeft, bottomRight, @[ChannelsRoles.Name.int, ChannelsRoles.Description.int, ChannelsRoles.ContentType.int, ChannelsRoles.LastMessage.int, ChannelsRoles.Timestamp.int, ChannelsRoles.UnreadMessages.int, ChannelsRoles.Identicon.int, ChannelsRoles.ChatType.int, ChannelsRoles.Color.int, ChannelsRoles.HasMentions.int, ChannelsRoles.Muted.int]) + self.dataChanged(index, index, @[ChannelsRoles.UnreadMessages.int]) + + proc clearAllMentionsFromChannelWithId*(self: ChannelsList, channelId: string) = + let idx = self.chats.findIndexById(channelId) + if idx == -1: + return + + let index = self.createIndex(idx, 0, nil) + self.chats[idx].mentionsCount = 0 + + self.dataChanged(index, index, @[ChannelsRoles.MentionsCount.int]) + + proc clearAllMentionsFromAllChannels*(self: ChannelsList) = + for c in self.chats: + self.clearAllMentionsFromChannelWithId(c.id) + + proc decrementMentions*(self: ChannelsList, channelId: string) = + let idx = self.chats.findIndexById(channelId) + if idx == -1: + return + + let index = self.createIndex(idx, 0, nil) + self.chats[idx].mentionsCount.dec + + self.dataChanged(index, index, @[ChannelsRoles.MentionsCount.int]) + + proc incrementMentions*(self: ChannelsList, channelId: string) : bool = + result = false + let idx = self.chats.findIndexById(channelId) + if idx == -1: + return + + let index = self.createIndex(idx, 0, nil) + self.chats[idx].mentionsCount.inc + result = true + + self.dataChanged(index, index, @[ChannelsRoles.MentionsCount.int]) proc renderInline(self: ChannelsList, elem: TextItem): string = case elem.textType: diff --git a/src/app/chat/views/chat_item.nim b/src/app/chat/views/chat_item.nim index a7a4eb3b26..e1cfe772c9 100644 --- a/src/app/chat/views/chat_item.nim +++ b/src/app/chat/views/chat_item.nim @@ -138,10 +138,10 @@ QtObject: read = isTimelineChat - proc hasMentions*(self: ChatItemView): bool {.slot.} = result = ?.self.chatItem.hasMentions + proc mentionsCount*(self: ChatItemView): int {.slot.} = result = ?.self.chatItem.mentionsCount - QtProperty[bool] hasMentions: - read = hasMentions + QtProperty[int] mentionsCount: + read = mentionsCount proc canPost*(self: ChatItemView): bool {.slot.} = result = ?.self.chatItem.canPost diff --git a/src/app/chat/views/communities.nim b/src/app/chat/views/communities.nim index 963fea24bd..2d7d93debb 100644 --- a/src/app/chat/views/communities.nim +++ b/src/app/chat/views/communities.nim @@ -1,6 +1,7 @@ import NimQml, json, sequtils, chronicles, strutils, strformat import ../../../status/status import ../../../status/chat/chat +import ./channels_list import ./community_list import ./community_item import ./community_membership_request_list @@ -57,6 +58,7 @@ QtObject: # canPost and categoryId are not available in the newChat so we need to check what we had before newChat.canPost = community.chats[i].canPost newChat.categoryId = community.chats[i].categoryId + newChat.mentionsCount = community.chats[i].mentionsCount community.chats[i] = newChat found = true i = i + 1 @@ -428,13 +430,66 @@ QtObject: if community.muted: chat.muted = true return chat - - proc setCommunityMuted*(self: CommunitiesView, communityId: string, muted: bool) {.slot.} = - self.status.chat.setCommunityMuted(communityId, muted) - if (communityId == self.activeCommunity.communityItem.id): - self.activeCommunity.setMuted(muted) - var community = self.joinedCommunityList.getCommunityById(communityId) - community.muted = muted - self.joinedCommunityList.replaceCommunity(community) - \ No newline at end of file + proc markNotificationsAsRead*(self: CommunitiesView, markAsReadProps: MarkAsReadNotificationProperties) = + if(markAsReadProps.communityId.len == 0 and markAsReadProps.channelId.len == 0): + # Remove all notifications from all communities and their channels for set types. + + for t in markAsReadProps.notificationTypes: + case t: + of ActivityCenterNotificationType.NewOneToOne: + debug "Clear all one to one notifications" + of ActivityCenterNotificationType.NewPrivateGroupChat: + debug "Clear all private group chat notifications" + of ActivityCenterNotificationType.Mention: + self.activeCommunity.chats.clearAllMentionsFromAllChannels() + + for c in self.joinedCommunityList.communities: + # We don't need to update channels from the currently active community. + let clearChannels = c.id != self.activeCommunity.communityItem.id + self.joinedCommunityList.clearAllMentions(c.id, clearChannels) + + of ActivityCenterNotificationType.Reply: + debug "Clear all reply notifications" + else: + debug "Unknown notifications" + + else: + # Remove single notification from the channel (channelId) of community (communityId) for set types. + for t in markAsReadProps.notificationTypes: + case t: + of ActivityCenterNotificationType.NewOneToOne: + debug "Clear one to one notification" + of ActivityCenterNotificationType.NewPrivateGroupChat: + debug "Clear private group chat notification" + of ActivityCenterNotificationType.Mention: + if (markAsReadProps.communityId == self.activeCommunity.communityItem.id): + self.activeCommunity.chats.decrementMentions(markAsReadProps.channelId) + else: + for c in self.joinedCommunityList.communities: + # We don't need to update channels from the currently active community. + if (c.id != self.activeCommunity.communityItem.id): + self.joinedCommunityList.decrementMentions(c.id, markAsReadProps.channelId) + + of ActivityCenterNotificationType.Reply: + debug "Clear reply notification" + else: + debug "Unknown notification" + + proc updateNotifications*(self: CommunitiesView, notifications: seq[ActivityCenterNotification]) = + for n in notifications: + if (not n.read): + case n.notificationType: + of ActivityCenterNotificationType.NewOneToOne: + debug "Update one to one notification" + of ActivityCenterNotificationType.NewPrivateGroupChat: + debug "Update private group chat notification" + of ActivityCenterNotificationType.Mention: + let incremented = self.activeCommunity.chats.incrementMentions(n.chatId) + if (not incremented): + self.joinedCommunityList.incrementMentions(n.chatId) + + of ActivityCenterNotificationType.Reply: + debug "Update reply notification" + else: + debug "Unknown notification" diff --git a/src/app/chat/views/community_list.nim b/src/app/chat/views/community_list.nim index b025e14d51..2927185051 100644 --- a/src/app/chat/views/community_list.nim +++ b/src/app/chat/views/community_list.nim @@ -208,3 +208,48 @@ QtObject: community.categories.delete(idx) let index = self.communities.findIndexById(communityId) self.communities[index] = community + + proc clearUnreadMessages*(self: CommunityList, communityId: string, clearFromChannels : bool) = + let idx = self.communities.findIndexById(communityId) + if (idx == -1): + return + + if (clearFromChannels): + # Clear unread messages for each channel in community. + for c in self.communities[idx].chats: + c.unviewedMessagesCount = 0 + + let index = self.createIndex(idx, 0, nil) + self.communities[idx].unviewedMessagesCount = 0 + + self.dataChanged(index, index, @[CommunityRoles.UnviewedMessagesCount.int]) + + proc clearAllMentions*(self: CommunityList, communityId: string, clearFromChannels : bool) = + let idx = self.communities.findIndexById(communityId) + if (idx == -1): + return + + if (clearFromChannels): + # Clear mentions for each chat in community. No need to emit dataChanged + # as mentins are not exposed to qml using roles from this model. + for c in self.communities[idx].chats: + c.mentionsCount = 0 + + # If we decide in one moment to expose mention role we should do that here. + + proc decrementMentions*(self: CommunityList, communityId: string, channelId : string) = + let comIndex = self.communities.findIndexById(communityId) + if (comIndex == -1): + return + + let chatIndex = self.communities[comIndex].chats.findIndexById(channelId) + if (chatIndex == -1): + return + + self.communities[comIndex].chats[chatIndex].mentionsCount.dec + + proc incrementMentions*(self: CommunityList, channelId : string) = + for c in self.communities: + let chatIndex = c.chats.findIndexById(channelId) + if (chatIndex != -1): + c.chats[chatIndex].mentionsCount.inc \ No newline at end of file diff --git a/src/status/chat.nim b/src/status/chat.nim index af3aeb28a5..889c44c675 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -67,6 +67,11 @@ type id*: string channel*: string + MarkAsReadNotificationProperties* = ref object of Args + communityId*: string + channelId*: string + notificationTypes*: seq[ActivityCenterNotificationType] + include chat/utils proc newChatModel*(events: EventEmitter): ChatModel = @@ -534,13 +539,25 @@ proc markAllActivityCenterNotificationsRead*(self: ChatModel): string = except Exception as e: error "Error marking all as read", msg = e.msg result = e.msg + + # This proc should accept ActivityCenterNotificationType in order to clear all notifications + # per type, that's why we have this part here. If we add all types to notificationsType that + # means that we need to clear all notifications for all types. + var types : seq[ActivityCenterNotificationType] + for t in ActivityCenterNotificationType: + types.add(t) -proc markActivityCenterNotificationsRead*(self: ChatModel, ids: seq[string]): string = + self.events.emit("markNotificationsAsRead", MarkAsReadNotificationProperties(notificationTypes: types)) + +proc markActivityCenterNotificationRead*(self: ChatModel, notificationId: string, +markAsReadProps: MarkAsReadNotificationProperties): string = try: - status_chat.markActivityCenterNotificationsRead(ids) + status_chat.markActivityCenterNotificationsRead(@[notificationId]) except Exception as e: error "Error marking as read", msg = e.msg result = e.msg + + self.events.emit("markNotificationsAsRead", markAsReadProps) proc acceptActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string = try: diff --git a/src/status/chat/chat.nim b/src/status/chat/chat.nim index 04355b25bb..7565956742 100644 --- a/src/status/chat/chat.nim +++ b/src/status/chat/chat.nim @@ -83,7 +83,7 @@ type Chat* = ref object lastMessage*: Message members*: seq[ChatMember] membershipUpdateEvents*: seq[ChatMembershipEvent] - hasMentions*: bool + mentionsCount*: int muted*: bool canPost*: bool ensName*: string diff --git a/src/status/signals/messages.nim b/src/status/signals/messages.nim index b577997479..93be040b77 100644 --- a/src/status/signals/messages.nim +++ b/src/status/signals/messages.nim @@ -49,7 +49,7 @@ proc fromEvent*(event: JsonNode): Signal = for jsonChat in event["event"]["chats"]: var chat = jsonChat.toChat if chatsWithMentions.contains(chat.id): - chat.hasMentions = true + chat.mentionsCount.inc signal.chats.add(chat) if event["event"]{"installations"} != nil: @@ -135,7 +135,7 @@ proc newChat*(id: string, chatType: ChatType): Chat = lastClockValue: 0, deletedAtClockValue: 0, unviewedMessagesCount: 0, - hasMentions: false, + mentionsCount: 0, members: @[] ) @@ -165,7 +165,7 @@ proc toChat*(jsonChat: JsonNode): Chat = lastClockValue: jsonChat{"lastClockValue"}.getBiggestInt, deletedAtClockValue: jsonChat{"deletedAtClockValue"}.getBiggestInt, unviewedMessagesCount: jsonChat{"unviewedMessagesCount"}.getInt, - hasMentions: false, + mentionsCount: 0, muted: false, ensName: "", joined: 0, diff --git a/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml b/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml index cd7161cef6..61c2dc2bdb 100644 --- a/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml +++ b/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml @@ -13,7 +13,7 @@ Item { property string timestamp: "1605212622434" property string unviewedMessagesCount: "2" property string identicon - property bool hasMentions: false + property int mentionsCount: 0 property int chatType: Constants.chatTypePublic property int realChatType: { if (chatType === Constants.chatTypeCommunity) { @@ -163,10 +163,10 @@ Item { anchors.bottomMargin: !isCompact ? Style.current.smallPadding : 0 anchors.verticalCenter: !isCompact ? undefined : parent.verticalCenter color: Style.current.blue - visible: (unviewedMessagesCount > 0) || wrapper.hasMentions + visible: (unviewedMessagesCount > 0) || wrapper.mentionsCount > 0 StyledText { id: contactNumberChats - text: wrapper.hasMentions ? '@' : (wrapper.unviewedMessagesCount < 100 ? wrapper.unviewedMessagesCount : "99+") + text: wrapper.mentionsCount > 0 ? '@' : (wrapper.unviewedMessagesCount < 100 ? wrapper.unviewedMessagesCount : "99+") font.pixelSize: 12 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter diff --git a/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml b/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml index e7fcbbd53c..2fa2b34d24 100644 --- a/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml +++ b/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml @@ -37,7 +37,7 @@ Item { chatType: model.chatType identicon: model.identicon unviewedMessagesCount: model.unviewedMessagesCount - hasMentions: model.hasMentions + mentionsCount: model.mentionsCount contentType: model.contentType searchStr: channelListContent.searchStr categoryId: model.categoryId