feat(Chat): add pinned messages feature
This commit is contained in:
parent
6abba06c42
commit
b52dceb984
|
@ -18,6 +18,9 @@ proc handleChatEvents(self: ChatController) =
|
|||
# Display emoji reactions
|
||||
self.status.events.on("reactionsLoaded") do(e:Args):
|
||||
self.view.reactions.push(ReactionsLoadedArgs(e).reactions)
|
||||
# Display already pinned messages
|
||||
self.status.events.on("pinnedMessagesLoaded") do(e:Args):
|
||||
self.view.pushPinnedMessages(MsgsLoadedArgs(e).messages)
|
||||
|
||||
self.status.events.on("contactUpdate") do(e: Args):
|
||||
var evArgs = ContactUpdateArgs(e)
|
||||
|
@ -40,6 +43,8 @@ proc handleChatEvents(self: ChatController) =
|
|||
self.view.communities.addCommunityToList(community)
|
||||
if (evArgs.communityMembershipRequests.len > 0):
|
||||
self.view.communities.addMembershipRequests(evArgs.communityMembershipRequests)
|
||||
if (evArgs.pinnedMessages.len > 0):
|
||||
self.view.addPinnedMessages(evArgs.pinnedMessages)
|
||||
|
||||
self.status.events.on("channelUpdate") do(e: Args):
|
||||
var evArgs = ChatUpdateArgs(e)
|
||||
|
|
|
@ -4,7 +4,7 @@ import
|
|||
proc handleSignals(self: ChatController) =
|
||||
self.status.events.on(SignalType.Message.event) do(e:Args):
|
||||
var data = MessageSignal(e)
|
||||
self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities, data.membershipRequests)
|
||||
self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities, data.membershipRequests, data.pinnedMessages)
|
||||
|
||||
self.status.events.on(SignalType.DiscoverySummary.event) do(e:Args):
|
||||
## Handle mailserver peers being added and removed
|
||||
|
|
|
@ -65,10 +65,17 @@ const asyncMessageLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.}
|
|||
if(reactionsCallSuccess):
|
||||
reactions = reactionsCallResult.parseJson()["result"]
|
||||
|
||||
var pinnedMessages: JsonNode
|
||||
var pinnedMessagesCallSuccess: bool
|
||||
let pinnedMessagesCallResult = rpcPinnedChatMessages(arg.chatId, newJString(""), 20, pinnedMessagesCallSuccess)
|
||||
if(reactionsCallSuccess):
|
||||
pinnedMessages = pinnedMessagesCallResult.parseJson()["result"]
|
||||
|
||||
let responseJson = %*{
|
||||
"chatId": arg.chatId,
|
||||
"messages": messages,
|
||||
"reactions": reactions
|
||||
"reactions": reactions,
|
||||
"pinnedMessages": pinnedMessages
|
||||
}
|
||||
arg.finish(responseJson)
|
||||
|
||||
|
@ -104,6 +111,7 @@ QtObject:
|
|||
currentSuggestions*: SuggestionsList
|
||||
callResult: string
|
||||
messageList*: OrderedTable[string, ChatMessageList]
|
||||
pinnedMessagesList*: OrderedTable[string, ChatMessageList]
|
||||
reactions*: ReactionView
|
||||
stickers*: StickersView
|
||||
groups*: GroupsView
|
||||
|
@ -129,11 +137,14 @@ QtObject:
|
|||
self.currentSuggestions.delete
|
||||
for msg in self.messageList.values:
|
||||
msg.delete
|
||||
for msg in self.pinnedMessagesList.values:
|
||||
msg.delete
|
||||
self.reactions.delete
|
||||
self.stickers.delete
|
||||
self.groups.delete
|
||||
self.transactions.delete
|
||||
self.messageList = initOrderedTable[string, ChatMessageList]()
|
||||
self.pinnedMessagesList = initOrderedTable[string, ChatMessageList]()
|
||||
self.communities.delete
|
||||
self.channelOpenTime = initTable[string, int64]()
|
||||
self.QAbstractListModel.delete
|
||||
|
@ -147,6 +158,7 @@ QtObject:
|
|||
result.contextChannel = newChatItemView(status)
|
||||
result.currentSuggestions = newSuggestionsList()
|
||||
result.messageList = initOrderedTable[string, ChatMessageList]()
|
||||
result.pinnedMessagesList = initOrderedTable[string, ChatMessageList]()
|
||||
result.reactions = newReactionView(status, result.messageList.addr, result.activeChannel)
|
||||
result.stickers = newStickersView(status, result.activeChannel)
|
||||
result.groups = newGroupsView(status,result.activeChannel)
|
||||
|
@ -431,6 +443,7 @@ QtObject:
|
|||
if not self.messageList.hasKey(channel):
|
||||
self.beginInsertRows(newQModelIndex(), self.messageList.len, self.messageList.len)
|
||||
self.messageList[channel] = newChatMessageList(channel, self.status, not chat.isNil and chat.chatType != ChatType.Profile)
|
||||
self.pinnedMessagesList[channel] = newChatMessageList(channel, self.status, false)
|
||||
self.channelOpenTime[channel] = now().toTime.toUnix * 1000
|
||||
self.endInsertRows();
|
||||
|
||||
|
@ -451,6 +464,13 @@ 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:
|
||||
self.upsertChannel(msg.chatId)
|
||||
self.pinnedMessagesList[msg.chatId].add(msg)
|
||||
# put the message as pinned in the message list
|
||||
self.messageList[msg.chatId].changeMessagePinned(msg.id, true)
|
||||
|
||||
proc pushMessages*(self:ChatsView, messages: var seq[Message]) =
|
||||
for msg in messages.mitems:
|
||||
self.upsertChannel(msg.chatId)
|
||||
|
@ -532,6 +552,14 @@ QtObject:
|
|||
read = getMessageList
|
||||
notify = activeChannelChanged
|
||||
|
||||
proc getPinnedMessagesList(self: ChatsView): QVariant {.slot.} =
|
||||
self.upsertChannel(self.activeChannel.id)
|
||||
return newQVariant(self.pinnedMessagesList[self.activeChannel.id])
|
||||
|
||||
QtProperty[QVariant] pinnedMessagesList:
|
||||
read = getPinnedMessagesList
|
||||
notify = activeChannelChanged
|
||||
|
||||
proc pushChatItem*(self: ChatsView, chatItem: Chat) =
|
||||
discard self.chats.addChatItemToList(chatItem)
|
||||
self.messagePushed(self.messageList[chatItem.id].messages.len - 1)
|
||||
|
@ -599,6 +627,10 @@ QtObject:
|
|||
let reactions = parseReactionsResponse(rpcResponseObj["chatId"].getStr, rpcResponseObj["reactions"])
|
||||
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"])
|
||||
self.status.chat.pinnedMessagesByChatID(rpcResponseObj["chatId"].getStr, pinnedMessages[0], pinnedMessages[1])
|
||||
|
||||
proc hideLoadingIndicator*(self: ChatsView) {.slot.} =
|
||||
self.loadingMessages = false
|
||||
self.loadingMessagesChanged(false)
|
||||
|
@ -835,6 +867,35 @@ QtObject:
|
|||
if(id == msg.id): return idx
|
||||
return idx
|
||||
|
||||
proc addPinMessage*(self: ChatsView, messageId: string, chatId: string) =
|
||||
self.upsertChannel(chatId)
|
||||
self.messageList[chatId].changeMessagePinned(messageId, true)
|
||||
self.pinnedMessagesList[chatId].add(self.messageList[chatId].getMessageById(messageId))
|
||||
|
||||
proc removePinMessage*(self: ChatsView, messageId: string, chatId: string) =
|
||||
self.upsertChannel(chatId)
|
||||
self.messageList[chatId].changeMessagePinned(messageId, false)
|
||||
try:
|
||||
self.pinnedMessagesList[chatId].remove(messageId)
|
||||
except Exception as e:
|
||||
error "Error removing ", msg = e.msg
|
||||
|
||||
|
||||
proc pinMessage*(self: ChatsView, messageId: string, chatId: string) {.slot.} =
|
||||
self.status.chat.setPinMessage(messageId, chatId, true)
|
||||
self.addPinMessage(messageId, chatId)
|
||||
|
||||
proc unPinMessage*(self: ChatsView, messageId: string, chatId: string) {.slot.} =
|
||||
self.status.chat.setPinMessage(messageId, chatId, false)
|
||||
self.removePinMessage(messageId, chatId)
|
||||
|
||||
proc addPinnedMessages*(self: ChatsView, pinnedMessages: seq[Message]) =
|
||||
for pinnedMessage in pinnedMessages:
|
||||
if (pinnedMessage.isPinned):
|
||||
self.addPinMessage(pinnedMessage.id, pinnedMessage.localChatId)
|
||||
else:
|
||||
self.removePinMessage(pinnedMessage.id, pinnedMessage.localChatId)
|
||||
|
||||
proc isActiveMailserverResult(self: ChatsView, resultEncoded: string) {.slot.} =
|
||||
let isActiveMailserverAvailable = decode[bool](resultEncoded)
|
||||
if isActiveMailserverAvailable:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import NimQml, Tables, sets, json, sugar
|
||||
import NimQml, Tables, sets, json, sugar, chronicles
|
||||
import ../../../status/status
|
||||
import ../../../status/accounts
|
||||
import ../../../status/chat
|
||||
|
@ -37,8 +37,9 @@ type
|
|||
CommunityId = UserRole + 27
|
||||
HasMention = UserRole + 28
|
||||
StickerPackId = UserRole + 29
|
||||
GapFrom = UserRole + 30
|
||||
GapTo = UserRole + 31
|
||||
IsPinned = UserRole + 30
|
||||
GapFrom = UserRole + 31
|
||||
GapTo = UserRole + 32
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -124,6 +125,15 @@ QtObject:
|
|||
method rowCount(self: ChatMessageList, index: QModelIndex = nil): int =
|
||||
return self.messages.len
|
||||
|
||||
proc countChanged*(self: ChatMessageList) {.signal.}
|
||||
|
||||
proc count*(self: ChatMessageList): int {.slot.} =
|
||||
self.messages.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = count
|
||||
notify = countChanged
|
||||
|
||||
proc getReactions*(self:ChatMessageList, messageId: string):string =
|
||||
if not self.messageReactions.hasKey(messageId): return ""
|
||||
result = self.messageReactions[messageId]
|
||||
|
@ -161,6 +171,7 @@ QtObject:
|
|||
of ChatMessageRoles.LinkUrls: result = newQVariant(message.linkUrls)
|
||||
of ChatMessageRoles.CommunityId: result = newQVariant(message.communityId)
|
||||
of ChatMessageRoles.HasMention: result = newQVariant(message.hasMention)
|
||||
of ChatMessageRoles.IsPinned: result = newQVariant(message.isPinned)
|
||||
# Pass the command parameters as a JSON string
|
||||
of ChatMessageRoles.CommandParameters: result = newQVariant($(%*{
|
||||
"id": message.commandParameters.id,
|
||||
|
@ -205,6 +216,7 @@ QtObject:
|
|||
ChatMessageRoles.CommunityId.int: "communityId",
|
||||
ChatMessageRoles.Alias.int:"alias",
|
||||
ChatMessageRoles.HasMention.int:"hasMention",
|
||||
ChatMessageRoles.IsPinned.int:"isPinned",
|
||||
ChatMessageRoles.LocalName.int:"localName",
|
||||
ChatMessageRoles.StickerPackId.int:"stickerPackId",
|
||||
ChatMessageRoles.GapFrom.int:"gapFrom",
|
||||
|
@ -237,9 +249,11 @@ QtObject:
|
|||
proc add*(self: ChatMessageList, message: Message) =
|
||||
if self.messageIndex.hasKey(message.id): return # duplicated msg
|
||||
|
||||
debug "New message", chatId = self.id, id = message.id, text = message.text
|
||||
self.beginInsertRows(newQModelIndex(), self.messages.len, self.messages.len)
|
||||
self.messageIndex[message.id] = self.messages.len
|
||||
self.messages.add(message)
|
||||
self.countChanged()
|
||||
self.endInsertRows()
|
||||
|
||||
proc add*(self: ChatMessageList, messages: seq[Message]) =
|
||||
|
@ -248,8 +262,19 @@ QtObject:
|
|||
if self.messageIndex.hasKey(message.id): continue
|
||||
self.messageIndex[message.id] = self.messages.len
|
||||
self.messages.add(message)
|
||||
self.countChanged()
|
||||
self.endInsertRows()
|
||||
|
||||
proc remove*(self: ChatMessageList, messageId: string) =
|
||||
if not self.messageIndex.hasKey(messageId): return
|
||||
|
||||
let index = self.getMessageIndex(messageId)
|
||||
self.beginRemoveRows(newQModelIndex(), index, index)
|
||||
self.messages.delete(index)
|
||||
self.messageIndex.del(messageId)
|
||||
self.countChanged()
|
||||
self.endRemoveRows()
|
||||
|
||||
proc getMessageById*(self: ChatMessageList, messageId: string): Message =
|
||||
if (not self.messageIndex.hasKey(messageId)): return
|
||||
return self.messages[self.messageIndex[messageId]]
|
||||
|
@ -269,6 +294,16 @@ QtObject:
|
|||
let bottomRight = self.createIndex(msgIdx, 0, nil)
|
||||
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.EmojiReactions.int])
|
||||
|
||||
proc changeMessagePinned*(self: ChatMessageList, messageId: string, pinned: bool) =
|
||||
if not self.messageIndex.hasKey(messageId): return
|
||||
let msgIdx = self.messageIndex[messageId]
|
||||
var message = self.messages[msgIdx]
|
||||
message.isPinned = pinned
|
||||
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])
|
||||
|
||||
proc markMessageAsSent*(self: ChatMessageList, messageId: string)=
|
||||
let topLeft = self.createIndex(0, 0, nil)
|
||||
let bottomRight = self.createIndex(self.messages.len, 0, nil)
|
||||
|
|
|
@ -24,6 +24,7 @@ type
|
|||
ChatUpdateArgs* = ref object of Args
|
||||
chats*: seq[Chat]
|
||||
messages*: seq[Message]
|
||||
pinnedMessages*: seq[Message]
|
||||
contacts*: seq[Profile]
|
||||
emojiReactions*: seq[Reaction]
|
||||
communities*: seq[Community]
|
||||
|
@ -56,6 +57,7 @@ type
|
|||
contacts*: Table[string, Profile]
|
||||
channels*: Table[string, Chat]
|
||||
msgCursor*: Table[string, string]
|
||||
pinnedMsgCursor*: Table[string, string]
|
||||
emojiCursor*: Table[string, string]
|
||||
lastMessageTimestamps*: Table[string, int64]
|
||||
|
||||
|
@ -73,6 +75,7 @@ proc newChatModel*(events: EventEmitter): ChatModel =
|
|||
result.contacts = initTable[string, Profile]()
|
||||
result.channels = initTable[string, Chat]()
|
||||
result.msgCursor = initTable[string, string]()
|
||||
result.pinnedMsgCursor = initTable[string, string]()
|
||||
result.emojiCursor = initTable[string, string]()
|
||||
result.lastMessageTimestamps = initTable[string, int64]()
|
||||
|
||||
|
@ -99,7 +102,7 @@ proc cleanSpamChatGroups(self: ChatModel, chats: seq[Chat], contacts: seq[Profil
|
|||
else:
|
||||
result.add(chat)
|
||||
|
||||
proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest]) =
|
||||
proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest], pinnedMessages: seq[Message]) =
|
||||
var contacts = getAddedContacts()
|
||||
|
||||
# Automatically decline chat group invitations if admin is not a contact
|
||||
|
@ -118,7 +121,7 @@ proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiRea
|
|||
if self.lastMessageTimestamps[chatId] > ts:
|
||||
self.lastMessageTimestamps[chatId] = ts
|
||||
|
||||
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chatList, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests))
|
||||
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chatList, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages))
|
||||
|
||||
proc hasChannel*(self: ChatModel, chatId: string): bool =
|
||||
self.channels.hasKey(chatId)
|
||||
|
@ -504,3 +507,20 @@ proc pendingRequestsToJoinForCommunity*(self: ChatModel, communityKey: string):
|
|||
|
||||
proc myPendingRequestsToJoin*(self: ChatModel): seq[CommunityMembershipRequest] =
|
||||
result = status_chat.myPendingRequestsToJoin()
|
||||
|
||||
proc setPinMessage*(self: ChatModel, messageId: string, chatId: string, pinned: bool) =
|
||||
status_chat.setPinMessage(messageId, chatId, pinned)
|
||||
|
||||
proc pinnedMessagesByChatID*(self: ChatModel, chatId: string): seq[Message] =
|
||||
if not self.pinnedMsgCursor.hasKey(chatId):
|
||||
self.pinnedMsgCursor[chatId] = "";
|
||||
|
||||
let messageTuple = status_chat.pinnedMessagesByChatID(chatId, self.pinnedMsgCursor[chatId])
|
||||
self.pinnedMsgCursor[chatId] = messageTuple[0];
|
||||
|
||||
result = messageTuple[1]
|
||||
|
||||
proc pinnedMessagesByChatID*(self: ChatModel, chatId: string, cursor: string = "", pinnedMessages: seq[Message]) =
|
||||
self.msgCursor[chatId] = cursor
|
||||
|
||||
self.events.emit("pinnedMessagesLoaded", MsgsLoadedArgs(messages: pinnedMessages))
|
||||
|
|
|
@ -68,6 +68,7 @@ type Message* = object
|
|||
communityId*: string
|
||||
audioDurationMs*: int
|
||||
hasMention*: bool
|
||||
isPinned*: bool
|
||||
|
||||
type Reaction* = object
|
||||
id*: string
|
||||
|
|
|
@ -454,3 +454,31 @@ proc banUserFromCommunity*(pubKey: string, communityId: string): string =
|
|||
"communityId": communityId,
|
||||
"user": pubKey
|
||||
}])
|
||||
|
||||
proc rpcPinnedChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, success: var bool): string =
|
||||
success = true
|
||||
try:
|
||||
result = callPrivateRPC("chatPinnedMessages".prefix, %* [chatId, cursorVal, limit])
|
||||
except RpcException as e:
|
||||
success = false
|
||||
result = e.msg
|
||||
|
||||
proc pinnedMessagesByChatID*(chatId: string, cursor: string): (string, seq[Message]) =
|
||||
var cursorVal: JsonNode
|
||||
|
||||
if cursor == "":
|
||||
cursorVal = newJNull()
|
||||
else:
|
||||
cursorVal = newJString(cursor)
|
||||
|
||||
var success: bool
|
||||
let callResult = rpcPinnedChatMessages(chatId, cursorVal, 20, success)
|
||||
if success:
|
||||
result = parseChatMessagesResponse(chatId, callResult.parseJson()["result"])
|
||||
|
||||
proc setPinMessage*(messageId: string, chatId: string, pinned: bool) =
|
||||
discard callPrivateRPC("sendPinMessage".prefix, %*[{
|
||||
"message_id": messageId,
|
||||
"pinned": pinned,
|
||||
"chat_id": chatId
|
||||
}])
|
|
@ -1,7 +1,7 @@
|
|||
import json, json, options, json_serialization, stint, chronicles
|
||||
import core, types, utils, strutils, strformat
|
||||
import utils
|
||||
from status_go import validateMnemonic, startWallet
|
||||
from status_go import validateMnemonic#, startWallet
|
||||
import ../wallet/account
|
||||
import web3/ethtypes
|
||||
import ./types
|
||||
|
|
|
@ -63,10 +63,29 @@ proc fromEvent*(event: JsonNode): Signal =
|
|||
signal.communities.add(jsonCommunity.toCommunity)
|
||||
|
||||
if event["event"]{"requestsToJoinCommunity"} != nil:
|
||||
debug "requests", event = event["event"]["requestsToJoinCommunity"]
|
||||
for jsonCommunity in event["event"]["requestsToJoinCommunity"]:
|
||||
signal.membershipRequests.add(jsonCommunity.toCommunityMembershipRequest)
|
||||
|
||||
if event["event"]{"pinMessages"} != nil:
|
||||
for jsonPinnedMessage in event["event"]["pinMessages"]:
|
||||
var contentType: ContentType
|
||||
try:
|
||||
contentType = ContentType(jsonPinnedMessage{"contentType"}.getInt)
|
||||
except:
|
||||
warn "Unknown content type received", type = jsonPinnedMessage{"contentType"}.getInt
|
||||
contentType = ContentType.Message
|
||||
signal.pinnedMessages.add(Message(
|
||||
id: jsonPinnedMessage{"message_id"}.getStr,
|
||||
chatId: jsonPinnedMessage{"chat_id"}.getStr,
|
||||
localChatId: jsonPinnedMessage{"localChatId"}.getStr,
|
||||
fromAuthor: jsonPinnedMessage{"from"}.getStr,
|
||||
identicon: jsonPinnedMessage{"identicon"}.getStr,
|
||||
alias: jsonPinnedMessage{"alias"}.getStr,
|
||||
clock: jsonPinnedMessage{"clock"}.getInt,
|
||||
isPinned: jsonPinnedMessage{"pinned"}.getBool,
|
||||
contentType: contentType
|
||||
))
|
||||
|
||||
result = signal
|
||||
|
||||
proc toChatMember*(jsonMember: JsonNode): ChatMember =
|
||||
|
@ -206,8 +225,6 @@ proc toCommunity*(jsonCommunity: JsonNode): Community =
|
|||
name: chat{"name"}.getStr,
|
||||
canPost: chat{"canPost"}.getBool,
|
||||
chatType: ChatType.CommunityChat
|
||||
# TODO get this from access
|
||||
#chat{"permissions"}{"access"}.getInt,
|
||||
))
|
||||
|
||||
if jsonCommunity.hasKey("categories") and jsonCommunity["categories"].kind != JNull:
|
||||
|
|
|
@ -29,6 +29,7 @@ type EnvelopeExpiredSignal* = ref object of Signal
|
|||
|
||||
type MessageSignal* = ref object of Signal
|
||||
messages*: seq[Message]
|
||||
pinnedMessages*: seq[Message]
|
||||
chats*: seq[Chat]
|
||||
contacts*: seq[Profile]
|
||||
installations*: seq[Installation]
|
||||
|
|
|
@ -335,6 +335,7 @@ ScrollView {
|
|||
communityId: model.communityId
|
||||
hasMention: model.hasMention
|
||||
stickerPackId: model.stickerPackId
|
||||
pinnedMessage: model.isPinned
|
||||
gapFrom: model.gapFrom
|
||||
gapTo: model.gapTo
|
||||
prevMessageIndex: {
|
||||
|
|
|
@ -29,6 +29,8 @@ Item {
|
|||
property bool hasMention: false
|
||||
property string linkUrls: ""
|
||||
property bool placeholderMessage: false
|
||||
property bool pinnedMessage: false
|
||||
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
|
||||
|
@ -164,7 +166,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
function clickMessage(isProfileClick, isSticker = false, isImage = false, image = null, emojiOnly = false) {
|
||||
function clickMessage(isProfileClick, isSticker = false, isImage = false, image = null, emojiOnly = false, hideEmojiPicker = false) {
|
||||
if (isImage) {
|
||||
imageClick(image);
|
||||
return;
|
||||
|
@ -176,14 +178,19 @@ Item {
|
|||
|
||||
// Get contact nickname
|
||||
let nickname = appMain.getUserNickname(fromAuthor)
|
||||
messageContextMenu.messageId = root.messageId
|
||||
messageContextMenu.linkUrls = root.linkUrls
|
||||
messageContextMenu.isProfile = !!isProfileClick
|
||||
messageContextMenu.isSticker = isSticker
|
||||
messageContextMenu.emojiOnly = emojiOnly
|
||||
messageContextMenu.show(userName, fromAuthor, root.profileImageSource || identicon, "", nickname, emojiReactionsModel)
|
||||
messageContextMenu.hideEmojiPicker = hideEmojiPicker
|
||||
messageContextMenu.pinnedMessage = pinnedMessage
|
||||
messageContextMenu.show(userName, fromAuthor, root.profileImageSource || identicon, plainText, nickname, emojiReactionsModel)
|
||||
// Position the center of the menu where the mouse is
|
||||
if (messageContextMenu.x + messageContextMenu.width + Style.current.padding < root.width) {
|
||||
messageContextMenu.x = messageContextMenu.x - messageContextMenu.width / 2
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
|
|
|
@ -96,5 +96,26 @@ Rectangle {
|
|||
text: qsTrId("message-reply")
|
||||
}
|
||||
}
|
||||
|
||||
StatusIconButton {
|
||||
id: otherBtn
|
||||
icon.name: "dots-icon"
|
||||
width: 32
|
||||
height: 32
|
||||
onClicked: {
|
||||
if (typeof isMessageActive !== "undefined") {
|
||||
isMessageActive = true
|
||||
}
|
||||
clickMessage(false, isSticker, false, null, false, true)
|
||||
}
|
||||
onHoveredChanged: {
|
||||
buttonsContainer.hoverChanged(this.hovered)
|
||||
}
|
||||
|
||||
StatusToolTip {
|
||||
visible: otherBtn.hovered
|
||||
text: qsTr("More")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,13 +67,61 @@ Item {
|
|||
+ (!chatName.visible && chatImageContent.active ? 6 : 0)
|
||||
+ (emojiReactionLoader.active ? emojiReactionLoader.height: 0)
|
||||
+ (retry.visible && !chatTime.visible ? Style.current.smallPadding : 0)
|
||||
+ (pinnedRectangleLoader.active ? Style.current.smallPadding : 0)
|
||||
width: parent.width
|
||||
|
||||
color: root.isHovered || isMessageActive ? (hasMention ? Style.current.mentionMessageHoverColor : Style.current.backgroundHoverLight) :
|
||||
color: {
|
||||
if (pinnedMessage) {
|
||||
return root.isHovered || isMessageActive ? Style.current.pinnedMessageBackgroundHovered : Style.current.pinnedMessageBackground
|
||||
}
|
||||
|
||||
return root.isHovered || isMessageActive ? (hasMention ? Style.current.mentionMessageHoverColor : Style.current.backgroundHoverLight) :
|
||||
(hasMention ? Style.current.mentionMessageColor : Style.current.transparent)
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: pinnedRectangleLoader
|
||||
active: pinnedMessage
|
||||
anchors.left: chatName.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: active ? Style.current.halfPadding : 0
|
||||
|
||||
sourceComponent: Component {
|
||||
Rectangle {
|
||||
id: pinnedRectangle
|
||||
height: 24
|
||||
width: childrenRect.width + Style.current.smallPadding
|
||||
color: Style.current.pinnedRectangleBackground
|
||||
radius: 12
|
||||
|
||||
SVGImage {
|
||||
id: pinImage
|
||||
source: "../../../../img/pin.svg"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 3
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: Style.current.pinnedMessageBorder
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Pinned")
|
||||
anchors.left: pinImage.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatReply {
|
||||
id: chatReply
|
||||
anchors.top: pinnedRectangleLoader.active ? pinnedRectangleLoader.bottom : parent.top
|
||||
anchors.topMargin: active ? 4 : 0
|
||||
anchors.left: chatImage.left
|
||||
longReply: active && textFieldImplicitWidth > width
|
||||
container: root.container
|
||||
|
@ -87,8 +135,9 @@ Item {
|
|||
active: isMessage && headerRepeatCondition
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.top: chatReply.active ? chatReply.bottom : parent.top
|
||||
anchors.topMargin: Style.current.smallPadding
|
||||
anchors.top: chatReply.active ? chatReply.bottom :
|
||||
pinnedRectangleLoader.active ? pinnedRectangleLoader.bottom : parent.top
|
||||
anchors.topMargin: chatReply.active || pinnedRectangleLoader.active ? 4 : Style.current.smallPadding
|
||||
}
|
||||
|
||||
UsernameLabel {
|
||||
|
@ -245,14 +294,15 @@ Item {
|
|||
}
|
||||
|
||||
Loader {
|
||||
active: hasMention
|
||||
active: hasMention || pinnedMessage
|
||||
height: messageContainer.height
|
||||
anchors.left: messageContainer.left
|
||||
anchors.top: messageContainer.top
|
||||
|
||||
sourceComponent: Component {
|
||||
Rectangle {
|
||||
id: mentionBorder
|
||||
color: Style.current.mentionColor
|
||||
color: pinnedMessage ? Style.current.pinnedMessageBorder : Style.current.mentionColor
|
||||
width: 2
|
||||
height: parent.height
|
||||
}
|
||||
|
@ -260,7 +310,7 @@ Item {
|
|||
}
|
||||
|
||||
HoverHandler {
|
||||
enabled: typeof messageContextMenu !== "undefined" && typeof profilePopupOpened !== "undefined" && !messageContextMenu.opened && !profilePopupOpened && !popupOpened
|
||||
enabled: forceHoverHandler || (typeof messageContextMenu !== "undefined" && typeof profilePopupOpened !== "undefined" && !messageContextMenu.opened && !profilePopupOpened && !popupOpened)
|
||||
onHoveredChanged: setHovered(messageId, hovered)
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,9 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
PinnedMessagesPopup {
|
||||
id: pinnedMessagesPopup
|
||||
}
|
||||
StatusContextMenuButton {
|
||||
id: moreActionsBtn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
|
|
@ -8,9 +8,12 @@ import "../../../../shared/status"
|
|||
import "./"
|
||||
|
||||
PopupMenu {
|
||||
property string messageId
|
||||
property bool isProfile: false
|
||||
property bool isSticker: false
|
||||
property bool emojiOnly: false
|
||||
property bool hideEmojiPicker: false
|
||||
property bool pinnedMessage: false
|
||||
property string linkUrls: ""
|
||||
property alias emojiContainer: emojiContainer
|
||||
|
||||
|
@ -53,7 +56,7 @@ PopupMenu {
|
|||
|
||||
Item {
|
||||
id: emojiContainer
|
||||
visible: messageContextMenu.emojiOnly || !messageContextMenu.isProfile
|
||||
visible: !hideEmojiPicker && (messageContextMenu.emojiOnly || !messageContextMenu.isProfile)
|
||||
width: emojiRow.width
|
||||
height: visible ? emojiRow.height : 0
|
||||
|
||||
|
@ -134,6 +137,37 @@ PopupMenu {
|
|||
visible: !messageContextMenu.emojiOnly
|
||||
}
|
||||
|
||||
Action {
|
||||
id: pinAction
|
||||
text: pinnedMessage ? qsTr("Unpin") :
|
||||
qsTr("Pin")
|
||||
onTriggered: {
|
||||
if (pinnedMessage) {
|
||||
chatsModel.unPinMessage(messageId, chatsModel.activeChannel.id)
|
||||
return
|
||||
}
|
||||
|
||||
chatsModel.pinMessage(messageId, chatsModel.activeChannel.id)
|
||||
messageContextMenu.close()
|
||||
}
|
||||
icon.source: "../../../img/pin"
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
enabled: chatsModel.activeChannel.chatType !== Constants.chatTypePublic
|
||||
}
|
||||
|
||||
Action {
|
||||
id: copyAction
|
||||
text: qsTr("Copy")
|
||||
onTriggered: {
|
||||
chatsModel.copyToClipboard(messageContextMenu.text)
|
||||
messageContextMenu.close()
|
||||
}
|
||||
icon.source: "../../../../shared/img/copy-to-clipboard-icon"
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
}
|
||||
|
||||
Action {
|
||||
id: copyLinkAction
|
||||
text: qsTr("Copy link")
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import QtQuick 2.13
|
||||
import "../../../../imports"
|
||||
import "../../../../shared"
|
||||
import "../../../../shared/status"
|
||||
import "../ChatColumn"
|
||||
|
||||
ModalPopup {
|
||||
id: popup
|
||||
|
||||
header: Item {
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
id: title
|
||||
text: qsTr("Pinned messages")
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
font.bold: true
|
||||
font.pixelSize: 17
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: nbPinnedMessages
|
||||
text: qsTr("%1 message").arg(pinnedMessageListView.count)
|
||||
anchors.left: parent.left
|
||||
anchors.top: title.bottom
|
||||
anchors.topMargin: 2
|
||||
font.pixelSize: 15
|
||||
color: Style.current.secondaryText
|
||||
}
|
||||
|
||||
Separator {
|
||||
anchors.top: nbPinnedMessages.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Style.current.padding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: -Style.current.padding
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusRoundButton {
|
||||
id: btnBack
|
||||
anchors.left: parent.left
|
||||
icon.name: "arrow-right"
|
||||
icon.width: 20
|
||||
icon.height: 16
|
||||
rotation: 180
|
||||
onClicked: popup.close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.6891 4.24964C8.6891 3.7664 8.31098 3.27219 7.84455 3.14582C7.37812 3.01944 7 3.30874 7 3.79199C7 4.27524 7.37812 4.76944 7.84455 4.89582C8.31098 5.02219 8.6891 4.73289 8.6891 4.24964Z" fill="#939BA1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.16699C5.067 1.16699 3.5 2.734 3.5 4.66699C3.5 6.36315 4.70653 7.7775 6.30817 8.09863C6.45243 8.12755 6.5625 8.25034 6.5625 8.39747L6.5625 12.2503C6.5625 12.492 6.75837 12.6878 7 12.6878C7.24162 12.6878 7.4375 12.492 7.4375 12.2503L7.4375 8.39747C7.4375 8.25034 7.54757 8.12755 7.69183 8.09863C9.29347 7.7775 10.5 6.36315 10.5 4.66699C10.5 2.734 8.933 1.16699 7 1.16699ZM4.375 4.66699C4.375 6.11674 5.55025 7.29199 7 7.29199C8.44975 7.29199 9.625 6.11674 9.625 4.66699C9.625 3.21724 8.44975 2.04199 7 2.04199C5.55025 2.04199 4.375 3.21724 4.375 4.66699Z" fill="#939BA1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 942 B |
|
@ -90,6 +90,11 @@ Theme {
|
|||
property color contextMenuButtonForegroundColor: midGrey
|
||||
property color contextMenuButtonBackgroundHoverColor: Qt.hsla(white.hslHue, white.hslSaturation, white.hslLightness, 0.05)
|
||||
|
||||
property color pinnedMessageBorder: "#FFA67B"
|
||||
property color pinnedMessageBackground: "#1afe8f59"
|
||||
property color pinnedMessageBackgroundHovered: "#33fe8f59"
|
||||
property color pinnedRectangleBackground: "#1afe8f59"
|
||||
|
||||
property color roundedButtonForegroundColor: white
|
||||
property color roundedButtonBackgroundColor: buttonBackgroundColor
|
||||
property color roundedButtonSecondaryForegroundColor: black
|
||||
|
|
|
@ -90,6 +90,11 @@ Theme {
|
|||
property color contextMenuButtonForegroundColor: black
|
||||
property color contextMenuButtonBackgroundHoverColor: Qt.hsla(black.hslHue, black.hslSaturation, black.hslLightness, 0.1)
|
||||
|
||||
property color pinnedMessageBorder: "#FE8F59"
|
||||
property color pinnedMessageBackground: "#1aFF9F0F"
|
||||
property color pinnedMessageBackgroundHovered: "#33FF9F0F"
|
||||
property color pinnedRectangleBackground: "#1affffff"
|
||||
|
||||
property color roundedButtonForegroundColor: buttonForegroundColor
|
||||
property color roundedButtonBackgroundColor: secondaryBackground
|
||||
property color roundedButtonSecondaryForegroundColor: grey2
|
||||
|
|
|
@ -72,6 +72,11 @@ QtObject {
|
|||
property color tooltipBackgroundColor
|
||||
property color tooltipForegroundColor
|
||||
|
||||
property color pinnedMessageBorder
|
||||
property color pinnedMessageBackground
|
||||
property color pinnedMessageBackgroundHovered
|
||||
property color pinnedRectangleBackground
|
||||
|
||||
property int xlPadding: 32
|
||||
property int bigPadding: 24
|
||||
property int padding: 16
|
||||
|
|
|
@ -162,6 +162,7 @@ DISTFILES += \
|
|||
app/AppLayouts/Chat/components/EmojiReaction.qml \
|
||||
app/AppLayouts/Chat/components/LeftTabBottomButtons.qml \
|
||||
app/AppLayouts/Chat/components/NoFriendsRectangle.qml \
|
||||
app/AppLayouts/Chat/components/PinnedMessagesPopup.qml \
|
||||
app/AppLayouts/Chat/components/ProfilePopup.qml \
|
||||
app/AppLayouts/Chat/components/EmojiSection.qml \
|
||||
app/AppLayouts/Chat/components/InviteFriendsPopup.qml \
|
||||
|
|
|
@ -137,6 +137,59 @@ Item {
|
|||
font.pixelSize: 12
|
||||
anchors.top: chatName.bottom
|
||||
}
|
||||
|
||||
Item {
|
||||
property bool hovered: false
|
||||
|
||||
id: pinnedMessagesGroup
|
||||
visible: chatType !== Constants.chatTypePublic && chatsModel.pinnedMessagesList.count > 0
|
||||
width: childrenRect.width
|
||||
height: vertiSep.height
|
||||
anchors.left: chatInfo.right
|
||||
anchors.leftMargin: 4
|
||||
anchors.verticalCenter: chatInfo.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: vertiSep
|
||||
height: 12
|
||||
width: 1
|
||||
color: Style.current.border
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: pinImg
|
||||
source: "../../app/img/pin.svg"
|
||||
height: 14
|
||||
width: 14
|
||||
anchors.left: vertiSep.right
|
||||
anchors.leftMargin: 4
|
||||
anchors.verticalCenter: vertiSep.verticalCenter
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: pinnedMessagesGroup.hovered ? Style.current.textColor : Style.current.secondaryText
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: nbPinnedMessagesText
|
||||
color: pinnedMessagesGroup.hovered ? Style.current.textColor : Style.current.secondaryText
|
||||
text: chatsModel.pinnedMessagesList.count
|
||||
font.pixelSize: 12
|
||||
font.underline: pinnedMessagesGroup.hovered
|
||||
anchors.left: pinImg.right
|
||||
anchors.verticalCenter: vertiSep.verticalCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: pinnedMessagesGroup.hovered = true
|
||||
onExited: pinnedMessagesGroup.hovered = false
|
||||
onClicked: pinnedMessagesPopup.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 71f66f68064e9897cd17b6bcecba426a91405034
|
||||
Subproject commit e9a42bfa2be93d9ee09a82e0893d8019c4bcdd3d
|
Loading…
Reference in New Issue