fix(@desktop/chat): making edit work on base_bc

fixes #4410
This commit is contained in:
Khushboo Mehta 2022-01-17 19:46:46 +01:00 committed by Sale Djenic
parent a027ec98f8
commit f68d967544
20 changed files with 279 additions and 77 deletions

View File

@ -109,6 +109,12 @@ method init*(self: Controller) =
return
self.delegate.onMessageDeleted(args.messageId)
self.events.on(SIGNAL_MESSAGE_EDITED) do(e: Args):
let args = MessageEditedArgs(e)
if(self.chatId != args.chatId):
return
self.delegate.onMessageEdited(args.message)
method getMySectionId*(self: Controller): string =
return self.sectionId
@ -158,6 +164,8 @@ method getMessageDetails*(self: Controller, messageId: string):
method deleteMessage*(self: Controller, messageId: string) =
self.messageService.deleteMessage(messageId)
method decodeContentHash*(self: Controller, hash: string): string =
return eth_utils.decodeContentHash(hash)
method editMessage*(self: Controller, messageId: string, updatedMsg: string) =
self.messageService.editMessage(messageId, updatedMsg)

View File

@ -66,3 +66,6 @@ method deleteMessage*(self: AccessInterface, messageId: string) {.base.} =
method decodeContentHash*(self: AccessInterface, hash: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method editMessage*(self: AccessInterface, messageId: string, updatedMsg: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -139,6 +139,9 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
item.pinned = true
item.pinnedBy = p.pinnedBy
if m.editedAt != 0:
item.isEdited = true
# messages are sorted from the most recent to the least recent one
viewItems.add(item)
@ -270,3 +273,11 @@ method deleteMessage*(self: Module, messageId: string) =
method onMessageDeleted*(self: Module, messageId: string) =
self.view.model().removeItem(messageId)
method editMessage*(self: Module, messageId: string, updatedMsg: string) =
self.controller.editMessage(messageId, updatedMsg)
method onMessageEdited*(self: Module, message: MessageDto) =
let renderedMessageText = self.controller.getRenderedText(message.parsedText)
self.view.model().updateEditedMsg(message.id, renderedMessageText, message.containsContactMentions())

View File

@ -27,3 +27,6 @@ method onSendingMessageError*(self: AccessInterface) {.base.} =
method updateContactDetails*(self: AccessInterface, contactId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onMessageEdited*(self: AccessInterface, message: MessageDto) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -27,3 +27,6 @@ method deleteMessage*(self: AccessInterface, messageId: string) {.base.} =
method onMessageDeleted*(self: AccessInterface, messageId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method editMessage*(self: AccessInterface, messageId: string, updatedMsg: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -109,3 +109,13 @@ QtObject:
proc deleteMessage*(self: View, messageId: string) {.slot.} =
self.delegate.deleteMessage(messageId)
proc setEditModeOn*(self: View, messageId: string) {.slot.} =
self.model.setEditModeOn(messageId)
proc setEditModeOff*(self: View, messageId: string) {.slot.} =
self.model.setEditModeOff(messageId)
proc editMessage*(self: View, messageId: string, updatedMsg: string) {.slot.} =
self.delegate.editMessage(messageId, updatedMsg)

View File

@ -29,6 +29,8 @@ type
reactionsModel: MessageReactionModel
pinned: bool
pinnedBy: string
editMode: bool
isEdited: bool
proc initItem*(
id,
@ -71,6 +73,8 @@ proc initItem*(
result.reactionsModel = newMessageReactionModel()
result.sticker = sticker
result.stickerPack = stickerPack
result.editMode = false
result.isEdited = false
proc `$`*(self: Item): string =
result = fmt"""Item(
@ -90,7 +94,9 @@ proc `$`*(self: Item): string =
messageType:{$self.messageType},
pinned:{$self.pinned},
pinnedBy:{$self.pinnedBy},
messageReactions: [{$self.reactionsModel}]
messageReactions: [{$self.reactionsModel}],
editMode:{$self.editMode},
isEdited:{$self.isEdited}
)"""
proc id*(self: Item): string {.inline.} =
@ -216,5 +222,21 @@ proc toJsonNode*(self: Item): JsonNode =
"contentType": self.contentType.int,
"messageType": self.messageType,
"pinned": self.pinned,
"pinnedBy": self.pinnedBy
"pinnedBy": self.pinnedBy,
"editMode": self.editMode,
"isEdited": self.isEdited
}
proc editMode*(self: Item): bool {.inline.} =
self.editMode
proc `editMode=`*(self: Item, value: bool) {.inline.} =
self.editMode = value
proc isEdited*(self: Item): bool {.inline.} =
self.isEdited
proc `isEdited=`*(self: Item, value: bool) {.inline.} =
self.isEdited = value

View File

@ -109,3 +109,11 @@ QtObject:
proc pinned*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.pinned
QtProperty[int] bool:
read = bool
proc editMode*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.editMode
QtProperty[int] bool:
read = bool
proc isEdited*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.isEdited
QtProperty[int] bool:
read = bool

View File

@ -28,6 +28,8 @@ type
Pinned
PinnedBy
Reactions
EditMode
IsEdited
QtObject:
type
@ -86,6 +88,8 @@ QtObject:
ModelRole.Pinned.int:"pinned",
ModelRole.PinnedBy.int:"pinnedBy",
ModelRole.Reactions.int:"reactions",
ModelRole.EditMode.int: "editMode",
ModelRole.IsEdited.int: "isEdited"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -145,6 +149,10 @@ QtObject:
result = newQVariant(item.pinnedBy)
of ModelRole.Reactions:
result = newQVariant(item.reactionsModel)
of ModelRole.EditMode:
result = newQVariant(item.editMode)
of ModelRole.IsEdited:
result = newQVariant(item.isEdited)
proc findIndexForMessageId(self: Model, messageId: string): int =
for i in 0 ..< self.items.len:
@ -295,3 +303,37 @@ QtObject:
if(roles.len > 0):
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, roles)
proc setEditModeOn*(self: Model, messageId: string) =
let ind = self.findIndexForMessageId(messageId)
if(ind == -1):
return
self.items[ind].editMode = true
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.EditMode.int])
proc setEditModeOff*(self: Model, messageId: string) =
let ind = self.findIndexForMessageId(messageId)
if(ind == -1):
return
self.items[ind].editMode = false
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.EditMode.int])
proc updateEditedMsg*(self: Model, messageId: string, updatedMsg: string, messageContainsMentions: bool) =
let ind = self.findIndexForMessageId(messageId)
if(ind == -1):
return
self.items[ind].messageText = updatedMsg
self.items[ind].messageContainsMentions = messageContainsMentions
self.items[ind].isEdited = true
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.MessageText.int, ModelRole.MessageContainsMentions.int, ModelRole.IsEdited.int])

