fix(messages): fix Resend btn not working and add Sending visual state

Fixes #7643

This adds the backend to resend. It then hooks the button to it.
This also adds a visual state for when we are sending. This gives a good indication that a message was sent.
This commit is contained in:
Jonathan Rainville 2022-12-08 16:01:08 -05:00
parent 59a05243af
commit 194e3048bc
17 changed files with 126 additions and 28 deletions

View File

@ -97,7 +97,8 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch
message.mentionedUsersPks, message.mentionedUsersPks,
contactDetails.details.trustStatus, contactDetails.details.trustStatus,
contactDetails.details.ensVerified, contactDetails.details.ensVerified,
message.discordMessage message.discordMessage,
resendError = ""
)) ))
method convertToItems*( method convertToItems*(

View File

@ -289,3 +289,6 @@ proc leaveChat*(self: Controller) =
method checkEditedMessageForMentions*(self: Controller, chatId: string, method checkEditedMessageForMentions*(self: Controller, chatId: string,
editedMessage: MessageDto, oldMentions: seq[string]) = editedMessage: MessageDto, oldMentions: seq[string]) =
self.messageService.checkEditedMessageForMentions(chatId, editedMessage, oldMentions) self.messageService.checkEditedMessageForMentions(chatId, editedMessage, oldMentions)
method resendChatMessage*(self: Controller, messageId: string): string =
return self.messageService.resendChatMessage(messageId)

View File

@ -148,3 +148,6 @@ method getMessageById*(self: AccessInterface, messageId: string): message_item.I
method onMailserverSynced*(self: AccessInterface, syncedFrom: int64) = method onMailserverSynced*(self: AccessInterface, syncedFrom: int64) =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method resendChatMessage*(self: AccessInterface, messageId: string): string =
raise newException(ValueError, "No implementation available")

View File

@ -99,7 +99,8 @@ proc createFetchMoreMessagesItem(self: Module): Item =
mentionedUsersPks = @[], mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown, senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false, senderEnsVerified = false,
DiscordMessage() DiscordMessage(),
resendError = ""
) )
proc createChatIdentifierItem(self: Module): Item = proc createChatIdentifierItem(self: Module): Item =
@ -140,7 +141,8 @@ proc createChatIdentifierItem(self: Module): Item =
mentionedUsersPks = @[], mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown, senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false, senderEnsVerified = false,
DiscordMessage() DiscordMessage(),
resendError = ""
) )
proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool = proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool =
@ -220,7 +222,8 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
m.mentionedUsersPks(), m.mentionedUsersPks(),
sender.details.trustStatus, sender.details.trustStatus,
sender.details.ensVerified, sender.details.ensVerified,
m.discordMessage m.discordMessage,
resendError = ""
) )
for r in reactions: for r in reactions:
@ -319,7 +322,8 @@ method messageAdded*(self: Module, message: MessageDto) =
message.mentionedUsersPks, message.mentionedUsersPks,
sender.details.trustStatus, sender.details.trustStatus,
sender.details.ensVerified, sender.details.ensVerified,
message.discordMessage message.discordMessage,
resendError = ""
) )
self.view.model().insertItemBasedOnClock(item) self.view.model().insertItemBasedOnClock(item)
@ -593,7 +597,8 @@ method getMessageById*(self: Module, messageId: string): message_item.Item =
m.mentionedUsersPks(), m.mentionedUsersPks(),
sender.details.trustStatus, sender.details.trustStatus,
sender.details.ensVerified, sender.details.ensVerified,
m.discordMessage m.discordMessage,
resendError = ""
) )
return item return item
return nil return nil
@ -602,3 +607,6 @@ method onMailserverSynced*(self: Module, syncedFrom: int64) =
let chatDto = self.controller.getChatDetails() let chatDto = self.controller.getChatDetails()
if (not chatDto.hasMoreMessagesToRequest(syncedFrom)): if (not chatDto.hasMoreMessagesToRequest(syncedFrom)):
self.view.model().removeItem(FETCH_MORE_MESSAGES_MESSAGE_ID) self.view.model().removeItem(FETCH_MORE_MESSAGES_MESSAGE_ID)
method resendChatMessage*(self: Module, messageId: string): string =
return self.controller.resendChatMessage(messageId)

View File

@ -183,3 +183,11 @@ QtObject:
proc jumpToMessage*(self: View, messageId: string) {.slot.} = proc jumpToMessage*(self: View, messageId: string) {.slot.} =
self.delegate.scrollToMessage(messageId) 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)

View File

