feat: support message replies
This commit is contained in:
parent
b22b500d4f
commit
649023bacf
|
@ -30,6 +30,7 @@ QtObject:
|
|||
stickerPacks*: StickerPackList
|
||||
stickers*: Table[int, StickerList]
|
||||
emptyStickerList: StickerList
|
||||
replyTo: string
|
||||
|
||||
proc setup(self: ChatsView) = self.QAbstractListModel.setup
|
||||
|
||||
|
@ -75,6 +76,24 @@ QtObject:
|
|||
proc getChannelColor*(self: ChatsView, channel: string): string {.slot.} =
|
||||
self.chats.getChannelColor(channel)
|
||||
|
||||
proc replyAreaEnabled*(self: ChatsView, enable: bool, userName: string, message: string, identicon: string) {.signal.}
|
||||
|
||||
proc setReplyTo(self: ChatsView, messageId: string) {.slot.} =
|
||||
self.replyTo = messageId
|
||||
|
||||
proc enableReplyArea*(self: ChatsView, enable: bool, userName: string = "", message: string = "", identicon: string = "") {.slot.} =
|
||||
if not enable:
|
||||
self.replyTo = ""
|
||||
self.replyAreaEnabled(enable, username, message, identicon)
|
||||
|
||||
proc disableReplyArea(self: ChatsView) =
|
||||
self.replyTo = ""
|
||||
self.replyAreaEnabled(false, "", "", "")
|
||||
|
||||
proc sendMessage*(self: ChatsView, message: string, isReply: bool) {.slot.} =
|
||||
self.status.chat.sendMessage(self.activeChannel.id, message, if isReply: self.replyTo else: "")
|
||||
self.disableReplyArea()
|
||||
|
||||
proc activeChannelChanged*(self: ChatsView) {.signal.}
|
||||
|
||||
proc setActiveChannelByIndex*(self: ChatsView, index: int) {.slot.} =
|
||||
|
@ -86,6 +105,7 @@ QtObject:
|
|||
if self.activeChannel.id == selectedChannel.id: return
|
||||
self.activeChannel.setChatItem(selectedChannel)
|
||||
self.status.chat.setActiveChannel(selectedChannel.id)
|
||||
self.disableReplyArea()
|
||||
self.activeChannelChanged()
|
||||
|
||||
proc getActiveChannelIdx(self: ChatsView): QVariant {.slot.} =
|
||||
|
@ -115,6 +135,7 @@ QtObject:
|
|||
proc setActiveChannel*(self: ChatsView, channel: string) =
|
||||
if(channel == ""): return
|
||||
self.activeChannel.setChatItem(self.chats.getChannel(self.chats.chats.findIndexById(channel)))
|
||||
self.disableReplyArea()
|
||||
self.activeChannelChanged()
|
||||
|
||||
proc getActiveChannel*(self: ChatsView): QVariant {.slot.} =
|
||||
|
@ -168,9 +189,6 @@ QtObject:
|
|||
discard self.chats.addChatItemToList(chatItem)
|
||||
self.messagePushed()
|
||||
|
||||
proc sendMessage*(self: ChatsView, message: string) {.slot.} =
|
||||
self.status.chat.sendMessage(self.activeChannel.id, message)
|
||||
|
||||
proc addRecentStickerToList*(self: ChatsView, sticker: Sticker) =
|
||||
self.stickers[RECENT_STICKERS].addStickerToList(sticker)
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ QtObject:
|
|||
of ChatMessageRoles.Id: result = newQVariant(message.id)
|
||||
of ChatMessageRoles.OutgoingStatus: result = newQVariant(message.outgoingStatus)
|
||||
of ChatMessageRoles.ResponseTo: result = newQVariant(message.responseTo)
|
||||
of ChatMessageRoles.Text: result = newQVariant(message.text)
|
||||
|
||||
method roleNames(self: ChatMessageList): Table[int, string] =
|
||||
{
|
||||
|
@ -97,7 +98,8 @@ QtObject:
|
|||
ChatMessageRoles.SectionIdentifier.int: "sectionIdentifier",
|
||||
ChatMessageRoles.Id.int: "messageId",
|
||||
ChatMessageRoles.OutgoingStatus.int: "outgoingStatus",
|
||||
ChatMessageRoles.ResponseTo.int: "responseTo"
|
||||
ChatMessageRoles.ResponseTo.int: "responseTo",
|
||||
ChatMessageRoles.Text.int: "text"
|
||||
}.toTable
|
||||
|
||||
proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} =
|
||||
|
|
|
@ -156,11 +156,12 @@ proc clearHistory*(self: ChatModel, chatId: string) =
|
|||
proc setActiveChannel*(self: ChatModel, chatId: string) =
|
||||
self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId))
|
||||
|
||||
proc sendMessage*(self: ChatModel, chatId: string, msg: string) =
|
||||
var response = status_chat.sendChatMessage(chatId, msg)
|
||||
proc sendMessage*(self: ChatModel, chatId: string, msg: string, replyTo: string = "") =
|
||||
var response = status_chat.sendChatMessage(chatId, msg, replyTo)
|
||||
var (chats, messages) = self.processChatUpdate(parseJson(response))
|
||||
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
|
||||
self.events.emit("sendingMessage", MessageArgs(id: messages[0].id, channel: messages[0].chatId))
|
||||
for msg in messages:
|
||||
self.events.emit("sendingMessage", MessageArgs(id: msg.id, channel: msg.chatId))
|
||||
|
||||
proc addStickerToRecent*(self: ChatModel, sticker: Sticker, save: bool = false) =
|
||||
self.recentStickers.insert(sticker, 0)
|
||||
|
|
|
@ -71,12 +71,12 @@ proc generateSymKeyFromPassword*(): string =
|
|||
"status-offline-inbox"
|
||||
]))["result"]).strip(chars = {'"'})
|
||||
|
||||
proc sendChatMessage*(chatId: string, msg: string): string =
|
||||
proc sendChatMessage*(chatId: string, msg: string, replyTo: string): string =
|
||||
callPrivateRPC("sendChatMessage".prefix, %* [
|
||||
{
|
||||
"chatId": chatId,
|
||||
"text": msg,
|
||||
"responseTo": nil,
|
||||
"responseTo": replyTo,
|
||||
"ensName": nil,
|
||||
"sticker": nil,
|
||||
"contentType": ContentType.Message.int
|
||||
|
|
|
@ -8,6 +8,7 @@ import "./ChatColumn"
|
|||
|
||||
StackLayout {
|
||||
property int chatGroupsListViewCount: 0
|
||||
property bool isReply: false
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 300
|
||||
|
@ -15,14 +16,14 @@ StackLayout {
|
|||
currentIndex: chatsModel.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1
|
||||
|
||||
ColumnLayout {
|
||||
id: chatColumn
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id: chatTopBar
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
z: 60
|
||||
|
||||
spacing: 0
|
||||
TopBar {}
|
||||
}
|
||||
|
||||
|
@ -31,8 +32,9 @@ StackLayout {
|
|||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
|
||||
spacing: 0
|
||||
ChatMessages {
|
||||
id: chatMessages
|
||||
messageList: chatsModel.messageList
|
||||
}
|
||||
}
|
||||
|
@ -42,21 +44,52 @@ StackLayout {
|
|||
id: profilePopup
|
||||
}
|
||||
|
||||
PopupMenu {
|
||||
id: messageContextMenu
|
||||
Action {
|
||||
id: viewProfileAction
|
||||
text: qsTr("View profile")
|
||||
onTriggered: profilePopup.open()
|
||||
}
|
||||
Action {
|
||||
text: qsTr("Reply to")
|
||||
onTriggered: chatsModel.enableReplyArea(true, profilePopup.userName, profilePopup.text, profilePopup.identicon)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: chatInputContainer
|
||||
height: 70
|
||||
id: inputArea
|
||||
border.width: 1
|
||||
border.color: Style.current.grey
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: parent.width
|
||||
height: !isReply ? 70 : 140
|
||||
Layout.preferredHeight: height
|
||||
transformOrigin: Item.Bottom
|
||||
clip: true
|
||||
|
||||
|
||||
ReplyArea {
|
||||
id: replyArea
|
||||
visible: isReply
|
||||
}
|
||||
|
||||
ChatInput {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: -border.width
|
||||
border.width: 1
|
||||
border.color: Style.current.grey
|
||||
height: 40
|
||||
anchors.top: !isReply ? inputArea.top : replyArea.bottom
|
||||
anchors.topMargin: 4
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatsModel
|
||||
onReplyAreaEnabled: {
|
||||
isReply = enable;
|
||||
replyArea.userName = userName;
|
||||
replyArea.identicon = identicon;
|
||||
replyArea.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
onClicked: {
|
||||
chatsModel.sendMessage(txtData.text)
|
||||
chatsModel.sendMessage(txtData.text, chatColumn.isReply)
|
||||
txtData.text = ""
|
||||
}
|
||||
background: Rectangle {
|
||||
|
|
|
@ -33,7 +33,7 @@ Rectangle {
|
|||
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
|
||||
if(txtData.text.trim().length > 0){
|
||||
let msg = interpretMessage(txtData.text.trim())
|
||||
chatsModel.sendMessage(msg);
|
||||
chatsModel.sendMessage(msg, chatColumn.isReply);
|
||||
txtData.text = "";
|
||||
event.accepted = true;
|
||||
sendMessageSound.stop()
|
||||
|
|
|
@ -39,6 +39,12 @@ ScrollView {
|
|||
Qt.callLater( chatLogView.positionViewAtEnd )
|
||||
}
|
||||
|
||||
onReplyAreaEnabled: {
|
||||
if (enable){
|
||||
Qt.callLater( chatLogView.positionViewAtEnd )
|
||||
}
|
||||
}
|
||||
|
||||
onMessagePushed: {
|
||||
if (!chatLogView.atYEnd) {
|
||||
// User has scrolled up, we don't want to scroll back
|
||||
|
@ -127,7 +133,8 @@ ScrollView {
|
|||
responseTo: model.responseTo
|
||||
authorCurrentMsg: msgDelegate.ListView.section
|
||||
authorPrevMsg: msgDelegate.ListView.previousSection
|
||||
profileClick: profilePopup.openPopup.bind(profilePopup)
|
||||
profileClick: profilePopup.setPopupData.bind(profilePopup)
|
||||
messageId: model.messageId
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ Item {
|
|||
property string chatId: "chatId"
|
||||
property string outgoingStatus: ""
|
||||
property string responseTo: ""
|
||||
property string messageId: ""
|
||||
|
||||
property string authorCurrentMsg: "authorCurrentMsg"
|
||||
property string authorPrevMsg: "authorPrevMsg"
|
||||
|
@ -205,7 +206,9 @@ Item {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
profileClick(userName, fromAuthor, identicon)
|
||||
chatsModel.setReplyTo(messageId)
|
||||
profileClick(userName, fromAuthor, identicon, text);
|
||||
messageContextMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +230,9 @@ Item {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
profileClick(userName, fromAuthor, identicon)
|
||||
chatsModel.setReplyTo(messageId)
|
||||
profileClick(userName, fromAuthor, identicon, text)
|
||||
messageContextMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,7 +267,7 @@ Item {
|
|||
Rectangle {
|
||||
id: chatReply
|
||||
color: isCurrentUser ? Style.current.blue : Style.current.lightBlue
|
||||
visible: responseTo != ""
|
||||
visible: responseTo != "" && replyMessageIndex > -1
|
||||
height: chatReply.visible ? childrenRect.height : 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: chatReply.visible ? chatBox.chatVerticalPadding : 0
|
||||
|
@ -330,25 +335,6 @@ Item {
|
|||
selectByMouse: true
|
||||
color: !isCurrentUser ? Style.current.black : Style.current.white
|
||||
visible: contentType == Constants.messageType || isEmoji
|
||||
onLinkActivated: {
|
||||
if(link.startsWith("#")){
|
||||
chatsModel.joinChat(link.substring(1), Constants.chatTypePublic);
|
||||
return;
|
||||
}
|
||||
|
||||
if (link.startsWith('//')) {
|
||||
let pk = link.replace("//", "");
|
||||
profileClick(chatsModel.userNameOrAlias(pk), pk, chatsModel.generateIdenticon(pk))
|
||||
return;
|
||||
}
|
||||
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
|
@ -363,6 +349,31 @@ Item {
|
|||
source: contentType === Constants.stickerType ? ("https://ipfs.infura.io/ipfs/" + sticker) : ""
|
||||
visible: contentType === Constants.stickerType
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: chatText.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
let link = chatText.hoveredLink;
|
||||
if(link.startsWith("#")){
|
||||
chatsModel.joinChat(link.substring(1), Constants.chatTypePublic);
|
||||
return;
|
||||
}
|
||||
|
||||
if (link.startsWith('//')) {
|
||||
let pk = link.replace("//", "");
|
||||
profileClick(chatsModel.userNameOrAlias(pk), pk, chatsModel.generateIdenticon(pk), text)
|
||||
return;
|
||||
}
|
||||
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
onPressAndHold: {
|
||||
chatsModel.setReplyTo(messageId)
|
||||
profileClick(userName, fromAuthor, identicon, text);
|
||||
messageContextMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTextEdit {
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../../../../imports"
|
||||
import "../../../../shared"
|
||||
import "./"
|
||||
|
||||
Rectangle {
|
||||
property string userName: "Joseph Joestar"
|
||||
property string message: "Your next line is: is this is a Jojo reference?"
|
||||
property string identicon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
|
||||
|
||||
id: replyArea
|
||||
height: 70
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
color: "#00000000"
|
||||
|
||||
Rectangle {
|
||||
id: closeButton
|
||||
height: 32
|
||||
width: 32
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.right: parent.right
|
||||
radius: 8
|
||||
|
||||
SVGImage {
|
||||
id: closeModalImg
|
||||
source: "../../../../shared/img/close.svg"
|
||||
width: 25
|
||||
height: 25
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeModalMouseArea
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onExited: {
|
||||
closeButton.color = Style.current.white
|
||||
}
|
||||
onEntered: {
|
||||
closeButton.color = Style.current.grey
|
||||
}
|
||||
onClicked: chatsModel.enableReplyArea(false, "","","")
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: chatImage
|
||||
width: 36
|
||||
height: 36
|
||||
anchors.topMargin: 20
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.top: parent.top
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: identicon
|
||||
mipmap: true
|
||||
smooth: false
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
StyledTextEdit {
|
||||
id: replyToUsername
|
||||
text: userName
|
||||
font.bold: true
|
||||
font.pixelSize: 14
|
||||
anchors.leftMargin: 20
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: chatImage.right
|
||||
readOnly: true
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: replyText
|
||||
text: Emoji.parse(message, "26x26")
|
||||
anchors.left: replyToUsername.left
|
||||
anchors.top: replyToUsername.bottom
|
||||
anchors.topMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 15
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
}
|
|
@ -2,4 +2,5 @@ TopBar 1.0 TopBar.qml
|
|||
ChatMessages 1.0 ChatMessages.qml
|
||||
ChatInput 1.0 ChatInput.qml
|
||||
EmptyChat 1.0 EmptyChat.qml
|
||||
ChatButtons 1.0 ChatButtons.qml
|
||||
ChatButtons 1.0 ChatButtons.qml
|
||||
ReplyArea 1.0 ReplyArea.qml
|
|
@ -10,16 +10,23 @@ ModalPopup {
|
|||
property var identicon: ""
|
||||
property var userName: ""
|
||||
property var fromAuthor: ""
|
||||
property var text: ""
|
||||
|
||||
property bool showQR: false
|
||||
property bool isEnsVerified: false
|
||||
property bool noFooter: false
|
||||
|
||||
function openPopup(userNameParam, fromAuthorParam, identiconParam) {
|
||||
function setPopupData(userNameParam, fromAuthorParam, identiconParam, textParam){
|
||||
this.showQR = false
|
||||
this.userName = userNameParam
|
||||
this.fromAuthor = fromAuthorParam
|
||||
this.identicon = identiconParam
|
||||
this.text = textParam
|
||||
this.isEnsVerified = chatsModel.isEnsVerified(this.fromAuthor)
|
||||
}
|
||||
|
||||
function openPopup(userNameParam, fromAuthorParam, identiconParam) {
|
||||
setPopupData(userNameParam, fromAuthorParam, identiconParam, "");
|
||||
popup.open()
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import "../shared"
|
|||
Menu {
|
||||
property alias arrowX: bgPopupMenuTopArrow.x
|
||||
property int paddingSize: 8
|
||||
closePolicy: Popup.CloseOnPressOutsideParent
|
||||
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnReleaseOutside | Popup.CloseOnEscape
|
||||
id: popupMenu
|
||||
topPadding: bgPopupMenuTopArrow.height + paddingSize
|
||||
bottomPadding: paddingSize
|
||||
|
|
Loading…
Reference in New Issue