View File

@ -65,6 +65,7 @@ type MessageDto* = object
contentType*: int
messageType*: int
links*: seq[string]
editedAt*: int
proc toParsedTextChild*(jsonObj: JsonNode): ParsedTextChild =
result = ParsedTextChild()
@ -124,6 +125,7 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto =
discard jsonObj.getProp("contentType", result.contentType)
discard jsonObj.getProp("messageType", result.messageType)
discard jsonObj.getProp("image", result.image)
discard jsonObj.getProp("editedAt", result.editedAt)
var quotedMessageObj: JsonNode
if(jsonObj.getProp("quotedMessage", quotedMessageObj)):

View File

@ -9,6 +9,8 @@ import ./dto/message as message_dto
import ./dto/pinned_message as pinned_msg_dto
import ./dto/reaction as reaction_dto
import ../chat/dto/chat as chat_dto
import ./dto/pinned_message_update as pinned_msg_update_dto
import ./dto/removed_message as removed_msg_dto
export message_dto
export pinned_msg_dto
@ -31,6 +33,7 @@ const SIGNAL_MESSAGES_MARKED_AS_READ* = "messagesMarkedAsRead"
const SIGNAL_MESSAGE_REACTION_ADDED* = "messageReactionAdded"
const SIGNAL_MESSAGE_REACTION_REMOVED* = "messageReactionRemoved"
const SIGNAL_MESSAGE_DELETION* = "messageDeleted"
const SIGNAL_MESSAGE_EDITED* = "messageEdited"
include async_tasks
@ -68,6 +71,10 @@ type
chatId*: string
messageId*: string
MessageEditedArgs* = ref object of Args
chatId*: string
message*: MessageDto
QtObject:
type Service* = ref object of QObject
events: EventEmitter
@ -99,60 +106,75 @@ QtObject:
messages.delete(i)
return
proc handleMessagesUpdate(self: Service, chats: var seq[ChatDto], messages: var seq[MessageDto]) =
# We included `chats` in this condition cause that's the form how `status-go` sends updates.
# The first element from the `receivedData.chats` array contains details about the chat a messages received in
# `receivedData.messages` refer to.
let chatId = chats[0].id
let chatType = chats[0].chatType
let unviewedMessagesCount = chats[0].unviewedMessagesCount
let unviewedMentionsCount = chats[0].unviewedMentionsCount
if(chatType == ChatType.Unknown):
error "error: new message with an unknown chat type received for chat id ", chatId
return
# Handling edited message update
if(messages.len == 1 and messages[0].editedAt > 0):
# this is an update for an edited message
let data = MessageEditedArgs(chatId: chatId, message: messages[0])
self.events.emit(SIGNAL_MESSAGE_EDITED, data)
return
# In case of reply to a message we're receiving 2 messages in the `messages` array (replied message
# and a message one replied to) but we actually need only a new replied message, that's why we need to filter
# messages here.
# We are not sure if we can receive more replies here, also ordering in the `messages` array is not
# the same (once we may have replied messages before once after the messages one replied to), that's why we are
# covering the most general case here.
var messagesOneRepliedTo: seq[string]
for m in messages:
if m.responseTo.len > 0:
messagesOneRepliedTo.add(m.responseTo)
for msgId in messagesOneRepliedTo:
removeMessageWithId(messages, msgId)
let data = MessagesArgs(chatId: chatId,
chatType: chatType,
unviewedMessagesCount: unviewedMessagesCount,
unviewedMentionsCount: unviewedMentionsCount,
messages: messages
)
self.events.emit(SIGNAL_NEW_MESSAGE_RECEIVED, data)
proc handlePinnedMessagesUpdate(self: Service, pinnedMessages: var seq[PinnedMessageUpdateDto]) =
for pm in pinnedMessages:
let data = MessagePinUnpinArgs(chatId: pm.chatId, messageId: pm.messageId, actionInitiatedBy: pm.pinnedBy)
if(pm.pinned):
self.numOfPinnedMessagesPerChat[pm.chatId] = self.numOfPinnedMessagesPerChat[pm.chatId] + 1
self.events.emit(SIGNAL_MESSAGE_PINNED, data)
else:
self.numOfPinnedMessagesPerChat[pm.chatId] = self.numOfPinnedMessagesPerChat[pm.chatId] - 1
self.events.emit(SIGNAL_MESSAGE_UNPINNED, data)
proc handleDeletedMessagesUpdate(self: Service, deletedMessages: var seq[RemovedMessageDto]) =
for dm in deletedMessages:
let data = MessageDeletedArgs(chatId: dm.chatId, messageId: dm.messageId)
self.events.emit(SIGNAL_MESSAGE_DELETION, data)
proc init*(self: Service) =
self.events.on(SignalType.Message.event) do(e: Args):
var receivedData = MessageSignal(e)
# Handling messages updates
if (receivedData.messages.len > 0 and receivedData.chats.len > 0):
# We included `chats` in this condition cause that's the form how `status-go` sends updates.
# The first element from the `receivedData.chats` array contains details about the chat a messages received in
# `receivedData.messages` refer to.
let chatId = receivedData.chats[0].id
let chatType = receivedData.chats[0].chatType
let unviewedMessagesCount = receivedData.chats[0].unviewedMessagesCount
let unviewedMentionsCount = receivedData.chats[0].unviewedMentionsCount
if(chatType == ChatType.Unknown):
error "error: new message with an unknown chat type received for chat id ", chatId
return
# In case of reply to a message we're receiving 2 messages in the `receivedData.messages` array (replied message
# and a message one replied to) but we actually need only a new replied message, that's why we need to filter
# messages here.
# We are not sure if we can receive more replies here, also ordering in the `receivedData.messages` array is not
# the same (once we may have replied messages before once after the messages one replied to), that's why we are
# covering the most general case here.
var messagesOneRepliedTo: seq[string]
for m in receivedData.messages:
if m.responseTo.len > 0:
messagesOneRepliedTo.add(m.responseTo)
for msgId in messagesOneRepliedTo:
removeMessageWithId(receivedData.messages, msgId)
let data = MessagesArgs(chatId: chatId,
chatType: chatType,
unviewedMessagesCount: unviewedMessagesCount,
unviewedMentionsCount: unviewedMentionsCount,
messages: receivedData.messages
)
self.events.emit(SIGNAL_NEW_MESSAGE_RECEIVED, data)
self.handleMessagesUpdate(receivedData.chats, receivedData.messages)
# Handling pinned messages updates
if (receivedData.pinnedMessages.len > 0):
for pm in receivedData.pinnedMessages:
let data = MessagePinUnpinArgs(chatId: pm.chatId, messageId: pm.messageId, actionInitiatedBy: pm.pinnedBy)
if(pm.pinned):
self.numOfPinnedMessagesPerChat[pm.chatId] = self.numOfPinnedMessagesPerChat[pm.chatId] + 1
self.events.emit(SIGNAL_MESSAGE_PINNED, data)
else:
self.numOfPinnedMessagesPerChat[pm.chatId] = self.numOfPinnedMessagesPerChat[pm.chatId] - 1
self.events.emit(SIGNAL_MESSAGE_UNPINNED, data)
self.handlePinnedMessagesUpdate(receivedData.pinnedMessages)
# Handling deleted messages updates
if (receivedData.deletedMessages.len > 0):
for dm in receivedData.deletedMessages:
let data = MessageDeletedArgs(chatId: dm.chatId, messageId: dm.messageId)
self.events.emit(SIGNAL_MESSAGE_DELETION, data)
self.handleDeletedMessagesUpdate(receivedData.deletedMessages)
proc initialMessagesFetched(self: Service, chatId: string): bool =
return self.msgCursor.hasKey(chatId)
@ -580,3 +602,26 @@ proc deleteMessage*(self: Service, messageId: string) =
except Exception as e:
error "error: ", methodName="deleteMessage", errName = e.name, errDesription = e.msg
proc editMessage*(self: Service, messageId: string, updatedMsg: string) =
try:
let response = status_go.editMessage(messageId, updatedMsg)
var messagesArr: JsonNode
var messages: seq[MessageDto]
if(response.result.getProp("messages", messagesArr) and messagesArr.kind == JArray):
messages = map(messagesArr.getElems(), proc(x: JsonNode): MessageDto = x.toMessageDto())
if(messages.len == 0):
error "error: ", methodName="editMessage", errDesription = "messages array is empty"
return
if messages[0].editedAt <= 0:
error "error: ", methodName="editMessage", errDesription = "message is not edited"
return
let data = MessageEditedArgs(chatId: messages[0].chatId, message: messages[0])
self.events.emit(SIGNAL_MESSAGE_EDITED, data)
except Exception as e:
error "error: ", methodName="editMessage", errName = e.name, errDesription = e.msg