@ -197,7 +197,8 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy:
m.mentionedUsersPks, m.mentionedUsersPks,
contactDetails.details.trustStatus, contactDetails.details.trustStatus,
contactDetails.details.ensVerified, contactDetails.details.ensVerified,
m.discordMessage m.discordMessage,
resendError = ""
) )
item.pinned = true item.pinned = true
item.pinnedBy = actionInitiatedBy item.pinnedBy = actionInitiatedBy

View File

@ -42,6 +42,7 @@ type
senderTrustStatus: TrustStatus senderTrustStatus: TrustStatus
senderEnsVerified: bool senderEnsVerified: bool
messageAttachments: seq[string] messageAttachments: seq[string]
resendError: string
proc initItem*( proc initItem*(
id, id,
@ -70,7 +71,8 @@ proc initItem*(
mentionedUsersPks: seq[string], mentionedUsersPks: seq[string],
senderTrustStatus: TrustStatus, senderTrustStatus: TrustStatus,
senderEnsVerified: bool, senderEnsVerified: bool,
discordMessage: DiscordMessage discordMessage: DiscordMessage,
resendError: string
): Item = ): Item =
result = Item() result = Item()
result.id = id result.id = id
@ -106,6 +108,7 @@ proc initItem*(
result.senderTrustStatus = senderTrustStatus result.senderTrustStatus = senderTrustStatus
result.senderEnsVerified = senderEnsVerified result.senderEnsVerified = senderEnsVerified
result.messageAttachments = @[] result.messageAttachments = @[]
result.resendError = resendError
if ContentType.DiscordMessage == contentType: if ContentType.DiscordMessage == contentType:
if result.messageText == "": if result.messageText == "":
@ -137,6 +140,7 @@ proc `$`*(self: Item): string =
senderIsAdded: {$self.senderIsAdded}, senderIsAdded: {$self.senderIsAdded},
seen: {$self.seen}, seen: {$self.seen},
outgoingStatus:{$self.outgoingStatus}, outgoingStatus:{$self.outgoingStatus},
resendError:{$self.resendError},
messageText:{self.messageText}, messageText:{self.messageText},
messageContainsMentions:{self.messageContainsMentions}, messageContainsMentions:{self.messageContainsMentions},
timestamp:{$self.timestamp}, timestamp:{$self.timestamp},
@ -212,6 +216,12 @@ proc outgoingStatus*(self: Item): string {.inline.} =
proc `outgoingStatus=`*(self: Item, value: string) {.inline.} = proc `outgoingStatus=`*(self: Item, value: string) {.inline.} =
self.outgoingStatus = value 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.} = proc messageText*(self: Item): string {.inline.} =
self.messageText self.messageText
@ -328,7 +338,8 @@ proc toJsonNode*(self: Item): JsonNode =
"isEdited": self.isEdited, "isEdited": self.isEdited,
"links": self.links, "links": self.links,
"mentionedUsersPks": self.mentionedUsersPks, "mentionedUsersPks": self.mentionedUsersPks,
"senderEnsVerified": self.senderEnsVerified "senderEnsVerified": self.senderEnsVerified,
"resendError": self.resendError
} }
proc editMode*(self: Item): bool {.inline.} = proc editMode*(self: Item): bool {.inline.} =

View File

