This commit is contained in:
Jonathan Rainville 2021-05-25 15:38:18 -04:00
parent e98cb3b9a4
commit c1f6afd799
18 changed files with 221 additions and 107 deletions

View File

@ -330,6 +330,8 @@ QtObject:
proc sendingMessageFailed*(self: ChatsView) {.signal.}
proc alias*(self: ChatsView, pubKey: string): string {.slot.} =
if (pubKey == ""):
return ""
generateAlias(pubKey)
proc userNameOrAlias*(self: ChatsView, pubKey: string): string {.slot.} =
@ -464,12 +466,17 @@ QtObject:
proc isAddedContact*(self: ChatsView, id: string): bool {.slot.} =
result = self.status.contacts.isAdded(id)
proc pushPinnedMessages*(self:ChatsView, messages: var seq[Message]) =
for msg in messages.mitems:
proc pushPinnedMessages*(self:ChatsView, pinnedMessages: var seq[Message]) =
for msg in pinnedMessages.mitems:
self.upsertChannel(msg.chatId)
self.pinnedMessagesList[msg.chatId].add(msg)
var message = self.messageList[msg.chatId].getMessageById(msg.id)
message.pinnedBy = msg.pinnedBy
message.isPinned = true
self.pinnedMessagesList[msg.chatId].add(message)
# put the message as pinned in the message list
self.messageList[msg.chatId].changeMessagePinned(msg.id, true)
self.messageList[msg.chatId].changeMessagePinned(msg.id, true, msg.pinnedBy)
proc pushMessages*(self:ChatsView, messages: var seq[Message]) =
for msg in messages.mitems:
@ -628,7 +635,7 @@ QtObject:
self.status.chat.chatReactions(rpcResponseObj["chatId"].getStr, true, reactions[0], reactions[1])
if(rpcResponseObj["pinnedMessages"].kind != JNull):
let pinnedMessages = parseChatMessagesResponse(rpcResponseObj["chatId"].getStr, rpcResponseObj["pinnedMessages"])
let pinnedMessages = parseChatMessagesResponse(rpcResponseObj["chatId"].getStr, rpcResponseObj["pinnedMessages"], true)
self.status.chat.pinnedMessagesByChatID(rpcResponseObj["chatId"].getStr, pinnedMessages[0], pinnedMessages[1])
proc hideLoadingIndicator*(self: ChatsView) {.slot.} =
@ -867,14 +874,16 @@ QtObject:
if(id == msg.id): return idx
return idx
proc addPinMessage*(self: ChatsView, messageId: string, chatId: string) =
proc addPinMessage*(self: ChatsView, messageId: string, chatId: string, pinnedBy: string) =
self.upsertChannel(chatId)
self.messageList[chatId].changeMessagePinned(messageId, true)
self.pinnedMessagesList[chatId].add(self.messageList[chatId].getMessageById(messageId))
self.messageList[chatId].changeMessagePinned(messageId, true, pinnedBy)
var message = self.messageList[chatId].getMessageById(messageId)
message.pinnedBy = pinnedBy
self.pinnedMessagesList[chatId].add(message)
proc removePinMessage*(self: ChatsView, messageId: string, chatId: string) =
self.upsertChannel(chatId)
self.messageList[chatId].changeMessagePinned(messageId, false)
self.messageList[chatId].changeMessagePinned(messageId, false, "")
try:
self.pinnedMessagesList[chatId].remove(messageId)
except Exception as e:
@ -883,7 +892,7 @@ QtObject:
proc pinMessage*(self: ChatsView, messageId: string, chatId: string) {.slot.} =
self.status.chat.setPinMessage(messageId, chatId, true)
self.addPinMessage(messageId, chatId)
self.addPinMessage(messageId, chatId, self.pubKey)
proc unPinMessage*(self: ChatsView, messageId: string, chatId: string) {.slot.} =
self.status.chat.setPinMessage(messageId, chatId, false)
@ -892,7 +901,7 @@ QtObject:
proc addPinnedMessages*(self: ChatsView, pinnedMessages: seq[Message]) =
for pinnedMessage in pinnedMessages:
if (pinnedMessage.isPinned):
self.addPinMessage(pinnedMessage.id, pinnedMessage.localChatId)
self.addPinMessage(pinnedMessage.id, pinnedMessage.localChatId, pinnedMessage.pinnedBy)
else:
self.removePinMessage(pinnedMessage.id, pinnedMessage.localChatId)

View File