View File

@ -158,4 +158,33 @@ QtObject {
return
messageModule.deleteMessage(messageId)
}
function setEditModeOn(messageId) {
if(!messageModule)
return
messageModule.setEditModeOn(messageId)
}
function setEditModeOff(messageId) {
if(!messageModule)
return
messageModule.setEditModeOff(messageId)
}
function editMessage(messageId, updatedMsg) {
if(!messageModule)
return
messageModule.editMessage(messageId, updatedMsg)
}
function interpretMessage(msg) {
if (msg.startsWith("/shrug")) {
return msg.replace("/shrug", "") + " ¯\\\\\\_(ツ)\\_/¯"
}
if (msg.startsWith("/tableflip")) {
return msg.replace("/tableflip", "") + " (╯°□°)╯︵ ┻━┻"
}
return msg
}
}

View File

@ -192,4 +192,8 @@ QtObject {
function generateAlias(pk) {
return globalUtils.generateAlias(pk)
}
function plainText(text) {
return globalUtils.plainText(text)
}
}

View File

@ -339,6 +339,8 @@ ColumnLayout {
onDeleteMessage: {
messageStore.deleteMessage(messageId)
}
onEditClicked: messageStore.setEditModeOn(messageId)
}
ColumnLayout {

View File

@ -338,6 +338,8 @@ Item {
reactionsModel: model.reactions
sticker: model.sticker
stickerPack: model.stickerPack
editModeOn: model.editMode
isEdited: model.isEdited
// This is possible since we have all data loaded before we load qml.
// When we fetch messages to fulfill a gap we have to set them at once.
@ -357,6 +359,11 @@ Item {
}
stickersLoaded: root.stickersLoaded
onVisibleChanged: {
if(!visible && model.editMode)
messageStore.setEditModeOff(model.id)
}
}
}

