feat: get and display emoji reactions in chat
This commit is contained in:
parent
60f7a3cbe2
commit
72af6adb69
2
Makefile
2
Makefile
|
@ -243,7 +243,7 @@ $(STATUS_CLIENT_APPIMAGE): nim_status_client $(APPIMAGE_TOOL) nim-status.desktop
|
|||
cp ui/i18n/* tmp/linux/dist/usr/i18n
|
||||
|
||||
echo -e $(BUILD_MSG) "AppImage"
|
||||
linuxdeployqt tmp/linux/dist/nim-status.desktop -no-translations -no-copy-copyright-files -qmldir=ui -qmlimport=$(QTDIR)/qml -bundle-non-qt-libs
|
||||
linuxdeployqt tmp/linux/dist/nim-status.desktop -no-copy-copyright-files -qmldir=ui -qmlimport=$(QTDIR)/qml -bundle-non-qt-libs
|
||||
|
||||
rm tmp/linux/dist/AppRun
|
||||
cp AppRun tmp/linux/dist/.
|
||||
|
|
|
@ -3,6 +3,9 @@ proc handleChatEvents(self: ChatController) =
|
|||
# Display already saved messages
|
||||
self.status.events.on("messagesLoaded") do(e:Args):
|
||||
self.view.pushMessages(MsgsLoadedArgs(e).messages)
|
||||
# Display emoji reactions
|
||||
self.status.events.on("reactionsLoaded") do(e:Args):
|
||||
self.view.pushReactions(ReactionsLoadedArgs(e).reactions)
|
||||
|
||||
self.status.events.on("contactUpdate") do(e: Args):
|
||||
var evArgs = ContactUpdateArgs(e)
|
||||
|
@ -13,6 +16,7 @@ proc handleChatEvents(self: ChatController) =
|
|||
self.view.updateUsernames(evArgs.contacts)
|
||||
self.view.updateChats(evArgs.chats)
|
||||
self.view.pushMessages(evArgs.messages)
|
||||
self.view.pushReactions(evArgs.emojiReactions)
|
||||
|
||||
self.status.events.on("channelUpdate") do(e: Args):
|
||||
var evArgs = ChatUpdateArgs(e)
|
||||
|
@ -26,6 +30,7 @@ proc handleChatEvents(self: ChatController) =
|
|||
var channel = ChannelArgs(e)
|
||||
discard self.view.chats.addChatItemToList(channel.chat)
|
||||
self.status.chat.chatMessages(channel.chat.id)
|
||||
self.status.chat.chatReactions(channel.chat.id)
|
||||
|
||||
self.status.events.on("chatsLoaded") do(e:Args):
|
||||
self.view.calculateUnreadMessages()
|
||||
|
@ -36,6 +41,7 @@ proc handleChatEvents(self: ChatController) =
|
|||
var channel = ChannelArgs(e)
|
||||
discard self.view.chats.addChatItemToList(channel.chat)
|
||||
self.status.chat.chatMessages(channel.chat.id)
|
||||
self.status.chat.chatReactions(channel.chat.id)
|
||||
self.view.setActiveChannel(channel.chat.id)
|
||||
|
||||
self.status.events.on("channelLeft") do(e: Args):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
proc handleMessage(self: ChatController, data: MessageSignal) =
|
||||
self.status.chat.update(data.chats, data.messages)
|
||||
self.status.chat.update(data.chats, data.messages, data.emojiReactions)
|
||||
|
||||
proc handleDiscoverySummary(self: ChatController, data: DiscoverySummarySignal) =
|
||||
## Handle mailserver peers being added and removed
|
||||
|
|
|
@ -253,6 +253,31 @@ QtObject:
|
|||
else:
|
||||
self.newMessagePushed()
|
||||
|
||||
proc pushReactions*(self:ChatsView, reactions: var seq[Reaction]) =
|
||||
let t = reactions.len
|
||||
for reaction in reactions.mitems:
|
||||
let messageList = self.messageList[reaction.chatId]
|
||||
var message = messageList.getMessageById(reaction.messageId)
|
||||
var oldReactions: JsonNode
|
||||
if (message.emojiReactions == "") :
|
||||
oldReactions = %*{}
|
||||
else:
|
||||
oldReactions = parseJson(message.emojiReactions)
|
||||
|
||||
if (oldReactions.hasKey(reaction.id)):
|
||||
if (reaction.retracted):
|
||||
# Remove the reaction
|
||||
oldReactions.delete(reaction.id)
|
||||
messageList.setMessageReactions(reaction.messageId, $oldReactions)
|
||||
continue
|
||||
|
||||
oldReactions[reaction.id] = %* {
|
||||
"from": reaction.fromAccount,
|
||||
"emojiId": reaction.emojiId
|
||||
}
|
||||
messageList.setMessageReactions(reaction.messageId, $oldReactions)
|
||||
|
||||
|
||||
proc updateUsernames*(self:ChatsView, contacts: seq[Profile]) =
|
||||
if contacts.len > 0:
|
||||
# Updating usernames for all the messages list
|
||||
|
@ -297,6 +322,7 @@ QtObject:
|
|||
proc loadMoreMessages*(self: ChatsView) {.slot.} =
|
||||
trace "Loading more messages", chaId = self.activeChannel.id
|
||||
self.status.chat.chatMessages(self.activeChannel.id, false)
|
||||
self.status.chat.chatReactions(self.activeChannel.id, false)
|
||||
self.messagesLoaded();
|
||||
|
||||
proc loadMoreMessagesWithIndex*(self: ChatsView, channelIndex: int) {.slot.} =
|
||||
|
@ -305,6 +331,7 @@ QtObject:
|
|||
if (selectedChannel == nil): return
|
||||
trace "Loading more messages", chaId = selectedChannel.id
|
||||
self.status.chat.chatMessages(selectedChannel.id, false)
|
||||
self.status.chat.chatReactions(selectedChannel.id, false)
|
||||
self.messagesLoaded();
|
||||
|
||||
proc leaveChatByIndex*(self: ChatsView, channelIndex: int) {.slot.} =
|
||||
|
|
|
@ -30,6 +30,7 @@ type
|
|||
Image = UserRole + 19
|
||||
Audio = UserRole + 20
|
||||
AudioDurationMs = UserRole + 21
|
||||
EmojiReactions = UserRole + 22
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -115,6 +116,7 @@ QtObject:
|
|||
of ChatMessageRoles.Image: result = newQVariant(message.image)
|
||||
of ChatMessageRoles.Audio: result = newQVariant(message.audio)
|
||||
of ChatMessageRoles.AudioDurationMs: result = newQVariant(message.audioDurationMs)
|
||||
of ChatMessageRoles.EmojiReactions: result = newQVariant(message.emojiReactions)
|
||||
|
||||
method roleNames(self: ChatMessageList): Table[int, string] =
|
||||
{
|
||||
|
@ -138,7 +140,8 @@ QtObject:
|
|||
ChatMessageRoles.Timeout.int: "timeout",
|
||||
ChatMessageRoles.Image.int: "image",
|
||||
ChatMessageRoles.Audio.int: "audio",
|
||||
ChatMessageRoles.AudioDurationMs.int: "audioDurationMs"
|
||||
ChatMessageRoles.AudioDurationMs.int: "audioDurationMs",
|
||||
ChatMessageRoles.EmojiReactions.int: "emojiReactions"
|
||||
}.toTable
|
||||
|
||||
proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} =
|
||||
|
@ -175,17 +178,29 @@ QtObject:
|
|||
self.messages.add(message)
|
||||
self.endInsertRows()
|
||||
|
||||
proc getMessageById*(self: ChatMessageList, messageId: string): Message =
|
||||
if (not self.messageIndex.hasKey(messageId)): return
|
||||
return self.messages[self.messageIndex[messageId]]
|
||||
|
||||
proc clear*(self: ChatMessageList) =
|
||||
self.beginResetModel()
|
||||
self.messages = @[]
|
||||
self.endResetModel()
|
||||
|
||||
proc setMessageReactions*(self: ChatMessageList, messageId: string, newReactions: string)=
|
||||
let msgIdx = self.messageIndex[messageId]
|
||||
self.messages[msgIdx].emojiReactions = newReactions
|
||||
let topLeft = self.createIndex(msgIdx, 0, nil)
|
||||
let bottomRight = self.createIndex(msgIdx, 0, nil)
|
||||
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.EmojiReactions.int])
|
||||
|
||||
proc markMessageAsSent*(self: ChatMessageList, messageId: string)=
|
||||
let topLeft = self.createIndex(0, 0, nil)
|
||||
let bottomRight = self.createIndex(self.messages.len, 0, nil)
|
||||
for m in self.messages.mitems:
|
||||
if m.id == messageId:
|
||||
m.outgoingStatus = "sent"
|
||||
break
|
||||
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.OutgoingStatus.int])
|
||||
|
||||
proc updateUsernames*(self: ChatMessageList, contacts: seq[Profile]) =
|
||||
|
|
|
@ -62,6 +62,7 @@ QtObject:
|
|||
|
||||
case signalType:
|
||||
of SignalType.Message:
|
||||
echo $jsonSignal
|
||||
signal = messages.fromEvent(jsonSignal)
|
||||
of SignalType.EnvelopeSent:
|
||||
signal = envelopes.fromEvent(jsonSignal)
|
||||
|
|
|
@ -11,6 +11,8 @@ proc toMessage*(jsonMsg: JsonNode): Message
|
|||
|
||||
proc toChat*(jsonChat: JsonNode): Chat
|
||||
|
||||
proc toReaction*(jsonReaction: JsonNode): Reaction
|
||||
|
||||
proc fromEvent*(event: JsonNode): Signal =
|
||||
var signal:MessageSignal = MessageSignal()
|
||||
signal.messages = @[]
|
||||
|
@ -43,6 +45,10 @@ proc fromEvent*(event: JsonNode): Signal =
|
|||
for jsonInstallation in event["event"]["installations"]:
|
||||
signal.installations.add(jsonInstallation.toInstallation)
|
||||
|
||||
if event["event"]{"emojiReactions"} != nil:
|
||||
for jsonReaction in event["event"]["emojiReactions"]:
|
||||
signal.emojiReactions.add(jsonReaction.toReaction)
|
||||
|
||||
result = signal
|
||||
|
||||
proc toChatMember*(jsonMember: JsonNode): ChatMember =
|
||||
|
@ -195,4 +201,12 @@ proc toMessage*(jsonMsg: JsonNode): Message =
|
|||
|
||||
result = message
|
||||
|
||||
|
||||
proc toReaction*(jsonReaction: JsonNode): Reaction =
|
||||
result = Reaction(
|
||||
id: jsonReaction{"id"}.getStr,
|
||||
chatId: jsonReaction{"chatId"}.getStr,
|
||||
fromAccount: jsonReaction{"from"}.getStr,
|
||||
messageId: jsonReaction{"messageId"}.getStr,
|
||||
emojiId: jsonReaction{"emojiId"}.getInt,
|
||||
retracted: jsonReaction{"retracted"}.getBool
|
||||
)
|
||||
|
|
|
@ -32,6 +32,7 @@ type MessageSignal* = ref object of Signal
|
|||
chats*: seq[Chat]
|
||||
contacts*: seq[Profile]
|
||||
installations*: seq[Installation]
|
||||
emojiReactions*: seq[Reaction]
|
||||
|
||||
type Filter* = object
|
||||
chatId*: string
|
||||
|
|
|
@ -17,6 +17,7 @@ type
|
|||
chats*: seq[Chat]
|
||||
messages*: seq[Message]
|
||||
contacts*: seq[Profile]
|
||||
emojiReactions*: seq[Reaction]
|
||||
|
||||
ChatIdArg* = ref object of Args
|
||||
chatId*: string
|
||||
|
@ -33,11 +34,15 @@ type
|
|||
MsgsLoadedArgs* = ref object of Args
|
||||
messages*: seq[Message]
|
||||
|
||||
ReactionsLoadedArgs* = ref object of Args
|
||||
reactions*: seq[Reaction]
|
||||
|
||||
ChatModel* = ref object
|
||||
events*: EventEmitter
|
||||
contacts*: Table[string, Profile]
|
||||
channels*: Table[string, Chat]
|
||||
msgCursor*: Table[string, string]
|
||||
emojiCursor*: Table[string, string]
|
||||
recentStickers*: seq[Sticker]
|
||||
availableStickerPacks*: Table[int, StickerPack]
|
||||
installedStickerPacks*: Table[int, StickerPack]
|
||||
|
@ -55,6 +60,7 @@ proc newChatModel*(events: EventEmitter): ChatModel =
|
|||
result.contacts = initTable[string, Profile]()
|
||||
result.channels = initTable[string, Chat]()
|
||||
result.msgCursor = initTable[string, string]()
|
||||
result.emojiCursor = initTable[string, string]()
|
||||
result.recentStickers = @[]
|
||||
result.availableStickerPacks = initTable[int, StickerPack]()
|
||||
result.installedStickerPacks = initTable[int, StickerPack]()
|
||||
|
@ -64,12 +70,12 @@ proc newChatModel*(events: EventEmitter): ChatModel =
|
|||
proc delete*(self: ChatModel) =
|
||||
discard
|
||||
|
||||
proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message]) =
|
||||
proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction]) =
|
||||
for chat in chats:
|
||||
if chat.isActive:
|
||||
self.channels[chat.id] = chat
|
||||
|
||||
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
|
||||
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[], emojiReactions: emojiReactions))
|
||||
|
||||
proc hasChannel*(self: ChatModel, chatId: string): bool =
|
||||
self.channels.hasKey(chatId)
|
||||
|
@ -256,6 +262,22 @@ proc chatMessages*(self: ChatModel, chatId: string, initialLoad:bool = true) =
|
|||
self.msgCursor[chatId] = messageTuple[0];
|
||||
self.events.emit("messagesLoaded", MsgsLoadedArgs(messages: messageTuple[1]))
|
||||
|
||||
proc chatReactions*(self: ChatModel, chatId: string, initialLoad:bool = true) =
|
||||
try:
|
||||
if not self.emojiCursor.hasKey(chatId):
|
||||
self.emojiCursor[chatId] = "";
|
||||
|
||||
# Messages were already loaded, since cursor will
|
||||
# be nil/empty if there are no more messages
|
||||
if(not initialLoad and self.emojiCursor[chatId] == ""): return
|
||||
|
||||
let reactionTuple = status_chat.getEmojiReactionsByChatId(chatId, self.emojiCursor[chatId])
|
||||
self.emojiCursor[chatId] = reactionTuple[0];
|
||||
self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactionTuple[1]))
|
||||
except Exception as e:
|
||||
error "Error reactions", msg = e.msg
|
||||
|
||||
|
||||
proc markAllChannelMessagesRead*(self: ChatModel, chatId: string): JsonNode =
|
||||
var response = status_chat.markAllRead(chatId)
|
||||
result = parseJson(response)
|
||||
|
|
|
@ -48,6 +48,15 @@ type Message* = object
|
|||
image*: string
|
||||
audio*: string
|
||||
audioDurationMs*: int
|
||||
emojiReactions*: string
|
||||
|
||||
type Reaction* = object
|
||||
id*: string
|
||||
chatId*: string
|
||||
fromAccount*: string
|
||||
messageId*: string
|
||||
emojiId*: int
|
||||
retracted*: bool
|
||||
|
||||
|
||||
proc `$`*(self: Message): string =
|
||||
|
|
|
@ -65,6 +65,24 @@ proc chatMessages*(chatId: string, cursor: string = ""): (string, seq[Message])
|
|||
|
||||
return (rpcResult{"cursor"}.getStr, messages)
|
||||
|
||||
|
||||
proc getEmojiReactionsByChatId*(chatId: string, cursor: string = ""): (string, seq[Reaction]) =
|
||||
var reactions: seq[Reaction] = @[]
|
||||
var cursorVal: JsonNode
|
||||
|
||||
if cursor == "":
|
||||
cursorVal = newJNull()
|
||||
else:
|
||||
cursorVal = newJString(cursor)
|
||||
|
||||
let rpcResult = parseJson(callPrivateRPC("emojiReactionsByChatID".prefix, %* [chatId, cursorVal, 20]))["result"]
|
||||
|
||||
if rpcResult != nil and rpcResult.len != 0:
|
||||
for jsonMsg in rpcResult:
|
||||
reactions.add(jsonMsg.toReaction)
|
||||
|
||||
return (rpcResult{"cursor"}.getStr, reactions)
|
||||
|
||||
# TODO this probably belongs in another file
|
||||
proc generateSymKeyFromPassword*(): string =
|
||||
result = ($parseJson(callPrivateRPC("waku_generateSymKeyFromPassword", %* [
|
||||
|
|
|
@ -229,6 +229,7 @@ ScrollView {
|
|||
authorPrevMsg: msgDelegate.ListView.previousSection
|
||||
profileClick: profilePopup.setPopupData.bind(profilePopup)
|
||||
messageId: model.messageId
|
||||
emojiReactions: model.emojiReactions
|
||||
prevMessageIndex: {
|
||||
// This is used in order to have access to the previous message and determine the timestamp
|
||||
// we can't rely on the index because the sequence of messages is not ordered on the nim side
|
||||
|
|
|
@ -18,6 +18,7 @@ Item {
|
|||
property string outgoingStatus: ""
|
||||
property string responseTo: ""
|
||||
property string messageId: ""
|
||||
property string emojiReactions: ""
|
||||
property int prevMessageIndex: -1
|
||||
property bool timeout: false
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ StyledTextEdit {
|
|||
`white-space: pre-wrap;`+
|
||||
`}`+
|
||||
`a {`+
|
||||
`color: ${isCurrentUser ? Style.current.white : Style.current.textColor};`+
|
||||
`color: ${isCurrentUser && !appSettings.compactMode ? Style.current.white : Style.current.textColor};`+
|
||||
`}`+
|
||||
`a.mention {`+
|
||||
`color: ${isCurrentUser ? Style.current.cyan : Style.current.turquoise};`+
|
||||
|
|
|
@ -151,4 +151,18 @@ Item {
|
|||
audioSource: audio
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: emojiReactionLoader
|
||||
active: emojiReactions !== ""
|
||||
sourceComponent: emojiReactionsComponent
|
||||
anchors.top: chatText.bottom
|
||||
anchors.left: chatText.left
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiReactionsComponent
|
||||
EmojiReactions {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import QtQuick 2.3
|
||||
import "../../../../../shared"
|
||||
import "../../../../../imports"
|
||||
|
||||
Item {
|
||||
property int imageMargin: 4
|
||||
id: root
|
||||
height: 20
|
||||
width: childrenRect.width
|
||||
|
||||
Repeater {
|
||||
id: reactionepeater
|
||||
model: {
|
||||
if (!emojiReactions) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
// group by id
|
||||
var allReactions = Object.values(JSON.parse(emojiReactions))
|
||||
var byEmoji = {}
|
||||
allReactions.forEach(function (reaction) {
|
||||
if (!byEmoji[reaction.emojiId]) {
|
||||
byEmoji[reaction.emojiId] = {
|
||||
emojiId: reaction.emojiId,
|
||||
count: 0,
|
||||
currentUserReacted: false
|
||||
}
|
||||
}
|
||||
byEmoji[reaction.emojiId].count++;
|
||||
if (!byEmoji[reaction.emojiId].currentUserReacted && reaction.fromAuthor === profileModel.profile.pubKey) {
|
||||
byEmoji[reaction.emojiId].currentUserReacted = true
|
||||
}
|
||||
|
||||
})
|
||||
return Object.values(byEmoji)
|
||||
} catch (e) {
|
||||
console.error('Error parsing emoji reactions', e)
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: emojiImage.width + emojiCount.width + (root.imageMargin * 2) + + 8
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.left: (index === 0) ? parent.left: parent.children[index-1].right
|
||||
anchors.leftMargin: (index === 0) ? 0 : root.imageMargin
|
||||
color: modelData.currentUserReacted ? Style.current.blue : Style.current.grey
|
||||
|
||||
|
||||
// Rounded corner to cover one corner
|
||||
Rectangle {
|
||||
color: parent.color
|
||||
width: 8
|
||||
height: 8
|
||||
anchors.top: parent.top
|
||||
anchors.left: !isCurrentUser ? parent.left : undefined
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: !isCurrentUser ? undefined : parent.right
|
||||
anchors.rightMargin: 0
|
||||
radius: 2
|
||||
z: -1
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: emojiImage
|
||||
width: 15
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: {
|
||||
const basePath = "../../../../img/emojiReactions/"
|
||||
switch (modelData.emojiId) {
|
||||
case 1: return basePath + "heart.svg"
|
||||
case 2: return basePath + "thumbsUp.svg"
|
||||
case 3: return basePath + "thumbsDown.svg"
|
||||
case 4: return basePath + "laughing.svg"
|
||||
case 5: return basePath + "sad.svg"
|
||||
case 6: return basePath + "angry.svg"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.imageMargin
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: emojiCount
|
||||
text: modelData.count
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: emojiImage.right
|
||||
anchors.leftMargin: root.imageMargin
|
||||
font.pixelSize: 12
|
||||
color: modelData.currentUserReacted ? Style.current.currentUserTextColor : Style.current.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ Rectangle {
|
|||
verticalPadding: imageChatBox.chatVerticalPadding
|
||||
anchors.top: (index === 0) ? parent.top: parent.children[index-1].bottom
|
||||
anchors.topMargin: verticalPadding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
source: modelData
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,4 +199,19 @@ Item {
|
|||
id: imageComponent
|
||||
ImageMessage {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: emojiReactionLoader
|
||||
active: emojiReactions !== ""
|
||||
sourceComponent: emojiReactionsComponent
|
||||
anchors.left: !isCurrentUser ? chatBox.left : undefined
|
||||
anchors.right: !isCurrentUser ? undefined : chatBox.right
|
||||
anchors.top: chatBox.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiReactionsComponent
|
||||
EmojiReactions {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ DISTFILES += \
|
|||
app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatTime.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml \
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit eadf68325ebb3b775ba463016098abfac883e522
|
||||
Subproject commit 2d0818d873fee570a73867d8ed4bc8e792215cf1
|
Loading…
Reference in New Issue