diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index d1b74013bf..c01501ad16 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -97,7 +97,8 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch message.mentionedUsersPks, contactDetails.details.trustStatus, contactDetails.details.ensVerified, - message.discordMessage + message.discordMessage, + resendError = "" )) method convertToItems*( diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller.nim b/src/app/modules/main/chat_section/chat_content/messages/controller.nim index 6132e81a10..86045202b4 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller.nim @@ -289,3 +289,6 @@ proc leaveChat*(self: Controller) = method checkEditedMessageForMentions*(self: Controller, chatId: string, editedMessage: MessageDto, oldMentions: seq[string]) = self.messageService.checkEditedMessageForMentions(chatId, editedMessage, oldMentions) + +method resendChatMessage*(self: Controller, messageId: string): string = + return self.messageService.resendChatMessage(messageId) diff --git a/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim index ccb9d741d0..c491bd401b 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim @@ -148,3 +148,6 @@ method getMessageById*(self: AccessInterface, messageId: string): message_item.I method onMailserverSynced*(self: AccessInterface, syncedFrom: int64) = raise newException(ValueError, "No implementation available") + +method resendChatMessage*(self: AccessInterface, messageId: string): string = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/chat_content/messages/module.nim b/src/app/modules/main/chat_section/chat_content/messages/module.nim index 4d909630ee..b82c32f4e4 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/module.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/module.nim @@ -99,7 +99,8 @@ proc createFetchMoreMessagesItem(self: Module): Item = mentionedUsersPks = @[], senderTrustStatus = TrustStatus.Unknown, senderEnsVerified = false, - DiscordMessage() + DiscordMessage(), + resendError = "" ) proc createChatIdentifierItem(self: Module): Item = @@ -140,7 +141,8 @@ proc createChatIdentifierItem(self: Module): Item = mentionedUsersPks = @[], senderTrustStatus = TrustStatus.Unknown, senderEnsVerified = false, - DiscordMessage() + DiscordMessage(), + resendError = "" ) proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool = @@ -220,7 +222,8 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se m.mentionedUsersPks(), sender.details.trustStatus, sender.details.ensVerified, - m.discordMessage + m.discordMessage, + resendError = "" ) for r in reactions: @@ -319,7 +322,8 @@ method messageAdded*(self: Module, message: MessageDto) = message.mentionedUsersPks, sender.details.trustStatus, sender.details.ensVerified, - message.discordMessage + message.discordMessage, + resendError = "" ) self.view.model().insertItemBasedOnClock(item) @@ -593,7 +597,8 @@ method getMessageById*(self: Module, messageId: string): message_item.Item = m.mentionedUsersPks(), sender.details.trustStatus, sender.details.ensVerified, - m.discordMessage + m.discordMessage, + resendError = "" ) return item return nil @@ -602,3 +607,6 @@ method onMailserverSynced*(self: Module, syncedFrom: int64) = let chatDto = self.controller.getChatDetails() if (not chatDto.hasMoreMessagesToRequest(syncedFrom)): self.view.model().removeItem(FETCH_MORE_MESSAGES_MESSAGE_ID) + +method resendChatMessage*(self: Module, messageId: string): string = + return self.controller.resendChatMessage(messageId) diff --git a/src/app/modules/main/chat_section/chat_content/messages/view.nim b/src/app/modules/main/chat_section/chat_content/messages/view.nim index 8b517e3a22..731fa8f3b3 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/view.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/view.nim @@ -183,3 +183,11 @@ QtObject: proc jumpToMessage*(self: View, messageId: string) {.slot.} = self.delegate.scrollToMessage(messageId) + + proc resendMessage*(self: View, messageId: string) {.slot.} = + let error = self.delegate.resendChatMessage(messageId) + if (error != ""): + self.model.itemFailedResending(messageId, error) + return + self.model.itemSending(messageId) + diff --git a/src/app/modules/main/chat_section/chat_content/module.nim b/src/app/modules/main/chat_section/chat_content/module.nim index 99be19bff6..d9ff208949 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -197,7 +197,8 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy: m.mentionedUsersPks, contactDetails.details.trustStatus, contactDetails.details.ensVerified, - m.discordMessage + m.discordMessage, + resendError = "" ) item.pinned = true item.pinnedBy = actionInitiatedBy diff --git a/src/app/modules/shared_models/message_item.nim b/src/app/modules/shared_models/message_item.nim index 434ff26d0c..1a50b2c069 100644 --- a/src/app/modules/shared_models/message_item.nim +++ b/src/app/modules/shared_models/message_item.nim @@ -42,6 +42,7 @@ type senderTrustStatus: TrustStatus senderEnsVerified: bool messageAttachments: seq[string] + resendError: string proc initItem*( id, @@ -70,7 +71,8 @@ proc initItem*( mentionedUsersPks: seq[string], senderTrustStatus: TrustStatus, senderEnsVerified: bool, - discordMessage: DiscordMessage + discordMessage: DiscordMessage, + resendError: string ): Item = result = Item() result.id = id @@ -106,6 +108,7 @@ proc initItem*( result.senderTrustStatus = senderTrustStatus result.senderEnsVerified = senderEnsVerified result.messageAttachments = @[] + result.resendError = resendError if ContentType.DiscordMessage == contentType: if result.messageText == "": @@ -137,6 +140,7 @@ proc `$`*(self: Item): string = senderIsAdded: {$self.senderIsAdded}, seen: {$self.seen}, outgoingStatus:{$self.outgoingStatus}, + resendError:{$self.resendError}, messageText:{self.messageText}, messageContainsMentions:{self.messageContainsMentions}, timestamp:{$self.timestamp}, @@ -212,6 +216,12 @@ proc outgoingStatus*(self: Item): string {.inline.} = proc `outgoingStatus=`*(self: Item, value: string) {.inline.} = self.outgoingStatus = value +proc resendError*(self: Item): string {.inline.} = + self.resendError + +proc `resendError=`*(self: Item, value: string) {.inline.} = + self.resendError = value + proc messageText*(self: Item): string {.inline.} = self.messageText @@ -328,7 +338,8 @@ proc toJsonNode*(self: Item): JsonNode = "isEdited": self.isEdited, "links": self.links, "mentionedUsersPks": self.mentionedUsersPks, - "senderEnsVerified": self.senderEnsVerified + "senderEnsVerified": self.senderEnsVerified, + "resendError": self.resendError } proc editMode*(self: Item): bool {.inline.} = diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index d57030935e..31cc6ed1d8 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -42,6 +42,7 @@ type SenderTrustStatus SenderEnsVerified MessageAttachments + ResendError QtObject: type @@ -98,6 +99,7 @@ QtObject: ModelRole.SenderIsAdded.int:"senderIsAdded", ModelRole.Seen.int:"seen", ModelRole.OutgoingStatus.int:"outgoingStatus", + ModelRole.ResendError.int:"resendError", ModelRole.MessageText.int:"messageText", ModelRole.MessageImage.int:"messageImage", ModelRole.MessageContainsMentions.int:"messageContainsMentions", @@ -166,6 +168,8 @@ QtObject: result = newQVariant(item.seen) of ModelRole.OutgoingStatus: result = newQVariant(item.outgoingStatus) + of ModelRole.ResendError: + result = newQVariant(item.resendError) of ModelRole.MessageText: result = newQVariant(item.messageText) of ModelRole.MessageImage: @@ -375,6 +379,9 @@ QtObject: let index = self.createIndex(ind, 0, nil) self.dataChanged(index, index, @[ModelRole.OutgoingStatus.int]) + proc itemSending*(self: Model, messageId: string) = + self.setOutgoingStatus(messageId, PARSED_TEXT_OUTGOING_STATUS_SENDING) + proc itemSent*(self: Model, messageId: string) = self.setOutgoingStatus(messageId, PARSED_TEXT_OUTGOING_STATUS_SENT) @@ -384,6 +391,14 @@ QtObject: proc itemExpired*(self: Model, messageId: string) = self.setOutgoingStatus(messageId, PARSED_TEXT_OUTGOING_STATUS_EXPIRED) + proc itemFailedResending*(self: Model, messageId: string, error: string) = + let ind = self.findIndexForMessageId(messageId) + if(ind == -1): + return + self.items[ind].resendError = error + let index = self.createIndex(ind, 0, nil) + self.dataChanged(index, index, @[ModelRole.ResendError.int]) + proc addReaction*(self: Model, messageId: string, emojiId: EmojiId, didIReactWithThisEmoji: bool, userPublicKey: string, userDisplayName: string, reactionId: string) = let ind = self.findIndexForMessageId(messageId) diff --git a/src/app_service/service/message/dto/message.nim b/src/app_service/service/message/dto/message.nim index 543d3305d4..99d2c6b29a 100644 --- a/src/app_service/service/message/dto/message.nim +++ b/src/app_service/service/message/dto/message.nim @@ -21,6 +21,7 @@ const PARSED_TEXT_OUTGOING_STATUS_SENDING* = "sending" const PARSED_TEXT_OUTGOING_STATUS_SENT* = "sent" const PARSED_TEXT_OUTGOING_STATUS_DELIVERED* = "delivered" const PARSED_TEXT_OUTGOING_STATUS_EXPIRED* = "expired" +const PARSED_TEXT_OUTGOING_STATUS_FAILED_RESENDING* = "failedResending" type ParsedText* = object `type`*: string diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index 10808c750d..64663a35fd 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -793,3 +793,16 @@ proc checkEditedMessageForMentions*(self: Service, chatId: string, editedMessage if not oldMentions.contains(myPubKey) and editedMessage.mentionedUsersPks().contains(myPubKey): let data = MessageEditedArgs(chatId: chatId, message: editedMessage) self.events.emit(SIGNAL_MENTIONED_IN_EDITED_MESSAGE, data) + +proc resendChatMessage*(self: Service, messageId: string): string = + try: + let response = status_go.resendChatMessage(messageId) + + if response.error != nil: + let error = Json.decode($response.error, RpcError) + raise newException(RpcException, "Error resending chat message: " & error.message) + + return + except Exception as e: + error "error: ", procName="resendChatMessage", errName = e.name, errDesription = e.msg + return fmt"{e.name}: {e.msg}" \ No newline at end of file diff --git a/src/backend/messages.nim b/src/backend/messages.nim index 9af4243e5e..9f5d3384d0 100644 --- a/src/backend/messages.nim +++ b/src/backend/messages.nim @@ -64,3 +64,6 @@ proc deleteMessageAndSend*(messageID: string): RpcResponse[JsonNode] {.raises: [ proc editMessage*(messageId: string, contentType: int, msg: string): RpcResponse[JsonNode] {.raises: [Exception].} = result = callPrivateRPC("editMessage".prefix, %* [{"id": messageId, "text": msg, "content-type": contentType}]) + +proc resendChatMessage*(messageId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("reSendChatMessage".prefix, %* [messageId]) diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml index 962916dbc9..8a7f8d7cb9 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml @@ -58,6 +58,8 @@ Control { property bool isPinned: false property string pinnedBy: "" property bool hasExpired: false + property bool isSending: false + property string resendError: "" property double timestamp: 0 property var reactionsModel: [] property bool hasLinks @@ -275,7 +277,9 @@ Control { amISender: root.messageDetails.amISender messageOriginInfo: root.messageDetails.messageOriginInfo resendText: root.resendText - showResendButton: root.hasExpired && root.messageDetails.amISender + showResendButton: root.hasExpired && root.messageDetails.amISender && !editMode + showSendingLoader: root.isSending && root.messageDetails.amISender && !editMode + resendError: root.messageDetails.amISender && !editMode ? root.resendError : "" onClicked: root.senderNameClicked(sender, mouse) onResendClicked: root.resendClicked() visible: root.showHeader && !editMode @@ -372,17 +376,6 @@ Control { onEditCancelled: root.editCancelled() onEditCompleted: root.editCompleted(newMsgText) } - StatusBaseText { - color: Theme.palette.dangerColor1 - text: root.resendText - font.pixelSize: 12 - visible: root.hasExpired && root.messageDetails.amISender && !root.timestamp && !editMode - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: root.resendClicked() - } - } Loader { active: root.reactionsModel.count > 0 visible: active diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessageHeader.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessageHeader.qml index 64aee5a6aa..34f1dd8857 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusMessageHeader.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusMessageHeader.qml @@ -22,6 +22,8 @@ Item { property string tertiaryDetail: sender.id property string resendText: "" property bool showResendButton: false + property bool showSendingLoader: false + property string resendError: "" property bool isContact: sender.isContact property int trustIndicator: sender.trustIndicator property bool amISender: false @@ -120,5 +122,19 @@ Item { onClicked: root.resendClicked() } } + StatusBaseText { + verticalAlignment: Text.AlignVCenter + color: Theme.palette.baseColor1 + font.pixelSize: Theme.tertiaryTextFontSize + text: qsTr("Failed to resend: %1").arg(resendError) // TODO replace this with the required design + visible: resendError && !!timestampText.text + } + StatusBaseText { + verticalAlignment: Text.AlignVCenter + color: Theme.palette.baseColor1 + font.pixelSize: Theme.tertiaryTextFontSize + text: qsTr("Sending...") // TODO replace this with the required design + visible: showSendingLoader && !!timestampText.text + } } } diff --git a/ui/app/AppLayouts/Chat/stores/MessageStore.qml b/ui/app/AppLayouts/Chat/stores/MessageStore.qml index 42bf7910d0..1c29b643e3 100644 --- a/ui/app/AppLayouts/Chat/stores/MessageStore.qml +++ b/ui/app/AppLayouts/Chat/stores/MessageStore.qml @@ -210,19 +210,19 @@ QtObject { function requestMoreMessages() { if(!messageModule) return - return messageModule.requestMoreMessages(); + return messageModule.requestMoreMessages() } function fillGaps(messageId) { - if(!messageModule) + if(!messageModule) return - return messageModule.fillGaps(messageId); + return messageModule.fillGaps(messageId) } function leaveChat() { - if(!messageModule) + if(!messageModule) return - messageModule.leaveChat(); + messageModule.leaveChat() } property bool playAnimation: { @@ -243,4 +243,10 @@ QtObject { return true } + + function resendMessage(messageId) { + if(!messageModule) + return + messageModule.resendMessage(messageId) + } } diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml index 5ca003e324..bf8c117077 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -293,6 +293,7 @@ Item { messageImage: model.messageImage messageTimestamp: model.timestamp messageOutgoingStatus: model.outgoingStatus + resendError: model.resendError messageContentType: model.contentType pinnedMessage: model.pinned messagePinnedBy: model.pinnedBy diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index a39e6a370f..3a2768a70d 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -52,6 +52,7 @@ Loader { property string messageImage: "" property double messageTimestamp: 0 // We use double, because QML's int is too small property string messageOutgoingStatus: "" + property string resendError: "" property int messageContentType: Constants.messageContentType.messageType property bool pinnedMessage: false property string messagePinnedBy: "" @@ -90,7 +91,7 @@ Loader { property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0 property double nextMsgTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.timestamp : 0 - property bool shouldRepeatHeader: ((messageTimestamp - prevMsgTimestamp) / 60 / 1000) > Constants.repeatHeaderInterval + property bool shouldRepeatHeader: ((messageTimestamp - prevMsgTimestamp) / 60 / 1000) > Constants.repeatHeaderInterval || isExpired property bool hasMention: false @@ -108,7 +109,8 @@ Loader { property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio || messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType - readonly property bool isExpired: (messageOutgoingStatus === "sending" && (Math.floor(messageTimestamp) + 180000) < Date.now()) || messageOutgoingStatus === "expired" + readonly property bool isExpired: (messageOutgoingStatus === Constants.sending && (Math.floor(messageTimestamp) + 180000) < Date.now()) || messageOutgoingStatus === Constants.expired + readonly property bool isSending: messageOutgoingStatus === Constants.sending && !isExpired property int statusAgeEpoch: 0 signal imageClicked(var image) @@ -468,6 +470,8 @@ Loader { isPinned: root.pinnedMessage pinnedBy: root.pinnedMessage && !root.isDiscordMessage ? Utils.getContactDetailsAsJson(root.messagePinnedBy, false).displayName : "" hasExpired: root.isExpired + isSending: root.isSending + resendError: root.resendError reactionsModel: root.reactionsModel showHeader: root.senderId !== root.authorPrevMsg || @@ -577,6 +581,10 @@ Loader { root.openStickerPackPopup(root.stickerPack); } + onResendClicked: { + root.messageStore.resendMessage(root.messageId) + } + mouseArea { acceptedButtons: root.activityCenterMessage ? Qt.LeftButton : Qt.RightButton enabled: !root.isChatBlocked && diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index afd70d1ead..80814db2d5 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -729,4 +729,11 @@ QtObject { readonly property QtObject walletSection: QtObject { readonly property string cancelledMessage: "cancelled" } + + // Message outgoing status + readonly property string sending: "sending" + readonly property string sent: "sent" + readonly property string delivered: "delivered" + readonly property string expired: "expired" + readonly property string failedResending: "failedResending" }