@ -38,8 +38,9 @@ type
HasMention = UserRole + 28
StickerPackId = UserRole + 29
IsPinned = UserRole + 30
GapFrom = UserRole + 31
GapTo = UserRole + 32
PinnedBy = UserRole + 31
GapFrom = UserRole + 32
GapTo = UserRole + 33
QtObject:
type
@ -172,6 +173,7 @@ QtObject:
of ChatMessageRoles.CommunityId: result = newQVariant(message.communityId)
of ChatMessageRoles.HasMention: result = newQVariant(message.hasMention)
of ChatMessageRoles.IsPinned: result = newQVariant(message.isPinned)
of ChatMessageRoles.PinnedBy: result = newQVariant(message.pinnedBy)
# Pass the command parameters as a JSON string
of ChatMessageRoles.CommandParameters: result = newQVariant($(%*{
"id": message.commandParameters.id,
@ -217,6 +219,7 @@ QtObject:
ChatMessageRoles.Alias.int:"alias",
ChatMessageRoles.HasMention.int:"hasMention",
ChatMessageRoles.IsPinned.int:"isPinned",
ChatMessageRoles.PinnedBy.int:"pinnedBy",
ChatMessageRoles.LocalName.int:"localName",
ChatMessageRoles.StickerPackId.int:"stickerPackId",
ChatMessageRoles.GapFrom.int:"gapFrom",
@ -293,15 +296,16 @@ QtObject:
let bottomRight = self.createIndex(msgIdx, 0, nil)
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.EmojiReactions.int])
proc changeMessagePinned*(self: ChatMessageList, messageId: string, pinned: bool) =
proc changeMessagePinned*(self: ChatMessageList, messageId: string, pinned: bool, pinnedBy: string) =
if not self.messageIndex.hasKey(messageId): return
let msgIdx = self.messageIndex[messageId]
var message = self.messages[msgIdx]
message.isPinned = pinned
message.pinnedBy = pinnedBy
self.messages[msgIdx] = message
let topLeft = self.createIndex(msgIdx, 0, nil)
let bottomRight = self.createIndex(msgIdx, 0, nil)
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.IsPinned.int])
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.IsPinned.int, ChatMessageRoles.PinnedBy.int])
proc markMessageAsSent*(self: ChatMessageList, messageId: string)=
let topLeft = self.createIndex(0, 0, nil)

View File

@ -69,6 +69,7 @@ type Message* = object
audioDurationMs*: int
hasMention*: bool
isPinned*: bool
pinnedBy*: string
type Reaction* = object
id*: string

View File

