feat: support message replies

This commit is contained in:
Richard Ramos 2020-07-09 13:47:36 -04:00 committed by RichΛrd
parent b22b500d4f
commit 649023bacf
13 changed files with 225 additions and 48 deletions

View File

@ -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)

View File

@ -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.} =

View File

@ -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)

View File

@ -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

View File

@ -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;
}
}
}
}

View File

@ -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 {

View File

@ -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()

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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

View File

@ -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()
}

View File

@ -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