refactor(messages): make message sending async (#15237)

Fixes #12411

Makes message sending async and adds a placeholder "Sending.." message
This commit is contained in:
Jonathan Rainville 2024-06-20 18:46:12 -04:00 committed by GitHub
parent 033bf99974
commit 8ca51c34cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 298 additions and 85 deletions

View File

@ -95,6 +95,18 @@ proc init*(self: Controller) =
self.unfurlingPlanActiveRequest = "" self.unfurlingPlanActiveRequest = ""
self.handleUnfurlingPlan(self.unfurlingPlanActiveRequestUnfurlAfter) self.handleUnfurlingPlan(self.unfurlingPlanActiveRequestUnfurlAfter)
self.events.on(SIGNAL_SENDING_SUCCESS) do(e:Args):
let args = MessageSendingSuccess(e)
if self.chatId != args.chat.id:
return
self.delegate.onSendingMessageSuccess()
self.events.on(SIGNAL_SENDING_FAILED) do(e:Args):
let args = MessageSendingFailure(e)
if self.chatId != args.chatId:
return
self.delegate.onSendingMessageFailure()
proc getChatId*(self: Controller): string = proc getChatId*(self: Controller): string =
return self.chatId return self.chatId
@ -116,9 +128,9 @@ proc sendImages*(self: Controller,
msg: string, msg: string,
replyTo: string, replyTo: string,
preferredUsername: string = "", preferredUsername: string = "",
linkPreviews: seq[LinkPreview]): string = linkPreviews: seq[LinkPreview]) =
self.resetLinkPreviews() self.resetLinkPreviews()
self.chatService.sendImages( self.chatService.asyncSendImages(
self.chatId, self.chatId,
imagePathsAndDataJson, imagePathsAndDataJson,
msg, msg,
@ -134,7 +146,7 @@ proc sendChatMessage*(self: Controller,
preferredUsername: string = "", preferredUsername: string = "",
linkPreviews: seq[LinkPreview]) = linkPreviews: seq[LinkPreview]) =
self.resetLinkPreviews() self.resetLinkPreviews()
self.chatService.sendChatMessage(self.chatId, self.chatService.asyncSendChatMessage(self.chatId,
msg, msg,
replyTo, replyTo,
contentType, contentType,

View File

@ -22,7 +22,7 @@ method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
method sendChatMessage*(self: AccessInterface, msg: string, replyTo: string, contentType: int, linkPreviews: seq[LinkPreview]) {.base.} = method sendChatMessage*(self: AccessInterface, msg: string, replyTo: string, contentType: int, linkPreviews: seq[LinkPreview]) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method sendImages*(self: AccessInterface, imagePathsJson: string, msg: string, replyTo: string, linkPreviews: seq[LinkPreview]): string {.base.} = method sendImages*(self: AccessInterface, imagePathsJson: string, msg: string, replyTo: string, linkPreviews: seq[LinkPreview]) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method requestAddressForTransaction*(self: AccessInterface, fromAddress: string, amount: string, tokenAddress: string) {.base.} = method requestAddressForTransaction*(self: AccessInterface, fromAddress: string, amount: string, tokenAddress: string) {.base.} =
@ -138,3 +138,9 @@ method setUrls*(self: AccessInterface, urls: seq[string]) {.base.} =
method getContactDetails*(self: AccessInterface, contactId: string): ContactDetails {.base.} = method getContactDetails*(self: AccessInterface, contactId: string): ContactDetails {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onSendingMessageSuccess*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onSendingMessageFailure*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -65,7 +65,7 @@ method getModuleAsVariant*(self: Module): QVariant =
proc getChatId*(self: Module): string = proc getChatId*(self: Module): string =
return self.controller.getChatId() return self.controller.getChatId()
method sendImages*(self: Module, imagePathsAndDataJson: string, msg: string, replyTo: string, linkPreviews: seq[LinkPreview]): string = method sendImages*(self: Module, imagePathsAndDataJson: string, msg: string, replyTo: string, linkPreviews: seq[LinkPreview]) =
self.controller.sendImages(imagePathsAndDataJson, msg, replyTo, singletonInstance.userProfile.getPreferredName(), linkPreviews) self.controller.sendImages(imagePathsAndDataJson, msg, replyTo, singletonInstance.userProfile.getPreferredName(), linkPreviews)
method sendChatMessage*( method sendChatMessage*(
@ -136,3 +136,9 @@ method setUrls*(self: Module, urls: seq[string]) =
method getContactDetails*(self: Module, contactId: string): ContactDetails = method getContactDetails*(self: Module, contactId: string): ContactDetails =
return self.controller.getContactDetails(contactId) return self.controller.getContactDetails(contactId)
method onSendingMessageSuccess*(self: Module) =
self.view.emitSendingMessageSuccess()
method onSendingMessageFailure*(self: Module) =
self.view.emitSendingMessageFailure()

View File

@ -15,10 +15,13 @@ QtObject:
linkPreviewModelVariant: QVariant linkPreviewModelVariant: QVariant
urlsModel: urls_model.Model urlsModel: urls_model.Model
urlsModelVariant: QVariant urlsModelVariant: QVariant
sendingInProgress: bool
askToEnableLinkPreview: bool askToEnableLinkPreview: bool
emojiReactionsModel: emoji_reactions_model.Model emojiReactionsModel: emoji_reactions_model.Model
emojiReactionsModelVariant: QVariant emojiReactionsModelVariant: QVariant
proc setSendingInProgress*(self: View, value: bool)
proc delete*(self: View) = proc delete*(self: View) =
self.QObject.delete self.QObject.delete
self.preservedPropertiesVariant.delete self.preservedPropertiesVariant.delete
@ -53,11 +56,13 @@ QtObject:
replyTo: string, replyTo: string,
contentType: int) {.slot.} = contentType: int) {.slot.} =
# FIXME: Update this when `setText` is async. # FIXME: Update this when `setText` is async.
self.setSendingInProgress(true)
self.delegate.setText(msg, false) self.delegate.setText(msg, false)
self.delegate.sendChatMessage(msg, replyTo, contentType, self.linkPreviewModel.getUnfuledLinkPreviews()) self.delegate.sendChatMessage(msg, replyTo, contentType, self.linkPreviewModel.getUnfuledLinkPreviews())
proc sendImages*(self: View, imagePathsAndDataJson: string, msg: string, replyTo: string): string {.slot.} = proc sendImages*(self: View, imagePathsAndDataJson: string, msg: string, replyTo: string) {.slot.} =
# FIXME: Update this when `setText` is async. # FIXME: Update this when `setText` is async.
self.setSendingInProgress(true)
self.delegate.setText(msg, false) self.delegate.setText(msg, false)
self.delegate.sendImages(imagePathsAndDataJson, msg, replyTo, self.linkPreviewModel.getUnfuledLinkPreviews()) self.delegate.sendImages(imagePathsAndDataJson, msg, replyTo, self.linkPreviewModel.getUnfuledLinkPreviews())
@ -154,3 +159,21 @@ QtObject:
QtProperty[QVariant] urlsModel: QtProperty[QVariant] urlsModel:
read = getUrlsModel read = getUrlsModel
notify = urlsModelChanged notify = urlsModelChanged
proc sendingInProgressChanged(self: View) {.signal.}
proc getSendingInProgress*(self: View): bool {.slot.} =
return self.sendingInProgress
QtProperty[bool] sendingInProgress:
read = getSendingInProgress
notify = sendingInProgressChanged
proc setSendingInProgress*(self: View, value: bool) =
self.sendingInProgress = value
self.sendingInProgressChanged()
proc emitSendingMessageSuccess*(self: View) =
self.setSendingInProgress(false)
proc emitSendingMessageFailure*(self: View) =
self.setSendingInProgress(false)

View File

@ -79,10 +79,10 @@ proc init*(self: Controller) =
self.delegate.onSendingMessageSuccess(args.message) self.delegate.onSendingMessageSuccess(args.message)
self.events.on(SIGNAL_SENDING_FAILED) do(e:Args): self.events.on(SIGNAL_SENDING_FAILED) do(e:Args):
let args = ChatArgs(e) let args = MessageSendingFailure(e)
if(self.chatId != args.chatId): if(self.chatId != args.chatId):
return return
self.delegate.onSendingMessageError() self.delegate.onSendingMessageError(args.error)
self.events.on(SIGNAL_ENVELOPE_SENT) do(e:Args): self.events.on(SIGNAL_ENVELOPE_SENT) do(e:Args):
let args = EnvelopeSentArgs(e) let args = EnvelopeSentArgs(e)

View File

@ -60,7 +60,7 @@ method messagesAdded*(self: AccessInterface, messages: seq[MessageDto]) {.base.}
method onSendingMessageSuccess*(self: AccessInterface, message: MessageDto) {.base.} = method onSendingMessageSuccess*(self: AccessInterface, message: MessageDto) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onSendingMessageError*(self: AccessInterface) {.base.} = method onSendingMessageError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onEnvelopeSent*(self: AccessInterface, messagesIds: seq[string]) {.base.} = method onEnvelopeSent*(self: AccessInterface, messagesIds: seq[string]) {.base.} =

View File

@ -433,8 +433,8 @@ method onSendingMessageSuccess*(self: Module, message: MessageDto) =
self.view.emitSendingMessageSuccessSignal() self.view.emitSendingMessageSuccessSignal()
self.removeNewMessagesMarker() self.removeNewMessagesMarker()
method onSendingMessageError*(self: Module) = method onSendingMessageError*(self: Module, error: string) =
self.view.emitSendingMessageErrorSignal() self.view.emitSendingMessageErrorSignal(error)
method onEnvelopeSent*(self: Module, messagesIds: seq[string]) = method onEnvelopeSent*(self: Module, messagesIds: seq[string]) =
for messageId in messagesIds: for messageId in messagesIds:

View File

@ -106,10 +106,10 @@ QtObject:
proc emitSendingMessageSuccessSignal*(self: View) = proc emitSendingMessageSuccessSignal*(self: View) =
self.messageSuccessfullySent() self.messageSuccessfullySent()
proc sendingMessageFailed*(self: View) {.signal.} proc sendingMessageFailed*(self: View, error: string) {.signal.}
proc emitSendingMessageErrorSignal*(self: View) = proc emitSendingMessageErrorSignal*(self: View, error: string) =
self.sendingMessageFailed() self.sendingMessageFailed(error)
proc deleteMessage*(self: View, messageId: string) {.slot.} = proc deleteMessage*(self: View, messageId: string) {.slot.} =
self.delegate.deleteMessage(messageId) self.delegate.deleteMessage(messageId)

View File

@ -151,7 +151,7 @@ proc sendSticker*(
replyTo: string, replyTo: string,
sticker: StickerDto, sticker: StickerDto,
preferredUsername: string) = preferredUsername: string) =
self.stickerService.sendSticker(channelId, replyTo, sticker, preferredUsername) self.stickerService.asyncSendSticker(channelId, replyTo, sticker, preferredUsername)
proc wei2Eth*(self: Controller, price: Stuint[256]): string = proc wei2Eth*(self: Controller, price: Stuint[256]): string =
eth_utils.wei2Eth(price) eth_utils.wei2Eth(price)

View File

@ -62,3 +62,77 @@ proc asyncCheckAllChannelsPermissionsTask(argEncoded: string) {.gcsafe, nimcall.
"communityId": arg.communityId, "communityId": arg.communityId,
"error": e.msg, "error": e.msg,
}) })
type
AsyncSendMessageTaskArg = ref object of QObjectTaskArg
chatId: string
processedMsg: string
replyTo: string
contentType: int
preferredUsername: string
communityId: string
standardLinkPreviews: JsonNode
statusLinkPreviews: JsonNode
const asyncSendMessageTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncSendMessageTaskArg](argEncoded)
try:
let response = status_chat.sendChatMessage(
arg.chatId,
arg.processedMsg,
arg.replyTo,
arg.contentType,
arg.preferredUsername,
arg.standardLinkPreviews,
arg.statusLinkPreviews,
arg.communityId)
arg.finish(%* {
"response": response,
"chatId": arg.chatId,
"error": "",
})
except Exception as e:
arg.finish(%* {
"error": e.msg,
"chatId": arg.chatId,
})
type
AsyncSendImagesTaskArg = ref object of QObjectTaskArg
chatId: string
imagePathsAndDataJson: string
tempDir: string
msg: string
replyTo: string
preferredUsername: string
linkPreviews: JsonNode
const asyncSendImagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncSendImagesTaskArg](argEncoded)
try:
var images = Json.decode(arg.imagePathsAndDataJson, seq[string])
var imagePaths: seq[string] = @[]
for imagePathOrSource in images.mitems:
let imagePath = image_resizer(imagePathOrSource, 2000, arg.tempDir)
if imagePath != "":
imagePaths.add(imagePath)
let response = status_chat.sendImages(arg.chatId, imagePaths, arg.msg, arg.replyTo, arg.preferredUsername,
arg.linkPreviews)
for imagePath in imagePaths:
removeFile(imagePath)
arg.finish(%* {
"response": response,
"chatId": arg.chatId,
"error": "",
})
except Exception as e:
arg.finish(%* {
"error": e.msg,
"chatId": arg.chatId,
})

