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