@ -42,6 +42,7 @@ type
SenderTrustStatus SenderTrustStatus
SenderEnsVerified SenderEnsVerified
MessageAttachments MessageAttachments
ResendError
QtObject: QtObject:
type type
@ -98,6 +99,7 @@ QtObject:
ModelRole.SenderIsAdded.int:"senderIsAdded", ModelRole.SenderIsAdded.int:"senderIsAdded",
ModelRole.Seen.int:"seen", ModelRole.Seen.int:"seen",
ModelRole.OutgoingStatus.int:"outgoingStatus", ModelRole.OutgoingStatus.int:"outgoingStatus",
ModelRole.ResendError.int:"resendError",
ModelRole.MessageText.int:"messageText", ModelRole.MessageText.int:"messageText",
ModelRole.MessageImage.int:"messageImage", ModelRole.MessageImage.int:"messageImage",
ModelRole.MessageContainsMentions.int:"messageContainsMentions", ModelRole.MessageContainsMentions.int:"messageContainsMentions",
@ -166,6 +168,8 @@ QtObject:
result = newQVariant(item.seen) result = newQVariant(item.seen)
of ModelRole.OutgoingStatus: of ModelRole.OutgoingStatus:
result = newQVariant(item.outgoingStatus) result = newQVariant(item.outgoingStatus)
of ModelRole.ResendError:
result = newQVariant(item.resendError)
of ModelRole.MessageText: of ModelRole.MessageText:
result = newQVariant(item.messageText) result = newQVariant(item.messageText)
of ModelRole.MessageImage: of ModelRole.MessageImage:
@ -375,6 +379,9 @@ QtObject:
let index = self.createIndex(ind, 0, nil) let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.OutgoingStatus.int]) 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) = proc itemSent*(self: Model, messageId: string) =
self.setOutgoingStatus(messageId, PARSED_TEXT_OUTGOING_STATUS_SENT) self.setOutgoingStatus(messageId, PARSED_TEXT_OUTGOING_STATUS_SENT)
@ -384,6 +391,14 @@ QtObject:
proc itemExpired*(self: Model, messageId: string) = proc itemExpired*(self: Model, messageId: string) =
self.setOutgoingStatus(messageId, PARSED_TEXT_OUTGOING_STATUS_EXPIRED) 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, proc addReaction*(self: Model, messageId: string, emojiId: EmojiId, didIReactWithThisEmoji: bool,
userPublicKey: string, userDisplayName: string, reactionId: string) = userPublicKey: string, userDisplayName: string, reactionId: string) =
let ind = self.findIndexForMessageId(messageId) let ind = self.findIndexForMessageId(messageId)

View File

@ -21,6 +21,7 @@ const PARSED_TEXT_OUTGOING_STATUS_SENDING* = "sending"
const PARSED_TEXT_OUTGOING_STATUS_SENT* = "sent" const PARSED_TEXT_OUTGOING_STATUS_SENT* = "sent"
const PARSED_TEXT_OUTGOING_STATUS_DELIVERED* = "delivered" const PARSED_TEXT_OUTGOING_STATUS_DELIVERED* = "delivered"
const PARSED_TEXT_OUTGOING_STATUS_EXPIRED* = "expired" const PARSED_TEXT_OUTGOING_STATUS_EXPIRED* = "expired"
const PARSED_TEXT_OUTGOING_STATUS_FAILED_RESENDING* = "failedResending"
type ParsedText* = object type ParsedText* = object
`type`*: string `type`*: string

View File

@ -793,3 +793,16 @@ proc checkEditedMessageForMentions*(self: Service, chatId: string, editedMessage
if not oldMentions.contains(myPubKey) and editedMessage.mentionedUsersPks().contains(myPubKey): if not oldMentions.contains(myPubKey) and editedMessage.mentionedUsersPks().contains(myPubKey):
let data = MessageEditedArgs(chatId: chatId, message: editedMessage) let data = MessageEditedArgs(chatId: chatId, message: editedMessage)
self.events.emit(SIGNAL_MENTIONED_IN_EDITED_MESSAGE, data) 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}"

View File