View File

@ -1,10 +1,10 @@
import NimQml, Tables, json, sequtils, stew/shims/strformat, chronicles, os, strutils, uuids, base64 import NimQml, Tables, json, sequtils, chronicles, os, strutils, uuids, base64
import std/[times, os] import std/[times, os]
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
import ./dto/chat as chat_dto import ./dto/chat as chat_dto
import ../message/dto/message as message_dto import ../message/dto/message as message_dto
import ../message/dto/link_preview import ../message/dto/[link_preview, standard_link_preview, status_link_preview]
import ../activity_center/dto/notification as notification_dto import ../activity_center/dto/notification as notification_dto
import ../community/dto/community as community_dto import ../community/dto/community as community_dto
import ../contacts/service as contact_service import ../contacts/service as contact_service
@ -47,6 +47,10 @@ type
chat*: ChatDto chat*: ChatDto
message*: MessageDto message*: MessageDto
MessageSendingFailure* = ref object of Args
chatId*: string
error*: string
MessageArgs* = ref object of Args MessageArgs* = ref object of Args
id*: string id*: string
channel*: string channel*: string
@ -420,36 +424,46 @@ QtObject:
error "Error deleting channel", chatId, msg = e.msg error "Error deleting channel", chatId, msg = e.msg
return return
proc sendImages*(self: Service, proc asyncSendImages*(self: Service,
chatId: string, chatId: string,
imagePathsAndDataJson: string, imagePathsAndDataJson: string,
msg: string, msg: string,
replyTo: string, replyTo: string,
preferredUsername: string = "", preferredUsername: string = "",
linkPreviews: seq[LinkPreview] = @[]): string = linkPreviews: seq[LinkPreview] = @[]) =
result = ""
let arg = AsyncSendImagesTaskArg(
tptr: asyncSendImagesTask,
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncSendImagesDone",
chatId: chatId,
imagePathsAndDataJson: imagePathsAndDataJson,
tempDir: TMPDIR,
msg: msg,
replyTo: replyTo,
preferredUsername: preferredUsername,
linkPreviews: %linkPreviews,
)
self.threadpool.start(arg)
proc onAsyncSendImagesDone*(self: Service, rpcResponseJson: string) {.slot.} =
let rpcResponseObj = rpcResponseJson.parseJson
try: try:
var images = Json.decode(imagePathsAndDataJson, seq[string])
let base64JPGPrefix = "data:image/jpeg;base64,"
var imagePaths: seq[string] = @[]
for imagePathOrSource in images.mitems: let errorString = rpcResponseObj{"error"}.getStr()
let imagePath = image_resizer(imagePathOrSource, 2000, TMPDIR) if errorString != "":
if imagePath != "": raise newException(CatchableError, errorString)
imagePaths.add(imagePath)
let response = status_chat.sendImages(chatId, imagePaths, msg, replyTo, preferredUsername, linkPreviews) let rpcResponse = Json.decode($rpcResponseObj["response"], RpcResponse[JsonNode])
for imagePath in imagePaths: discard self.processMessengerResponse(rpcResponse)
removeFile(imagePath)
discard self.processMessengerResponse(response)
except Exception as e: except Exception as e:
error "Error sending images", msg = e.msg error "Error sending images", msg = e.msg
result = fmt"Error sending images: {e.msg}" self.events.emit(SIGNAL_SENDING_FAILED, MessageSendingFailure(chatId: rpcResponseObj["chatId"].getStr, error: e.msg))
proc sendChatMessage*( proc asyncSendChatMessage*(self: Service,
self: Service,
chatId: string, chatId: string,
msg: string, msg: string,
replyTo: string, replyTo: string,
@ -461,20 +475,43 @@ QtObject:
let allKnownContacts = self.contactService.getContactsByGroup(ContactsGroup.AllKnownContacts) let allKnownContacts = self.contactService.getContactsByGroup(ContactsGroup.AllKnownContacts)
let processedMsg = message_common.replaceMentionsWithPubKeys(allKnownContacts, msg) let processedMsg = message_common.replaceMentionsWithPubKeys(allKnownContacts, msg)
let response = status_chat.sendChatMessage( let (standardLinkPreviews, statusLinkPreviews) = extractLinkPreviewsLists(linkPreviews)
chatId,
processedMsg,
replyTo,
contentType,
preferredUsername,
linkPreviews,
communityId) # Only send a community ID for the community invites
let (chats, messages) = self.processMessengerResponse(response) let arg = AsyncSendMessageTaskArg(
if chats.len == 0 or messages.len == 0: tptr: asyncSendMessageTask,
self.events.emit(SIGNAL_SENDING_FAILED, ChatArgs(chatId: chatId)) vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncSendMessageDone",
chatId: chatId,
processedMsg: processedMsg,
replyTo: replyTo,
contentType: contentType,
preferredUsername: preferredUsername,
communityId: communityId, # Only send a community ID for the community invites
standardLinkPreviews: %standardLinkPreviews,
statusLinkPreviews: %statusLinkPreviews,
)
self.threadpool.start(arg)
except Exception as e: except Exception as e:
error "Error sending message", msg = e.msg error "Error sending message", msg = e.msg
self.events.emit(SIGNAL_SENDING_FAILED, MessageSendingFailure(chatId: chatId, error: e.msg))
proc onAsyncSendMessageDone*(self: Service, rpcResponseJson: string) {.slot.} =
let rpcResponseObj = rpcResponseJson.parseJson
try:
let errorString = rpcResponseObj{"error"}.getStr()
if errorString != "":
raise newException(CatchableError, errorString)
let rpcResponse = Json.decode($rpcResponseObj["response"], RpcResponse[JsonNode])
let (chats, messages) = self.processMessengerResponse(rpcResponse)
if chats.len == 0 or messages.len == 0:
raise newException(CatchableError, "no chat or message returned")
except Exception as e:
error "Error sending message", msg = e.msg
self.events.emit(SIGNAL_SENDING_FAILED, MessageSendingFailure(chatId: rpcResponseObj["chatId"].getStr, error: e.msg))
proc requestAddressForTransaction*(self: Service, chatId: string, fromAddress: string, amount: string, tokenAddress: string) = proc requestAddressForTransaction*(self: Service, chatId: string, fromAddress: string, amount: string, tokenAddress: string) =
try: try:

View File

@ -93,3 +93,38 @@ proc installStickerPackTask(argEncoded: string) {.gcsafe, nimcall.} =
error "Error installing stickers", message = getCurrentExceptionMsg() error "Error installing stickers", message = getCurrentExceptionMsg()
let tpl: tuple[packId: string, installed: bool] = (arg.packId, installed) let tpl: tuple[packId: string, installed: bool] = (arg.packId, installed)
arg.finish(tpl) arg.finish(tpl)
type
AsyncSendStickerTaskArg = ref object of QObjectTaskArg
chatId: string
replyTo: string
stickerHash: string
stickerPackId: string
preferredUsername: string
const asyncSendStickerTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncSendStickerTaskArg](argEncoded)
try:
let response = status_chat.sendChatMessage(
arg.chatId,
"You can see a nice sticker here!",
arg.replyTo,
ContentType.Sticker.int,
arg.preferredUsername,
standardLinkPreviews = JsonNode(),
statusLinkPreviews = JsonNode(),
communityId = "", # communityId is not necessary when sending a sticker
arg.stickerHash,
arg.stickerPackId,
)
arg.finish(%* {
"response": response,
"chatId": arg.chatId,
"error": "",
})
except Exception as e:
arg.finish(%* {
"error": e.msg,
"chatId": arg.chatId,
})

View File

@ -345,25 +345,39 @@ QtObject:
except RpcException: except RpcException:
error "Error removing installed sticker", message = getCurrentExceptionMsg() error "Error removing installed sticker", message = getCurrentExceptionMsg()
proc sendSticker*( proc asyncSendSticker*(
self: Service, self: Service,
chatId: string, chatId: string,
replyTo: string, replyTo: string,
sticker: StickerDto, sticker: StickerDto,
preferredUsername: string) = preferredUsername: string) =
let response = status_chat.sendChatMessage( let arg = AsyncSendStickerTaskArg(
chatId, tptr: asyncSendStickerTask,
"Update to latest version to see a nice sticker here!", vptr: cast[ByteAddress](self.vptr),
replyTo, slot: "onAsyncSendStickerDone",
ContentType.Sticker.int, chatId: chatId,
preferredUsername, replyTo: replyTo,
linkPreviews = @[], stickerHash: sticker.hash,
communityId = "", # communityId is not necessary when sending a sticker stickerPackId: sticker.packId,
sticker.hash, preferredUsername: preferredUsername,
sticker.packId) )
discard self.chatService.processMessengerResponse(response)
self.threadpool.start(arg)
self.addStickerToRecent(sticker) self.addStickerToRecent(sticker)
proc onAsyncSendStickerDone*(self: Service, rpcResponseJson: string) {.slot.} =
let rpcResponseObj = rpcResponseJson.parseJson
try:
let errorString = rpcResponseObj{"error"}.getStr()
if errorString != "":
raise newException(CatchableError, errorString)
let rpcResponse = Json.decode($rpcResponseObj["response"], RpcResponse[JsonNode])
discard self.chatService.processMessengerResponse(rpcResponse)
except Exception as e:
error "Error sending sticker", msg = e.msg
self.events.emit(SIGNAL_SENDING_FAILED, ChatArgs(chatId: rpcResponseObj["chatId"].getStr))
proc removeRecentStickers*(self: Service, packId: string) = proc removeRecentStickers*(self: Service, packId: string) =
self.recentStickers.keepItIf(it.packId != packId) self.recentStickers.keepItIf(it.packId != packId)

View File

@ -2,9 +2,6 @@ import json, sequtils, sugar, strutils
import core, ../app_service/common/utils import core, ../app_service/common/utils
import response_type import response_type
import interpret/cropped_image import interpret/cropped_image
import ../app_service/service/message/dto/link_preview
import ../app_service/service/message/dto/standard_link_preview
import ../app_service/service/message/dto/status_link_preview
export response_type export response_type
@ -59,12 +56,12 @@ proc sendChatMessage*(
replyTo: string, replyTo: string,
contentType: int, contentType: int,
preferredUsername: string = "", preferredUsername: string = "",
linkPreviews: seq[LinkPreview], standardLinkPreviews: JsonNode,
statusLinkPreviews: JsonNode,
communityId: string = "", communityId: string = "",
stickerHash: string = "", stickerHash: string = "",
stickerPack: string = "0", stickerPack: string = "0",
): RpcResponse[JsonNode] = ): RpcResponse[JsonNode] =
let (standardLinkPreviews, statusLinkPreviews) = extractLinkPreviewsLists(linkPreviews)
result = callPrivateRPC("sendChatMessage".prefix, %* [ result = callPrivateRPC("sendChatMessage".prefix, %* [
{ {
"chatId": chatId, "chatId": chatId,
@ -87,7 +84,7 @@ proc sendImages*(chatId: string,
msg: string, msg: string,
replyTo: string, replyTo: string,
preferredUsername: string, preferredUsername: string,
linkPreviews: seq[LinkPreview], linkPreviews: JsonNode,
): RpcResponse[JsonNode] = ): RpcResponse[JsonNode] =
let imagesJson = %* images.map(image => %* let imagesJson = %* images.map(image => %*
{ {

View File

@ -80,6 +80,8 @@ Item {
id: d id: d
readonly property var activeChatContentModule: d.getChatContentModule(root.activeChatId) readonly property var activeChatContentModule: d.getChatContentModule(root.activeChatId)
property bool sendingInProgress: !!d.activeChatContentModule? d.activeChatContentModule.inputAreaModule.sendingInProgress : false
readonly property var urlsList: { readonly property var urlsList: {
if (!d.activeChatContentModule) { if (!d.activeChatContentModule) {
return return
@ -276,6 +278,7 @@ Item {
&& root.rootStore.sectionDetails.joined && root.rootStore.sectionDetails.joined
&& !root.rootStore.sectionDetails.amIBanned && !root.rootStore.sectionDetails.amIBanned
&& root.rootStore.isUserAllowedToSendMessage && root.rootStore.isUserAllowedToSendMessage
&& !d.sendingInProgress
} }
store: root.rootStore store: root.rootStore
@ -298,6 +301,9 @@ Item {
if (!root.canPost) { if (!root.canPost) {
return qsTr("Sorry, you don't have permissions to post in this channel.") return qsTr("Sorry, you don't have permissions to post in this channel.")
} }
if (d.sendingInProgress) {
return qsTr("Sending...")
}
return root.rootStore.chatInputPlaceHolderText return root.rootStore.chatInputPlaceHolderText
} else { } else {
return ""; return "";

View File

@ -86,7 +86,8 @@ Item {
chatLogView.positionViewAtBeginning() chatLogView.positionViewAtBeginning()
} }
function onSendingMessageFailed() { function onSendingMessageFailed(error) {
sendingMsgFailedPopup.error = error
sendingMsgFailedPopup.open() sendingMsgFailedPopup.open()
} }
@ -377,9 +378,11 @@ Item {
} }
MessageDialog { MessageDialog {
property string error
id: sendingMsgFailedPopup id: sendingMsgFailedPopup
standardButtons: StandardButton.Ok standardButtons: StandardButton.Ok
text: qsTr("Failed to send message.") text: qsTr("Failed to send message.\n" + error)
icon: StandardIcon.Critical icon: StandardIcon.Critical
} }