feat(Chat): display MutualStateUpdate system messages in 1-to-1 chats (#10847)

* feat(Chat): Display MutualStateUpdate system messages in 1-to-1 chats

* feat(ActivityCenter): Add AC notification when a user get removed by another contact

* fix: crutch fixing segfault on contact removal
This commit is contained in:
Mikhail Rogachev 2023-06-08 16:52:03 +04:00 committed by GitHub
parent 96f4b5cc83
commit 010640acd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 127 additions and 25 deletions

View File

@ -109,7 +109,9 @@ proc blockContact*(self: Controller, publicKey: string) =
self.contactsService.blockContact(publicKey)
proc removeContact*(self: Controller, publicKey: string) =
self.contactsService.removeContact(publicKey)
let response = self.contactsService.removeContact(publicKey)
# TODO: segfault if using SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND
discard self.chatService.processMessageUpdateAfterSend(response)
proc changeContactNickname*(self: Controller, publicKey: string, nickname: string) =
self.contactsService.changeContactNickname(publicKey, nickname)

View File

@ -9,7 +9,7 @@ type
Status = 3
Emoji = 4
Transaction = 5
Group = 6
SystemMessageGroup = 6
Image = 7
Audio = 8
Community = 9
@ -19,6 +19,7 @@ type
ContactIdentityVerification = 13
# Local only
SystemMessagePinnedMessage = 14
SystemMessageMutualStateUpdate = 15
proc toContentType*(value: int): ContentType =
try:

View File

@ -19,6 +19,7 @@ type ActivityCenterNotificationType* {.pure.}= enum
CommunityMembershipRequest = 8,
CommunityKicked = 9,
ContactVerification = 10
ContactRemoved = 11
type ActivityCenterGroup* {.pure.}= enum
All = 0,
@ -140,7 +141,8 @@ proc activityCenterNotificationTypesByGroup*(group: ActivityCenterGroup) : seq[i
ActivityCenterNotificationType.CommunityRequest.int,
ActivityCenterNotificationType.CommunityMembershipRequest.int,
ActivityCenterNotificationType.CommunityKicked.int,
ActivityCenterNotificationType.ContactVerification.int
ActivityCenterNotificationType.ContactVerification.int,
ActivityCenterNotificationType.ContactRemoved.int
]
of ActivityCenterGroup.Mentions:
return @[ActivityCenterNotificationType.Mention.int]
@ -157,7 +159,10 @@ proc activityCenterNotificationTypesByGroup*(group: ActivityCenterGroup) : seq[i
of ActivityCenterGroup.Admin:
return @[ActivityCenterNotificationType.CommunityMembershipRequest.int]
of ActivityCenterGroup.ContactRequests:
return @[ActivityCenterNotificationType.ContactRequest.int]
return @[
ActivityCenterNotificationType.ContactRequest.int,
ActivityCenterNotificationType.ContactRemoved.int
]
of ActivityCenterGroup.IdentityVerification:
return @[ActivityCenterNotificationType.ContactVerification.int]
else:

View File

@ -83,6 +83,9 @@ type
admin*: bool
joined*: bool
RpcResponseArgs* = ref object of Args
response*: RpcResponse[JsonNode]
# Signals which may be emitted by this service:
const SIGNAL_CHANNEL_GROUPS_LOADED* = "channelGroupsLoaded"
@ -104,6 +107,7 @@ const SIGNAL_CHAT_MEMBER_UPDATED* = "chatMemberUpdated"
const SIGNAL_CHAT_SWITCH_TO_OR_CREATE_1_1_CHAT* = "switchToOrCreateOneToOneChat"
const SIGNAL_CHAT_ADDED_OR_UPDATED* = "chatAddedOrUpdated"
const SIGNAL_CHAT_CREATED* = "chatCreated"
const SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND* = "chatRequestUpdateAfterSend"
QtObject:
type Service* = ref object of QObject
@ -133,6 +137,7 @@ QtObject:
proc updateOrAddChat*(self: Service, chat: ChatDto)
proc hydrateChannelGroups*(self: Service, data: JsonNode)
proc updateOrAddChannelGroup*(self: Service, channelGroup: ChannelGroupDto, isCommunityChannelGroup: bool = false)
proc processMessageUpdateAfterSend*(self: Service, response: RpcResponse[JsonNode]): (seq[ChatDto], seq[MessageDto])
proc doConnect(self: Service) =
self.events.on(SignalType.Message.event) do(e: Args):
@ -161,7 +166,11 @@ QtObject:
for community in receivedData.communities:
if community.joined:
self.updateOrAddChannelGroup(community.toChannelGroupDto(), isCommunityChannelGroup = true)
self.events.on(SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND) do(e: Args):
var args = RpcResponseArgs(e)
discard self.processMessageUpdateAfterSend(args.response)
proc getChannelGroups*(self: Service): seq[ChannelGroupDto] =
return toSeq(self.channelGroups.values)

View File

@ -51,6 +51,9 @@ type
publicKey*: string
ok*: bool
RpcResponseArgs* = ref object of Args
response*: RpcResponse[JsonNode]
# Signals which may be emitted by this service:
const SIGNAL_ENS_RESOLVED* = "ensResolved"
const SIGNAL_CONTACT_ADDED* = "contactAdded"
@ -73,6 +76,7 @@ const SIGNAL_CONTACT_VERIFICATION_ACCEPTED* = "contactVerificationRequestAccepte
const SIGNAL_CONTACT_VERIFICATION_ADDED* = "contactVerificationRequestAdded"
const SIGNAL_CONTACT_VERIFICATION_UPDATED* = "contactVerificationRequestUpdated"
const SIGNAL_CONTACT_INFO_REQUEST_FINISHED* = "contactInfoRequestFinished"
const SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND* = "chatRequestUpdateAfterSend"
type
ContactsGroup* {.pure.} = enum
@ -453,8 +457,6 @@ QtObject:
if response.result["contacts"] != nil:
for contactJson in response.result["contacts"]:
self.updateContact(contactJson.toContactsDto())
# NOTE: this response may also contain chats and messages
self.activityCenterService.parseActivityCenterResponse(response)
proc sendContactRequest*(self: Service, publicKey: string, message: string) =
# Prefetch contact to avoid race condition with AC notification
@ -467,6 +469,8 @@ QtObject:
return
self.parseContactsResponse(response)
self.activityCenterService.parseActivityCenterResponse(response)
self.events.emit(SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND, RpcResponseArgs(response: response))
except Exception as e:
error "an error occurred while sending contact request", msg = e.msg
@ -485,6 +489,8 @@ QtObject:
return
self.parseContactsResponse(response)
self.activityCenterService.parseActivityCenterResponse(response)
self.events.emit(SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND, RpcResponseArgs(response: response))
except Exception as e:
error "an error occurred while accepting contact request", msg=e.msg
@ -503,6 +509,8 @@ QtObject:
return
self.parseContactsResponse(response)
self.activityCenterService.parseActivityCenterResponse(response)
self.events.emit(SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND, RpcResponseArgs(response: response))
except Exception as e:
error "an error occurred while dismissing contact request", msg=e.msg
@ -542,13 +550,15 @@ QtObject:
self.parseContactsResponse(response)
self.events.emit(SIGNAL_CONTACT_BLOCKED, ContactArgs(contactId: contact.id))
proc removeContact*(self: Service, publicKey: string) =
proc removeContact*(self: Service, publicKey: string): RpcResponse[JsonNode] =
let response = status_contacts.retractContactRequest(publicKey)
if not response.error.isNil:
error "error removing contact ", msg = response.error.message
return
self.parseContactsResponse(response)
self.activityCenterService.parseActivityCenterResponse(response)
return response
proc ensResolved*(self: Service, jsonObj: string) {.slot.} =
let jsonObj = jsonObj.parseJson()

View File

@ -22,7 +22,8 @@ Control {
Transaction = 6,
Invitation = 7,
DiscordMessage = 8,
SystemMessagePinnedMessage = 14
SystemMessagePinnedMessage = 14,
SystemMessageMutualStateUpdate = 15
}
property list<Item> quickActions
@ -188,7 +189,10 @@ Control {
Loader {
Layout.fillWidth: true
active: isAReply && root.messageDetails.contentType !== StatusMessage.ContentType.SystemMessagePinnedMessage
active: isAReply &&
root.messageDetails.contentType !== StatusMessage.ContentType.SystemMessagePinnedMessage &&
root.messageDetails.contentType !== StatusMessage.ContentType.SystemMessageMutualStateUpdate
visible: active
sourceComponent: StatusMessageReply {
objectName: "StatusMessage_replyDetails"

View File

@ -111,6 +111,8 @@ Popup {
return communityRequestNotificationComponent
case ActivityCenterStore.ActivityCenterNotificationType.CommunityKicked:
return communityKickedNotificationComponent
case ActivityCenterStore.ActivityCenterNotificationType.ContactRemoved:
return contactRemovedComponent
default:
return null
}
@ -206,4 +208,16 @@ Popup {
onCloseActivityCenter: root.close()
}
}
Component {
id: contactRemovedComponent
ActivityNotificationContactRemoved {
filteredIndex: parent.filteredIndex
notification: parent.notification
store: root.store
activityCenterStore: root.activityCenterStore
onCloseActivityCenter: root.close()
}
}
}

View File

@ -28,7 +28,8 @@ QtObject {
CommunityRequest = 7,
CommunityMembershipRequest = 8,
CommunityKicked = 9,
ContactVerification = 10
ContactVerification = 10,
ContactRemoved = 11
}
enum ActivityCenterReadType {

View File

@ -0,0 +1,54 @@
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 StatusQ.Controls 0.1
import shared 1.0
import shared.panels 1.0
import utils 1.0
import "../panels"
import "../popups"
import "../stores"
ActivityNotificationMessage {
id: root
function checkAndUpdateContactDetails(pubKey) {
if (pubKey === root.contactId)
root.updateContactDetails()
}
Connections {
target: root.store.contactsStore.sentContactRequestsModel
function onItemChanged(pubKey) {
root.checkAndUpdateContactDetails(pubKey)
}
}
Connections {
target: root.store.contactsStore.receivedContactRequestsModel
function onItemChanged(pubKey) {
root.checkAndUpdateContactDetails(pubKey)
}
}
messageSubheaderComponent: StatusBaseText {
text: qsTr("Removed you as a contact")
font.italic: true
font.pixelSize: 15
color: Theme.palette.baseColor1
}
ctaComponent: StatusFlatButton {
enabled: root.contactDetails && !root.contactDetails.added && !root.contactDetails.hasAddedUs
size: StatusBaseButton.Size.Small
text: qsTr("Send Contact Request")
onClicked: Global.openContactRequestPopup(root.contactId, null)
}
}

View File

@ -12,7 +12,7 @@ import utils 1.0
ActivityNotificationBase {
id: root
readonly property bool isOutgoingMessage: notification && notification.message.amISender
readonly property bool isOutgoingMessage: notification && notification.message && notification.message.amISender
readonly property string contactId: notification ? isOutgoingMessage ? notification.chatId : notification.author : ""
readonly property string contactName: contactDetails ? ProfileUtils.displayName(contactDetails.localNickname, contactDetails.name,
contactDetails.displayName, contactDetails.alias) : ""
@ -23,7 +23,7 @@ ActivityNotificationBase {
signal messageClicked()
property StatusMessageDetails messageDetails: StatusMessageDetails {
messageText: notification ? notification.message.messageText : ""
messageText: notification && notification.message ? notification.message.messageText : ""
amISender: false
sender.id: contactId
sender.displayName: contactName

View File

@ -1 +0,0 @@
ActivityCenterGroupRequest 1.0 ActivityCenterGroupRequest.qml

View File

@ -111,7 +111,6 @@ Loader {
property bool isEmoji: messageContentType === Constants.messageContentType.emojiType
property bool isImage: messageContentType === Constants.messageContentType.imageType || (isDiscordMessage && messageImage != "")
property bool isAudio: messageContentType === Constants.messageContentType.audioType
property bool isStatusMessage: messageContentType === Constants.messageContentType.systemMessagePrivateGroupType
property bool isSticker: messageContentType === Constants.messageContentType.stickerType
property bool isDiscordMessage: messageContentType === Constants.messageContentType.discordMessageType
property bool isText: messageContentType === Constants.messageContentType.messageType || messageContentType === Constants.messageContentType.contactRequestType || isDiscordMessage
@ -195,8 +194,9 @@ Loader {
return channelIdentifierComponent
case Constants.messageContentType.fetchMoreMessagesButton:
return fetchMoreMessagesButtonComponent
case Constants.messageContentType.systemMessagePrivateGroupType:
return privateGroupHeaderComponent
case Constants.messageContentType.systemMessagePrivateGroupType: // no break
case Constants.messageContentType.systemMessageMutualStateUpdate:
return systemMessageComponent
case Constants.messageContentType.systemMessagePinnedMessage:
return systemMessagePinnedMessageComponent
case Constants.messageContentType.gapType:
@ -271,6 +271,8 @@ Loader {
return StatusMessage.ContentType.DiscordMessage;
case Constants.messageContentType.systemMessagePinnedMessage:
return StatusMessage.ContentType.SystemMessagePinnedMessage;
case Constants.messageContentType.systemMessageMutualStateUpdate:
return StatusMessage.ContentType.SystemMessageMutualStateUpdate;
case Constants.messageContentType.fetchMoreMessagesButton:
case Constants.messageContentType.chatIdentifier:
case Constants.messageContentType.unknownContentType:
@ -345,11 +347,13 @@ Loader {
}
}
// Private group Messages
Component {
id: privateGroupHeaderComponent
id: systemMessageComponent
StyledText {
wrapMode: Text.Wrap
readonly property string systemMessageText: root.messageText.length > 0 ? root.messageText : root.unparsedText
text: {
return `<html>`+
`<head>`+
@ -361,14 +365,13 @@ Loader {
`</style>`+
`</head>`+
`<body>`+
`${messageText}`+
`${systemMessageText}`+
`</body>`+
`</html>`;
}
visible: isStatusMessage
font.pixelSize: 14
color: Style.current.secondaryText
width: parent.width - 120
width: parent.width - 120
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
textFormat: Text.RichText
@ -478,6 +481,7 @@ Loader {
showHeader: root.shouldRepeatHeader || dateGroupLabel.visible || isAReply ||
root.prevMessageContentType === Constants.messageContentType.systemMessagePrivateGroupType ||
root.prevMessageContentType === Constants.messageContentType.systemMessagePinnedMessage ||
root.prevMessageContentType === Constants.messageContentType.systemMessageMutualStateUpdate ||
root.senderId !== root.prevMessageSenderId
isActiveMessage: d.isMessageActive
topPadding: showHeader ? Style.current.halfPadding : 0
@ -970,6 +974,4 @@ Loader {
}
}
}
}

View File

@ -414,6 +414,7 @@ QtObject {
readonly property int contactRequestType: 11
readonly property int discordMessageType: 12
readonly property int systemMessagePinnedMessage: 14
readonly property int systemMessageMutualStateUpdate: 15
}
readonly property QtObject messageModelRoles: QtObject {

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 9e0a12cb32bf8921ba7b5c923f743bb7119fab96
Subproject commit 8589a525a5531cc4f6e7f6d6e04fe99eecae1658