@ -64,3 +64,6 @@ proc deleteMessageAndSend*(messageID: string): RpcResponse[JsonNode] {.raises: [
proc editMessage*(messageId: string, contentType: int, msg: string): RpcResponse[JsonNode] {.raises: [Exception].} = proc editMessage*(messageId: string, contentType: int, msg: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("editMessage".prefix, %* [{"id": messageId, "text": msg, "content-type": contentType}]) result = callPrivateRPC("editMessage".prefix, %* [{"id": messageId, "text": msg, "content-type": contentType}])
proc resendChatMessage*(messageId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("reSendChatMessage".prefix, %* [messageId])

View File

@ -58,6 +58,8 @@ Control {
property bool isPinned: false property bool isPinned: false
property string pinnedBy: "" property string pinnedBy: ""
property bool hasExpired: false property bool hasExpired: false
property bool isSending: false
property string resendError: ""
property double timestamp: 0 property double timestamp: 0
property var reactionsModel: [] property var reactionsModel: []
property bool hasLinks property bool hasLinks
@ -275,7 +277,9 @@ Control {
amISender: root.messageDetails.amISender amISender: root.messageDetails.amISender
messageOriginInfo: root.messageDetails.messageOriginInfo messageOriginInfo: root.messageDetails.messageOriginInfo
resendText: root.resendText 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) onClicked: root.senderNameClicked(sender, mouse)
onResendClicked: root.resendClicked() onResendClicked: root.resendClicked()
visible: root.showHeader && !editMode visible: root.showHeader && !editMode
@ -372,17 +376,6 @@ Control {
onEditCancelled: root.editCancelled() onEditCancelled: root.editCancelled()
onEditCompleted: root.editCompleted(newMsgText) 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 { Loader {
active: root.reactionsModel.count > 0 active: root.reactionsModel.count > 0
visible: active visible: active

View File

@ -22,6 +22,8 @@ Item {
property string tertiaryDetail: sender.id property string tertiaryDetail: sender.id
property string resendText: "" property string resendText: ""
property bool showResendButton: false property bool showResendButton: false
property bool showSendingLoader: false
property string resendError: ""
property bool isContact: sender.isContact property bool isContact: sender.isContact
property int trustIndicator: sender.trustIndicator property int trustIndicator: sender.trustIndicator
property bool amISender: false property bool amISender: false
@ -120,5 +122,19 @@ Item {
onClicked: root.resendClicked() 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
}
} }
} }

View File

@ -210,19 +210,19 @@ QtObject {
function requestMoreMessages() { function requestMoreMessages() {
if(!messageModule) if(!messageModule)
return return
return messageModule.requestMoreMessages(); return messageModule.requestMoreMessages()
} }
function fillGaps(messageId) { function fillGaps(messageId) {
if(!messageModule) if(!messageModule)
return return
return messageModule.fillGaps(messageId); return messageModule.fillGaps(messageId)
} }
function leaveChat() { function leaveChat() {
if(!messageModule) if(!messageModule)
return return
messageModule.leaveChat(); messageModule.leaveChat()
} }
property bool playAnimation: { property bool playAnimation: {
@ -243,4 +243,10 @@ QtObject {
return true return true
} }
function resendMessage(messageId) {
if(!messageModule)
return
messageModule.resendMessage(messageId)
}
} }

View File

@ -293,6 +293,7 @@ Item {
messageImage: model.messageImage messageImage: model.messageImage
messageTimestamp: model.timestamp messageTimestamp: model.timestamp
messageOutgoingStatus: model.outgoingStatus messageOutgoingStatus: model.outgoingStatus
resendError: model.resendError
messageContentType: model.contentType messageContentType: model.contentType
pinnedMessage: model.pinned pinnedMessage: model.pinned
messagePinnedBy: model.pinnedBy messagePinnedBy: model.pinnedBy

View File

@ -52,6 +52,7 @@ Loader {
property string messageImage: "" property string messageImage: ""
property double messageTimestamp: 0 // We use double, because QML's int is too small property double messageTimestamp: 0 // We use double, because QML's int is too small
property string messageOutgoingStatus: "" property string messageOutgoingStatus: ""
property string resendError: ""
property int messageContentType: Constants.messageContentType.messageType property int messageContentType: Constants.messageContentType.messageType
property bool pinnedMessage: false property bool pinnedMessage: false
property string messagePinnedBy: "" property string messagePinnedBy: ""
@ -90,7 +91,7 @@ Loader {
property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0 property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0
property double nextMsgTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.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 property bool hasMention: false
@ -108,7 +109,8 @@ Loader {
property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio
|| messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType || 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 property int statusAgeEpoch: 0
signal imageClicked(var image) signal imageClicked(var image)
@ -468,6 +470,8 @@ Loader {
isPinned: root.pinnedMessage isPinned: root.pinnedMessage
pinnedBy: root.pinnedMessage && !root.isDiscordMessage ? Utils.getContactDetailsAsJson(root.messagePinnedBy, false).displayName : "" pinnedBy: root.pinnedMessage && !root.isDiscordMessage ? Utils.getContactDetailsAsJson(root.messagePinnedBy, false).displayName : ""
hasExpired: root.isExpired hasExpired: root.isExpired
isSending: root.isSending
resendError: root.resendError
reactionsModel: root.reactionsModel reactionsModel: root.reactionsModel
showHeader: root.senderId !== root.authorPrevMsg || showHeader: root.senderId !== root.authorPrevMsg ||
@ -577,6 +581,10 @@ Loader {
root.openStickerPackPopup(root.stickerPack); root.openStickerPackPopup(root.stickerPack);
} }
onResendClicked: {
root.messageStore.resendMessage(root.messageId)
}
mouseArea { mouseArea {
acceptedButtons: root.activityCenterMessage ? Qt.LeftButton : Qt.RightButton acceptedButtons: root.activityCenterMessage ? Qt.LeftButton : Qt.RightButton
enabled: !root.isChatBlocked && enabled: !root.isChatBlocked &&

View File

@ -729,4 +729,11 @@ QtObject {
readonly property QtObject walletSection: QtObject { readonly property QtObject walletSection: QtObject {
readonly property string cancelledMessage: "cancelled" 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"
} }