View File

@ -105,7 +105,7 @@ Rectangle {
Loader {
id: editBtn
active: isText && !isEdit && isCurrentUser && showEdit
active: false
sourceComponent: StatusFlatRoundButton {
id: btn
width: 32
@ -114,7 +114,7 @@ Rectangle {
type: StatusFlatRoundButton.Type.Tertiary
//% "Edit"
tooltip.text: qsTrId("edit")
onClicked: isEdit = true
onClicked: messageStore.setEditModeOn(messageId)
onHoveredChanged: buttonsContainer.hoverChanged(btn.hovered)
}
}

View File

@ -46,6 +46,8 @@ Item {
return false
}
}
property bool editModeOn: false
signal openStickerPackPopup(string stickerPackId)
signal addEmoji(bool isProfileClick, bool isSticker, bool isImage , var image, bool emojiOnly, bool hideEmojiPicker)
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage, var image, bool emojiOnly, bool hideEmojiPicker, bool isReply, bool isRightClickOnImage, string imageSource)
@ -79,7 +81,7 @@ Item {
ChatButtonsPanel {
contentType: messageContentType
parentIsHovered: !isEdit && isHovered
parentIsHovered: !editModeOn && isHovered
onHoverChanged: {
hovered && setHovered(messageId, hovered)
}
@ -91,7 +93,7 @@ Item {
messageContextMenu: root.messageContextMenu
showMoreButton: root.showMoreButton
fromAuthor: senderId
editBtnActive: isText && !isEdit && isCurrentUser && showEdit
editBtnActive: isText && !editModeOn && root.isCurrentUser
activityCenterMsg: activityCenterMessage
placeholderMsg: placeholderMessage
onClickMessage: {
@ -168,11 +170,11 @@ Item {
+ (emojiReactionLoader.active ? emojiReactionLoader.height: 0)
+ (retry.visible && !chatTime.visible ? Style.current.smallPadding : 0)
+ (pinnedRectangleLoader.active ? Style.current.smallPadding : 0)
+ (isEdit ? 25 : 0)
+ (editModeOn ? 25 : 0)
width: parent.width
color: {
if (isEdit) {
if (editModeOn) {
return Style.current.backgroundHoverLight
}
@ -194,7 +196,7 @@ Item {
Loader {
id: pinnedRectangleLoader
active: !isEdit && pinnedMessage
active: !editModeOn && pinnedMessage
anchors.left: chatName.left
anchors.top: parent.top
anchors.topMargin: active ? Style.current.halfPadding : 0
@ -303,7 +305,7 @@ Item {
UsernameLabel {
id: chatName
visible: !isEdit && isMessage && headerRepeatCondition
visible: !editModeOn && isMessage && headerRepeatCondition
anchors.leftMargin: chatHorizontalPadding
anchors.top: chatImage.top
anchors.left: chatImage.right
@ -317,7 +319,7 @@ Item {
ChatTimePanel {
id: chatTime
visible: !isEdit && headerRepeatCondition
visible: !editModeOn && headerRepeatCondition
anchors.verticalCenter: chatName.verticalCenter
anchors.left: chatName.right
anchors.leftMargin: 4
@ -327,7 +329,7 @@ Item {
Loader {
id: editMessageLoader
active: isEdit
active: editModeOn
anchors.top: chatReply.active ? chatReply.bottom : parent.top
anchors.left: chatImage.right
anchors.leftMargin: chatHorizontalPadding
@ -372,7 +374,7 @@ Item {
index += linkTag.length
}
sourceText = plainText
sourceText = rootStore.plainText(Emoji.deparse(message))
for (let [key, value] of mentionsMap) {
sourceText = sourceText.replace(new RegExp(key, 'g'), value)
}
@ -416,7 +418,7 @@ Item {
//% "Cancel"
text: qsTrId("browsing-cancel")
onClicked: {
isEdit = false
messageStore.setEditModeOff(messageId)
editTextInput.textInput.text = Emoji.parse(message)
ensureMessageFullyVisibleTimer.start()
}
@ -431,13 +433,12 @@ Item {
text: qsTrId("save")
enabled: editTextInput.textInput.text.trim().length > 0
onClicked: {
// Not Refactored Yet
// let msg = rootStore.chatsModelInst.plainText(Emoji.deparse(editTextInput.textInput.text))
// if (msg.length > 0){
// msg = chatInput.interpretMessage(msg)
// isEdit = false
// rootStore.chatsModelInst.messageView.editMessage(messageId, contentType == Constants.messageContentType.editType ? replaces : messageId, msg);
// }
let msg = rootStore.plainText(Emoji.deparse(editTextInput.textInput.text))
if (msg.length > 0){
msg = messageStore.interpretMessage(msg)
messageStore.setEditModeOff(messageId)
messageStore.editMessage(messageId, msg)
}
}
}
}
@ -453,7 +454,7 @@ Item {
anchors.leftMargin: chatImage.imageWidth + Style.current.padding + root.chatHorizontalPadding
anchors.right: parent.right
anchors.rightMargin: root.chatHorizontalPadding
visible: !isEdit
visible: !editModeOn
ChatTextView {
id: chatText
// Not Refactored Yet

View File

@ -61,6 +61,7 @@ StatusPopupMenu {
signal showReplyArea()
signal toggleReaction(string messageId, int emojiId)
signal deleteMessage(string messageId)
signal editClicked(string messageId)
onHeightChanged: {
root.y = setYPosition()
@ -236,7 +237,7 @@ StatusPopupMenu {
//% "Edit message"
text: qsTrId("edit-message")
onTriggered: {
onClickEdit();
editClicked(messageId)
}
icon.name: "edit"
enabled: root.isMyMessage &&

View File

@ -47,6 +47,8 @@ Column {
property bool isHovered: typeof hoveredMessage !== "undefined" && hoveredMessage === messageId
property bool isMessageActive: typeof activeMessage !== "undefined" && activeMessage === messageId
property bool editModeOn: false
function setHovered(messageId, hovered) {
if (hovered) {
hoveredMessage = messageId;
@ -105,10 +107,8 @@ Column {
property bool forceHoverHandler: false // Used to force the HoverHandler to be active (useful for messages in popups)
property int gapFrom: 0
property int gapTo: 0
property bool isEdit: false
property string replaces: ""
property bool isEdited: false
property bool showEdit: true
property bool stickersLoaded: false
//////////////////////////////////////
@ -355,6 +355,7 @@ Column {
isMessageActive: root.isMessageActive
isCurrentUser: root.amISender
isHovered: root.isHovered
editModeOn: root.editModeOn
onAddEmoji: {
root.clickMessage(isProfileClick, isSticker, isImage , image, emojiOnly, hideEmojiPicker)