@ -73,13 +73,13 @@ proc loadChats*(): seq[Chat] =
result.add(chat)
result.sort(sortChats)
proc parseChatMessagesResponse*(chatId: string, rpcResult: JsonNode): (string, seq[Message]) =
proc parseChatMessagesResponse*(chatId: string, rpcResult: JsonNode, isPin: bool = false): (string, seq[Message]) =
let pk = status_settings.getSetting[string](Setting.PublicKey, "0x0")
var messages: seq[Message] = @[]
var msg: Message
if rpcResult["messages"].kind != JNull:
for jsonMsg in rpcResult["messages"]:
messages.add(jsonMsg.toMessage(pk))
messages.add(jsonMsg.toMessage(pk, isPin))
return (rpcResult{"cursor"}.getStr, messages)
proc rpcChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, success: var bool): string =
@ -474,7 +474,7 @@ proc pinnedMessagesByChatID*(chatId: string, cursor: string): (string, seq[Messa
var success: bool
let callResult = rpcPinnedChatMessages(chatId, cursorVal, 20, success)
if success:
result = parseChatMessagesResponse(chatId, callResult.parseJson()["result"])
result = parseChatMessagesResponse(chatId, callResult.parseJson()["result"], true)
proc setPinMessage*(messageId: string, chatId: string, pinned: bool) =
discard callPrivateRPC("sendPinMessage".prefix, %*[{

View File

@ -13,7 +13,7 @@ import types
import web3/conversions
from ../libstatus/utils import parseAddress, wei2Eth
proc toMessage*(jsonMsg: JsonNode, pk: string): Message
proc toMessage*(jsonMsg: JsonNode, pk: string, isPin: bool = false): Message
proc toChat*(jsonChat: JsonNode): Chat
@ -78,7 +78,7 @@ proc fromEvent*(event: JsonNode): Signal =
id: jsonPinnedMessage{"message_id"}.getStr,
chatId: jsonPinnedMessage{"chat_id"}.getStr,
localChatId: jsonPinnedMessage{"localChatId"}.getStr,
fromAuthor: jsonPinnedMessage{"from"}.getStr,
pinnedBy: jsonPinnedMessage{"from"}.getStr,
identicon: jsonPinnedMessage{"identicon"}.getStr,
alias: jsonPinnedMessage{"alias"}.getStr,
clock: jsonPinnedMessage{"clock"}.getInt,
@ -267,7 +267,7 @@ proc toTextItem*(jsonText: JsonNode): TextItem =
result.children.add(child.toTextItem)
proc toMessage*(jsonMsg: JsonNode, pk: string): Message =
proc toMessage*(jsonMsg: JsonNode, pk: string, isPin: bool = false): Message =
var contentType: ContentType
try:
contentType = ContentType(jsonMsg{"contentType"}.getInt)
@ -309,6 +309,10 @@ proc toMessage*(jsonMsg: JsonNode, pk: string): Message =
hasMention: false
)
if isPin:
message.pinnedBy = message.fromAuthor
message.fromAuthor = ""
if contentType == ContentType.Gap:
message.gapFrom = jsonMsg["gapParameters"]["from"].getInt
message.gapTo = jsonMsg["gapParameters"]["to"].getInt

View File

@ -14,6 +14,8 @@ import "../Wallet"
StackLayout {
id: chatColumnLayout
property alias pinnedMessagesPopupComponent: pinnedMessagesPopupComponent
property int chatGroupsListViewCount: 0
property bool isReply: false
@ -35,6 +37,25 @@ StackLayout {
property alias input: chatInput
property string hoveredMessage
property string activeMessage
function setHovered(messageId, hovered) {
if (hovered) {
hoveredMessage = messageId
} else if (hoveredMessage === messageId) {
hoveredMessage = ""
}
}
function setMessageActive(messageId, active) {
if (active) {
activeMessage = messageId
} else if (activeMessage === messageId) {
activeMessage = ""
}
}
Component.onCompleted: {
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
}
@ -45,6 +66,21 @@ StackLayout {
currentIndex: chatsModel.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1
Component {
id: pinnedMessagesPopupComponent
PinnedMessagesPopup {
id: pinnedMessagesPopup
onClosed: destroy()
}
}
StatusImageModal {
id: imagePopup
}
MessageContextMenu {
id: messageContextMenu
}
property var idMap: ({})
property var suggestionsObj: ([])
@ -263,18 +299,10 @@ StackLayout {
}
}
StatusImageModal {
id: imagePopup
}
EmojiReactions {
id: reactionModel
}
MessageContextMenu {
id: messageContextMenu
}
Connections {
target: chatsModel
onActiveChannelChanged: {

View File

@ -22,25 +22,6 @@ ScrollView {
property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight
property int newMessages: 0
property string hoveredMessage
property string activeMessage
function setHovered(messageId, hovered) {
if (hovered) {
hoveredMessage = messageId
} else if (hoveredMessage === messageId) {
hoveredMessage = ""
}
}
function setMessageActive(messageId, active) {
if (active) {
activeMessage = messageId
} else if (activeMessage === messageId) {
activeMessage = ""
}
}
contentItem: chatLogView
Layout.fillWidth: true
Layout.fillHeight: true
@ -336,6 +317,7 @@ ScrollView {
hasMention: model.hasMention
stickerPackId: model.stickerPackId
pinnedMessage: model.isPinned
pinnedBy: model.pinnedBy
gapFrom: model.gapFrom
gapTo: model.gapTo
prevMessageIndex: {

View File

@ -30,12 +30,21 @@ Item {
property string linkUrls: ""
property bool placeholderMessage: false
property bool pinnedMessage: false
property string pinnedBy
property bool forceHoverHandler: false // Used to force the HoverHandler to be active (useful for messages in popups)
property string communityId: ""
property int stickerPackId: -1
property int gapFrom: 0
property int gapTo: 0
z: {
if (typeof chatLogView === "undefined") {
return 1
}
return chatLogView.count - index
}
property string displayUserName: {
if (isCurrentUser) {
//% "You"

View File

@ -11,7 +11,7 @@ Rectangle {
property int contentType: 2
id: buttonsContainer
visible: (buttonsContainer.parentIsHovered || isMessageActive) && contentType != Constants.transactionType
visible: (buttonsContainer.parentIsHovered || isMessageActive) && contentType !== Constants.transactionType
width: buttonRow.width + buttonsContainer.containerMargin * 2
height: 36
radius: Style.current.radius
@ -61,10 +61,12 @@ Rectangle {
onClicked: {
setMessageActive(messageId, true)
clickMessage(false, false, false, null, true)
messageContextMenu.x = buttonsContainer.x + buttonsContainer.width - messageContextMenu.width
if (!forceHoverHandler) {
messageContextMenu.x = buttonsContainer.x + buttonsContainer.width - messageContextMenu.width
// The Math.max is to make sure that the menu is rendered
messageContextMenu.y -= Math.max(messageContextMenu.emojiContainer.height, 56) + Style.current.padding
// The Math.max is to make sure that the menu is rendered
messageContextMenu.y -= Math.max(messageContextMenu.emojiContainer.height, 56) + Style.current.padding
}
}
onHoveredChanged: {
buttonsContainer.hoverChanged(this.hovered)
@ -104,7 +106,7 @@ Rectangle {
height: 32
onClicked: {
if (typeof isMessageActive !== "undefined") {
isMessageActive = true
setMessageActive(messageId, true)
}
clickMessage(false, isSticker, false, null, false, true)
}

View File

@ -99,6 +99,8 @@ Item {
source: "../../../../img/pin.svg"
anchors.left: parent.left
anchors.leftMargin: 3
width: 16
height: 16
anchors.verticalCenter: parent.verticalCenter
ColorOverlay {
@ -109,7 +111,7 @@ Item {
}
StyledText {
text: qsTr("Pinned")
text: qsTr("Pinned by %1").arg(chatsModel.alias(pinnedBy))
anchors.left: pinImage.right
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 13

View File

@ -64,9 +64,6 @@ Item {
}
}
PinnedMessagesPopup {
id: pinnedMessagesPopup
}
StatusContextMenuButton {
id: moreActionsBtn
anchors.verticalCenter: parent.verticalCenter

View File

@ -46,7 +46,9 @@ SplitView {
Component {
id: groupInfoPopupComponent
GroupInfoPopup {}
GroupInfoPopup {
pinnedMessagesPopupComponent: chatColumn.pinnedMessagesPopupComponent
}
}
ChatColumn {

View File

@ -15,6 +15,7 @@ ModalPopup {
property var pubKeys: []
property var channel
property bool isAdmin: false
property Component pinnedMessagesPopupComponent
function resetSelectedMembers(){
pubKeys = [];
@ -98,7 +99,7 @@ ModalPopup {
anchors.top: groupName.bottom
anchors.topMargin: 2
font.pixelSize: 14
color: Style.current.darkGrey
color: Style.current.secondaryText
}
Rectangle {
@ -107,7 +108,7 @@ ModalPopup {
height: 24
width: 24
anchors.verticalCenter: groupName.verticalCenter
anchors.leftMargin: 4
anchors.leftMargin: Style.current.halfPadding
anchors.left: groupName.right
radius: 8
@ -199,16 +200,44 @@ ModalPopup {
anchors.rightMargin: -Style.current.padding
}
StatusSettingsLineButton {
property int pinnedCount: chatsModel.pinnedMessagesList.count
id: pinnedMessagesBtn
visible: pinnedCount > 0
height: visible ? implicitHeight : 0
text: qsTr("Pinned messages")
currentValue: pinnedCount
anchors.top: separator.bottom
anchors.topMargin: visible ? Style.current.halfPadding : 0
anchors.leftMargin: 0
anchors.rightMargin: 0
onClicked: openPopup(pinnedMessagesPopupComponent)
iconSource: "../../../img/pin.svg"
}
Separator {
id: separator2
visible: pinnedMessagesBtn.visible
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
anchors.top: pinnedMessagesBtn.bottom
anchors.topMargin: visible ? Style.current.halfPadding : 0
}
ListView {
id: memberList
anchors.fill: parent
anchors.top: separator.bottom
anchors.top: separator2.bottom
anchors.bottom: popup.bottom
anchors.topMargin: addMembers ? 30 : 15
anchors.topMargin: addMembers ? 30 : Style.current.padding
anchors.bottomMargin: Style.current.padding
anchors.leftMargin: 15
anchors.rightMargin: 15
spacing: 15
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
spacing: Style.current.padding
Layout.fillWidth: true
Layout.fillHeight: true
model: popup.channel.members

View File

@ -134,7 +134,7 @@ PopupMenu {
Separator {
anchors.bottom: viewProfileAction.top
visible: !messageContextMenu.emojiOnly
visible: !messageContextMenu.emojiOnly && !messageContextMenu.hideEmojiPicker
}
Action {
@ -153,7 +153,17 @@ PopupMenu {
icon.source: "../../../img/pin"
icon.width: 16
icon.height: 16
enabled: chatsModel.activeChannel.chatType !== Constants.chatTypePublic
enabled: {
switch (chatsModel.activeChannel.chatType) {
case Constants.chatTypePublic: return false
case Constants.chatTypeStatusUpdate: return false
case Constants.chatTypeOneToOne: return true
case Constants.chatTypePrivateGroupChat: return chatsModel.activeChannel.isAdmin(profileModel.profile.pubKey)
case Constants.chatTypeCommunity: return chatsModel.communities.activeCommunity.admin
}
return false
}
}
Action {

View File

@ -21,8 +21,11 @@ ModalPopup {
}
StyledText {
property int nbMessages: pinnedMessageListView.count
id: nbPinnedMessages
text: qsTr("%1 message").arg(pinnedMessageListView.count)
text: nbMessages > 1 ? qsTr("%1 messages").arg(nbMessages) :
qsTr("%1 message").arg(nbMessages)
anchors.left: parent.left
anchors.top: title.bottom
anchors.topMargin: 2
@ -40,44 +43,60 @@ ModalPopup {
}
}
ListView {
id: pinnedMessageListView
model: chatsModel.pinnedMessagesList
height: parent.height
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
clip: true
Item {
anchors.fill: parent
delegate: Message {
fromAuthor: model.fromAuthor
chatId: model.chatId
userName: model.userName
alias: model.alias
localName: model.localName
message: model.message
plainText: model.plainText
identicon: model.identicon
isCurrentUser: model.isCurrentUser
timestamp: model.timestamp
sticker: model.sticker
contentType: model.contentType
outgoingStatus: model.outgoingStatus
responseTo: model.responseTo
imageClick: imagePopup.openPopup.bind(imagePopup)
messageId: model.messageId
emojiReactions: model.emojiReactions
linkUrls: model.linkUrls
communityId: model.communityId
hasMention: model.hasMention
stickerPackId: model.stickerPackId
timeout: model.timeout
pinnedMessage: true
forceHoverHandler: true
StyledText {
visible: pinnedMessageListView.count === 0
text: qsTr("Pinned messages will appear here.")
anchors.centerIn: parent
color: Style.current.secondaryText
}
ListView {
id: pinnedMessageListView
model: chatsModel.pinnedMessagesList
height: parent.height
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
topMargin: Style.current.halfPadding
anchors.top: parent.top
anchors.topMargin: -Style.current.halfPadding
clip: true
delegate: Message {
fromAuthor: model.fromAuthor
chatId: model.chatId
userName: model.userName
alias: model.alias
localName: model.localName
message: model.message
plainText: model.plainText
identicon: model.identicon
isCurrentUser: model.isCurrentUser
timestamp: model.timestamp
sticker: model.sticker
contentType: model.contentType
outgoingStatus: model.outgoingStatus
responseTo: model.responseTo
imageClick: imagePopup.openPopup.bind(imagePopup)
messageId: model.messageId
emojiReactions: model.emojiReactions
linkUrls: model.linkUrls
communityId: model.communityId
hasMention: model.hasMention
stickerPackId: model.stickerPackId
timeout: model.timeout
pinnedMessage: true
pinnedBy: model.pinnedBy
forceHoverHandler: true
}
}
}
footer: StatusRoundButton {
id: btnBack
anchors.left: parent.left

View File

@ -93,7 +93,7 @@ Theme {
property color pinnedMessageBorder: "#FE8F59"
property color pinnedMessageBackground: "#1aFF9F0F"
property color pinnedMessageBackgroundHovered: "#33FF9F0F"
property color pinnedRectangleBackground: "#1affffff"
property color pinnedRectangleBackground: "#1aFE8F59"
property color roundedButtonForegroundColor: buttonForegroundColor
property color roundedButtonBackgroundColor: secondaryBackground

View File

@ -187,7 +187,7 @@ Item {
hoverEnabled: true
onEntered: pinnedMessagesGroup.hovered = true
onExited: pinnedMessagesGroup.hovered = false
onClicked: pinnedMessagesPopup.open()
onClicked: openPopup(pinnedMessagesPopupComponent)
}
}
}

View File

@ -15,9 +15,10 @@ Rectangle {
signal clicked(bool checked)
property bool isHovered: false
property int badgeSize: 18
property url iconSource
id: root
height: 52
implicitHeight: 52
color: isHovered ? Style.current.backgroundHover : Style.current.transparent
radius: Style.current.radius
border.width: 0
@ -26,9 +27,24 @@ Rectangle {
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
RoundedIcon {
id: pinImage
visible: !!root.iconSource.toString()
source: root.iconSource
iconColor: Style.current.primary
color: Style.current.secondaryBackground
width: 40
height: 40
iconWidth: 24
iconHeight: 24
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
id: textItem
anchors.left: parent.left
anchors.left: pinImage.visible ? pinImage.right : parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
text: root.text