From 55dd8e3065bf80cb5ba60d0adb00d601bcaa6bf6 Mon Sep 17 00:00:00 2001 From: Mykhailo Prakhov Date: Fri, 12 Apr 2024 18:28:28 +0200 Subject: [PATCH] feat(@desktop/activityCenter): show notification about invite to group chat from non our mutual contact (#14415) --- src/app/boot/app_controller.nim | 2 +- .../main/activity_center/controller.nim | 14 ++++ .../main/activity_center/io_interface.nim | 12 ++++ src/app/modules/main/activity_center/item.nim | 6 ++ .../modules/main/activity_center/model.nim | 64 +++++++++++++------ .../modules/main/activity_center/module.nim | 12 ++++ src/app/modules/main/activity_center/view.nim | 12 ++++ .../activity_center/dto/notification.nim | 1 + .../service/activity_center/service.nim | 53 ++++++++++++++- .../popups/ActivityCenterPopup.qml | 14 ++++ .../stores/ActivityCenterStore.qml | 8 +++ ...NotificationUnknownGroupChatInvitation.qml | 43 +++++++++++++ 12 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 ui/app/mainui/activitycenter/views/ActivityNotificationUnknownGroupChatInvitation.qml diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 04fa921c05..a3f4e25106 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -165,7 +165,6 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = # Services result.generalService = general_service.newService(statusFoundation.events, statusFoundation.threadpool) - result.activityCenterService = activity_center_service.newService(statusFoundation.events, statusFoundation.threadpool) result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool) result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration, result.settingsService, statusFoundation.events) @@ -177,6 +176,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService ) result.chatService = chat_service.newService(statusFoundation.events, statusFoundation.threadpool, result.contactsService) + result.activityCenterService = activity_center_service.newService(statusFoundation.events, statusFoundation.threadpool, result.chatService) result.tokenService = token_service.newService( statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService ) diff --git a/src/app/modules/main/activity_center/controller.nim b/src/app/modules/main/activity_center/controller.nim index 108a616e64..b9069aa766 100644 --- a/src/app/modules/main/activity_center/controller.nim +++ b/src/app/modules/main/activity_center/controller.nim @@ -76,6 +76,14 @@ proc init*(self: Controller) = if (evArgs.notificationIds.len > 0): self.delegate.removeActivityCenterNotifications(evArgs.notificationIds) + self.events.on(activity_center_service.SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_ACCEPTED) do(e: Args): + var evArgs = ActivityCenterNotificationIdArgs(e) + self.delegate.acceptActivityCenterNotificationDone(evArgs.notificationId) + + self.events.on(activity_center_service.SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_DISMISSED) do(e: Args): + var evArgs = ActivityCenterNotificationIdArgs(e) + self.delegate.dismissActivityCenterNotificationDone(evArgs.notificationId) + proc hasMoreToShow*(self: Controller): bool = return self.activityCenterService.hasMoreToShow() @@ -109,6 +117,12 @@ proc markActivityCenterNotificationUnread*(self: Controller,notificationId: stri proc markAsSeenActivityCenterNotifications*(self: Controller) = self.activityCenterService.markAsSeenActivityCenterNotifications() +proc acceptActivityCenterNotification*(self: Controller,notificationId: string) = + self.activityCenterService.acceptActivityCenterNotification(notificationId) + +proc dismissActivityCenterNotification*(self: Controller,notificationId: string) = + self.activityCenterService.dismissActivityCenterNotification(notificationId) + proc replacePubKeysWithDisplayNames*(self: Controller, message: string): string = return self.messageService.replacePubKeysWithDisplayNames(message) diff --git a/src/app/modules/main/activity_center/io_interface.nim b/src/app/modules/main/activity_center/io_interface.nim index 513662a278..5e68251636 100644 --- a/src/app/modules/main/activity_center/io_interface.nim +++ b/src/app/modules/main/activity_center/io_interface.nim @@ -63,6 +63,18 @@ method markActivityCenterNotificationUnread*(self: AccessInterface, notification method markAsSeenActivityCenterNotifications*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method acceptActivityCenterNotification*(self: AccessInterface, notificationId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method dismissActivityCenterNotification*(self: AccessInterface, notificationId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method acceptActivityCenterNotificationDone*(self: AccessInterface, notificationId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method dismissActivityCenterNotificationDone*(self: AccessInterface, notificationId: string) {.base.} = + raise newException(ValueError, "No implementation available") + method addActivityCenterNotifications*(self: AccessInterface, activityCenterNotifications: seq[ActivityCenterNotificationDto]) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/activity_center/item.nim b/src/app/modules/main/activity_center/item.nim index cd5b476305..6c99de1951 100644 --- a/src/app/modules/main/activity_center/item.nim +++ b/src/app/modules/main/activity_center/item.nim @@ -126,9 +126,15 @@ proc `read=`*(self: Item, value: bool) = proc dismissed*(self: Item): bool = return self.dismissed +proc `dismissed=`*(self: Item, value: bool) = + self.dismissed = value + proc accepted*(self: Item): bool = return self.accepted +proc `accepted=`*(self: Item, value: bool) = + self.accepted = value + proc messageItem*(self: Item): MessageItem = return self.messageItem diff --git a/src/app/modules/main/activity_center/model.nim b/src/app/modules/main/activity_center/model.nim index c6ce6c99f7..d0bfb8734a 100644 --- a/src/app/modules/main/activity_center/model.nim +++ b/src/app/modules/main/activity_center/model.nim @@ -112,27 +112,55 @@ QtObject: NotifRoles.TokenData.int: "tokenData" }.toTable + proc findNotificationIndex(self: Model, notificationId: string): int = + if notificationId.len == 0: + return -1 + for i in 0 ..< self.activityCenterNotifications.len: + if self.activityCenterNotifications[i].id == notificationId: + return i + return -1 + proc markActivityCenterNotificationUnread*(self: Model, notificationId: string) = - var i = 0 - for acnViewItem in self.activityCenterNotifications: - if (acnViewItem.id == notificationId): - acnViewItem.read = false - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[NotifRoles.Read.int]) - break - i.inc + let i = self.findNotificationIndex(notificationId) + if i == -1: + return + + self.activityCenterNotifications[i].read = false + let index = self.createIndex(i, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[NotifRoles.Read.int]) proc markActivityCenterNotificationRead*(self: Model, notificationId: string) = - var i = 0 - for acnViewItem in self.activityCenterNotifications: - if (acnViewItem.id == notificationId): - acnViewItem.read = true - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[NotifRoles.Read.int]) - break - i.inc + let i = self.findNotificationIndex(notificationId) + if i == -1: + return + + self.activityCenterNotifications[i].read = true + let index = self.createIndex(i, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[NotifRoles.Read.int]) + + proc activityCenterNotificationAccepted*(self: Model, notificationId: string) = + let i = self.findNotificationIndex(notificationId) + if i == -1: + return + + self.activityCenterNotifications[i].read = true + self.activityCenterNotifications[i].accepted = true + let index = self.createIndex(i, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[NotifRoles.Accepted.int, NotifRoles.Read.int]) + + proc activityCenterNotificationDismissed*(self: Model, notificationId: string) = + let i = self.findNotificationIndex(notificationId) + if i == -1: + return + + self.activityCenterNotifications[i].read = true + self.activityCenterNotifications[i].dismissed = true + let index = self.createIndex(i, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[NotifRoles.Dismissed.int, NotifRoles.Read.int]) proc removeNotifications*(self: Model, ids: seq[string]) = var i = 0 diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index 0f758b8912..c4b861a283 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -244,6 +244,18 @@ method resetActivityCenterNotifications*(self: Module, activityCenterNotificatio method markActivityCenterNotificationUnread*(self: Module, notificationId: string) = self.controller.markActivityCenterNotificationUnread(notificationId) +method acceptActivityCenterNotification*(self: Module, notificationId: string) = + self.controller.acceptActivityCenterNotification(notificationId) + +method dismissActivityCenterNotification*(self: Module, notificationId: string) = + self.controller.dismissActivityCenterNotification(notificationId) + +method acceptActivityCenterNotificationDone*(self: Module, notificationId: string) = + self.view.acceptActivityCenterNotificationDone(notificationId) + +method dismissActivityCenterNotificationDone*(self: Module, notificationId: string) = + self.view.dismissActivityCenterNotificationDone(notificationId) + method markActivityCenterNotificationUnreadDone*(self: Module, notificationIds: seq[string]) = for notificationId in notificationIds: self.view.markActivityCenterNotificationUnreadDone(notificationId) diff --git a/src/app/modules/main/activity_center/view.nim b/src/app/modules/main/activity_center/view.nim index a1adc14e56..3b0efa995b 100644 --- a/src/app/modules/main/activity_center/view.nim +++ b/src/app/modules/main/activity_center/view.nim @@ -90,6 +90,18 @@ QtObject: self.delegate.markAsSeenActivityCenterNotifications() self.hasUnseenActivityCenterNotificationsChanged() + proc acceptActivityCenterNotification(self: View, notificationId: string): void {.slot.} = + self.delegate.acceptActivityCenterNotification(notificationId) + + proc dismissActivityCenterNotification(self: View, notificationId: string): void {.slot.} = + self.delegate.dismissActivityCenterNotification(notificationId) + + proc acceptActivityCenterNotificationDone*(self: View, notificationId: string) = + self.model.activityCenterNotificationAccepted(notificationId) + + proc dismissActivityCenterNotificationDone*(self: View, notificationId: string) = + self.model.activityCenterNotificationDismissed(notificationId) + proc addActivityCenterNotifications*(self: View, activityCenterNotifications: seq[Item]) = self.model.upsertActivityCenterNotifications(activityCenterNotifications) diff --git a/src/app_service/service/activity_center/dto/notification.nim b/src/app_service/service/activity_center/dto/notification.nim index 169cb193fe..456da36032 100644 --- a/src/app_service/service/activity_center/dto/notification.nim +++ b/src/app_service/service/activity_center/dto/notification.nim @@ -100,6 +100,7 @@ proc toActivityCenterNotificationDto*(jsonObj: JsonNode): ActivityCenterNotifica discard jsonObj.getProp("id", result.id) discard jsonObj.getProp("chatId", result.chatId) discard jsonObj.getProp("communityId", result.communityId) + discard jsonObj.getProp("name", result.name) result.membershipStatus = ActivityCenterMembershipStatus.Idle var membershipStatusInt: int diff --git a/src/app_service/service/activity_center/service.nim b/src/app_service/service/activity_center/service.nim index 308aadbec5..14ed6afe19 100644 --- a/src/app_service/service/activity_center/service.nim +++ b/src/app_service/service/activity_center/service.nim @@ -13,6 +13,7 @@ import ./dto/notification import ../../common/activity_center import ../message/service import ../message/dto/seen_unseen_messages +import ../chat/service as chat_service export notification @@ -28,6 +29,9 @@ type ActivityCenterNotificationIdsArgs* = ref object of Args notificationIds*: seq[string] + ActivityCenterNotificationIdArgs* = ref object of Args + notificationId*: string + # Signals which may be emitted by this service: const SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_LOADED* = "activityCenterNotificationsLoaded" const SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_COUNT_MAY_HAVE_CHANGED* = "activityCenterNotificationsCountMayChanged" @@ -35,6 +39,8 @@ const SIGNAL_ACTIVITY_CENTER_MARK_NOTIFICATIONS_AS_READ* = "activityCenterMarkNo const SIGNAL_ACTIVITY_CENTER_MARK_NOTIFICATIONS_AS_UNREAD* = "activityCenterMarkNotificationsAsUnread" const SIGNAL_ACTIVITY_CENTER_MARK_ALL_NOTIFICATIONS_AS_READ* = "activityCenterMarkAllNotificationsAsRead" const SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_REMOVED* = "activityCenterNotificationsRemoved" +const SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_ACCEPTED* = "activityCenterNotificationsAccepted" +const SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_DISMISSED* = "activityCenterNotificationsDismissed" const DEFAULT_LIMIT = 20 @@ -55,6 +61,7 @@ QtObject: cursor*: string activeGroup: ActivityCenterGroup readType: ActivityCenterReadType + chatService: chat_service.Service # Forward declaration proc asyncActivityNotificationLoad*(self: Service) @@ -64,7 +71,8 @@ QtObject: proc newService*( events: EventEmitter, - threadpool: ThreadPool + threadpool: ThreadPool, + chatService: chat_service.Service, ): Service = new(result, delete) result.QObject.setup @@ -73,6 +81,7 @@ QtObject: result.cursor = "" result.activeGroup = ActivityCenterGroup.All result.readType = ActivityCenterReadType.All + result.chatService = chatService proc handleNewNotificationsLoaded(self: Service, activityCenterNotifications: seq[ActivityCenterNotificationDto]) = # For now status-go notify about every notification update regardless active group so we need filter manulay on the desktop side @@ -151,6 +160,7 @@ QtObject: readType: self.readType.int ) ) + let activityCenterNotificationsTuple = parseActivityCenterNotifications(response.result) self.cursor = activityCenterNotificationsTuple[0]; @@ -231,7 +241,10 @@ QtObject: proc markActivityCenterNotificationUnread*(self: Service, notificationId: string) = try: let notificationIds = @[notificationId] - discard backend.markActivityCenterNotificationsUnread(notificationIds) + let response = backend.markActivityCenterNotificationsUnread(notificationIds) + if response.error != nil: + raise newException(RpcException, response.error.message) + self.events.emit(SIGNAL_ACTIVITY_CENTER_MARK_NOTIFICATIONS_AS_UNREAD, ActivityCenterNotificationIdsArgs(notificationIds: notificationIds)) except Exception as e: error "Error marking as unread", msg = e.msg @@ -273,7 +286,10 @@ QtObject: proc deleteActivityCenterNotifications*(self: Service, notificationIds: seq[string]): string = try: - discard backend.deleteActivityCenterNotifications(notificationIds) + let response = backend.deleteActivityCenterNotifications(notificationIds) + if response.error != nil: + raise newException(RpcException, response.error.message) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_REMOVED, ActivityCenterNotificationIdsArgs(notificationIds: notificationIds)) self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_COUNT_MAY_HAVE_CHANGED, Args()) except Exception as e: @@ -285,3 +301,34 @@ QtObject: for acNotification in acNotifications: if acNotification.notificationType == notificationType and acNotification.communityId == communityId: return acNotification + + proc acceptActivityCenterNotification*(self: Service, notificationId: string) = + try: + let notificationIds = @[notificationId] + let response = backend.acceptActivityCenterNotifications(notificationIds) + if response.error != nil: + raise newException(RpcException, response.error.message) + + if response.result.kind != JNull: + if response.result.contains("chats"): + for jsonChat in response.result["chats"]: + let chat = toChatDto(jsonChat) + self.chatService.updateOrAddChat(chat) + self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgs(chats: @[chat])) + + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_ACCEPTED, ActivityCenterNotificationIdArgs(notificationId: notificationId)) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_COUNT_MAY_HAVE_CHANGED, Args()) + except Exception as e: + error "Error accepting activity center notification", msg = e.msg + + proc dismissActivityCenterNotification*(self: Service, notificationId: string) = + try: + let notificationIds = @[notificationId] + let response = backend.dismissActivityCenterNotifications(notificationIds) + if response.error != nil: + raise newException(RpcException, response.error.message) + + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_DISMISSED, ActivityCenterNotificationIdArgs(notificationId: notificationId)) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_COUNT_MAY_HAVE_CHANGED, Args()) + except Exception as e: + error "Error dismissing activity center notification", msg = e.msg \ No newline at end of file diff --git a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml index 5b6dcda2d3..99f91ff9df 100644 --- a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml +++ b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml @@ -144,6 +144,8 @@ Popup { return communityBannedNotificationComponent case ActivityCenterStore.ActivityCenterNotificationType.CommunityUnbanned: return communityUnbannedNotificationComponent + case ActivityCenterStore.ActivityCenterNotificationType.NewPrivateGroupChat: + return groupChatInvitationNotificationComponent default: return null } @@ -352,4 +354,16 @@ Popup { } } } + + Component { + id: groupChatInvitationNotificationComponent + + ActivityNotificationUnknownGroupChatInvitation { + filteredIndex: parent.filteredIndex + notification: parent.notification + store: root.store + activityCenterStore: root.activityCenterStore + onCloseActivityCenter: root.close() + } + } } diff --git a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml index a913c2d12b..4173272f8e 100644 --- a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml +++ b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml @@ -110,4 +110,12 @@ QtObject { function fetchActivityCenterNotifications() { root.activityCenterModuleInst.fetchActivityCenterNotifications() } + + function acceptActivityCenterNotification(notification) { + root.activityCenterModuleInst.acceptActivityCenterNotification(notification.id) + } + + function dismissActivityCenterNotification(notification) { + root.activityCenterModuleInst.dismissActivityCenterNotification(notification.id) + } } diff --git a/ui/app/mainui/activitycenter/views/ActivityNotificationUnknownGroupChatInvitation.qml b/ui/app/mainui/activitycenter/views/ActivityNotificationUnknownGroupChatInvitation.qml new file mode 100644 index 0000000000..a953c26f99 --- /dev/null +++ b/ui/app/mainui/activitycenter/views/ActivityNotificationUnknownGroupChatInvitation.qml @@ -0,0 +1,43 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 + +import shared 1.0 +import shared.panels 1.0 +import utils 1.0 + +import "../controls" +import "../panels" +import "../stores" + +ActivityNotificationMessage { + id: root + + messageDetails.messageText: qsTr("Invitation to an unknown group") + + badgeComponent: ChannelBadge { + property var group: root.store.getChatDetails(notification.chatId) + + chatType: notification.chatType + name: notification.name + asset.isImage: asset.name != "" + asset.name: group.icon + asset.emoji: group.emoji + asset.color: group.color + } + + ctaComponent: MembershipCta { + membershipStatus: if (notification.accepted) + return ActivityCenterStore.ActivityCenterMembershipStatus.Accepted + else if (notification.dismissed) + return ActivityCenterStore.ActivityCenterMembershipStatus.Declined + else + return ActivityCenterStore.ActivityCenterMembershipStatus.Pending + + onAcceptRequestToJoinCommunity: activityCenterStore.acceptActivityCenterNotification(notification) + onDeclineRequestToJoinCommunity: activityCenterStore.dismissActivityCenterNotification(notification) + } +}