2022-10-07 13:50:31 +00:00
import QtQuick 2.14
import QtQuick . Layouts 1.14
2021-10-26 14:21:08 +00:00
2021-10-28 20:23:30 +00:00
import utils 1.0
2021-10-27 21:27:49 +00:00
import shared . panels 1.0
import shared . status 1.0
import shared . controls 1.0
2022-07-05 10:12:27 +00:00
import shared . popups 1.0
2021-10-28 20:23:30 +00:00
import shared . views . chat 1.0
import shared . controls . chat 1.0
2021-09-28 15:04:06 +00:00
2022-07-05 10:12:27 +00:00
import StatusQ . Core 0.1
import StatusQ . Core . Theme 0.1
import StatusQ . Core . Utils 0.1 as StatusQUtils
import StatusQ . Controls 0.1
import StatusQ . Components 0.1
2022-07-04 15:48:04 +00:00
Loader {
2021-07-16 15:02:47 +00:00
id: root
2022-01-18 21:02:47 +00:00
2022-07-05 10:12:27 +00:00
property var rootStore
2021-10-21 22:39:53 +00:00
property var messageStore
2022-02-08 12:08:02 +00:00
property var usersStore
2022-01-04 12:06:05 +00:00
property var contactsStore
2022-12-13 15:49:31 +00:00
property var messageContextMenu: null
2022-03-14 19:32:52 +00:00
property string channelEmoji
2022-06-29 16:50:10 +00:00
property bool isActiveChannel: false
2021-12-09 12:53:40 +00:00
2022-07-25 12:43:05 +00:00
property var chatLogView
2022-04-13 09:59:16 +00:00
property var emojiPopup
2022-11-14 20:21:00 +00:00
property var stickersPopup
2022-04-13 09:59:16 +00:00
2022-02-24 12:15:02 +00:00
// Once we redo qml we will know all section/chat related details in each message form the parent components
// without an explicit need to fetch those details via message store/module.
property bool isChatBlocked: false
2021-12-09 12:53:40 +00:00
property string messageId: ""
2022-02-24 15:04:59 +00:00
property string communityId: ""
2022-10-06 13:48:04 +00:00
2021-12-09 12:53:40 +00:00
property string senderId: ""
property string senderDisplayName: ""
2022-09-14 07:35:26 +00:00
property string senderOptionalName: ""
property bool senderIsEnsVerified: false
2021-12-09 12:53:40 +00:00
property string senderIcon: ""
2023-01-10 11:29:24 +00:00
//TODO: provide the sender color hash from nim model in case of ContactVerificationRequest, OngoingContactVerificationRequest or PinnedMessagesPopupremove
property var senderColorHash: senderId != "" ? Utils . getColorHashAsJson ( senderId , senderIsEnsVerified ) : ""
2021-12-09 12:53:40 +00:00
property bool amISender: false
2023-01-12 09:28:45 +00:00
property bool amIChatAdmin: messageStore && messageStore . amIChatAdmin
2022-03-04 21:33:48 +00:00
property bool senderIsAdded: false
2022-06-28 18:11:18 +00:00
property int senderTrustStatus: Constants . trustStatus . unknown
2022-07-05 10:12:27 +00:00
property string messageText: ""
2023-01-06 20:19:27 +00:00
property string unparsedText: ""
2021-12-09 12:53:40 +00:00
property string messageImage: ""
2022-07-05 10:12:27 +00:00
property double messageTimestamp: 0 // We use double, because QML's int is too small
2021-12-09 12:53:40 +00:00
property string messageOutgoingStatus: ""
2022-12-08 21:01:08 +00:00
property string resendError: ""
2022-10-26 08:07:46 +00:00
property int messageContentType: Constants . messageContentType . messageType
2021-12-09 12:53:40 +00:00
property bool pinnedMessage: false
2022-01-05 15:50:03 +00:00
property string messagePinnedBy: ""
2021-12-29 10:32:43 +00:00
property var reactionsModel: [ ]
2022-01-25 12:56:53 +00:00
property string linkUrls: ""
2022-09-15 07:31:38 +00:00
property string messageAttachments: ""
2022-02-09 00:04:49 +00:00
property var transactionParams
2021-12-09 12:53:40 +00:00
2023-01-12 19:23:26 +00:00
property string responseToMessageWithId: ""
property string quotedMessageText: ""
property string quotedMessageFrom: ""
property int quotedMessageContentType: Constants . messageContentType . messageType
property int quotedMessageFromIterator: - 1
2023-01-11 21:16:35 +00:00
property bool quotedMessageDeleted: false
2023-01-10 11:29:24 +00:00
property string quotedMessageAuthorDetailsName: ""
property string quotedMessageAuthorDetailsDisplayName: ""
property string quotedMessageAuthorDetailsThumbnailImage: ""
property bool quotedMessageAuthorDetailsEnsVerified: false
property bool quotedMessageAuthorDetailsIsContact: false
property var quotedMessageAuthorDetailsColorHash
2023-01-12 19:23:26 +00:00
2022-07-05 10:12:27 +00:00
// External behavior changers
property bool isInPinnedPopup: false // The pinned popup limits the number of buttons shown
property bool disableHover: false // Used to force the HoverHandler to be active (useful for messages in popups)
property bool placeholderMessage: false
2022-02-25 10:42:32 +00:00
property int gapFrom: 0
property int gapTo: 0
2021-12-09 12:53:40 +00:00
property int prevMessageIndex: - 1
2023-01-10 11:29:24 +00:00
property int prevMessageContentType: prevMessageAsJsonObj ? prevMessageAsJsonObj.contentType : Constants . messageContentType . unknownContentType
property double prevMessageTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0
property string prevMessageSenderId: prevMessageAsJsonObj ? prevMessageAsJsonObj.senderId : ""
2021-12-09 12:53:40 +00:00
property var prevMessageAsJsonObj
property int nextMessageIndex: - 1
2023-01-10 11:29:24 +00:00
property int nextMessageTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.timestamp : 0
2021-12-09 12:53:40 +00:00
property var nextMessageAsJsonObj
2022-01-17 18:46:46 +00:00
property bool editModeOn: false
2022-07-05 10:12:27 +00:00
property bool isEdited: false
2022-01-17 18:46:46 +00:00
2023-01-10 11:29:24 +00:00
property bool shouldRepeatHeader: d . getShouldRepeatHeader ( messageTimestamp , prevMessageTimestamp , messageOutgoingStatus )
2021-10-21 22:39:53 +00:00
2021-02-01 18:40:55 +00:00
property bool hasMention: false
2021-12-09 12:53:40 +00:00
2022-10-26 08:07:46 +00:00
property bool stickersLoaded: false
2022-01-13 19:25:38 +00:00
property string sticker: "Qme8vJtyrEHxABcSVGPF95PtozDgUyfr1xGjePmFdZgk9v"
property int stickerPack: - 1
2022-07-05 10:12:27 +00:00
property bool isEmoji: messageContentType === Constants . messageContentType . emojiType
2022-09-15 07:31:38 +00:00
property bool isImage: messageContentType === Constants . messageContentType . imageType || ( isDiscordMessage && messageImage != "" )
2022-07-05 10:12:27 +00:00
property bool isAudio: messageContentType === Constants . messageContentType . audioType
property bool isStatusMessage: messageContentType === Constants . messageContentType . systemMessagePrivateGroupType
property bool isSticker: messageContentType === Constants . messageContentType . stickerType
2022-09-15 07:31:38 +00:00
property bool isDiscordMessage: messageContentType === Constants . messageContentType . discordMessageType
property bool isText: messageContentType === Constants . messageContentType . messageType || messageContentType === Constants . messageContentType . editType || isDiscordMessage
2021-02-01 18:40:55 +00:00
property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio
2022-07-05 10:12:27 +00:00
|| messageContentType === Constants . messageContentType . communityInviteType || messageContentType === Constants . messageContentType . transactionType
2020-06-10 15:14:12 +00:00
2023-01-11 15:12:07 +00:00
readonly property bool isExpired: d . getIsExpired ( messageTimestamp , messageOutgoingStatus )
2022-12-08 21:01:08 +00:00
readonly property bool isSending: messageOutgoingStatus === Constants . sending && ! isExpired
2020-07-16 17:27:09 +00:00
2022-01-18 21:02:47 +00:00
signal imageClicked ( var image )
2020-11-30 17:03:52 +00:00
2022-07-05 10:12:27 +00:00
// WARNING: To much arguments here. Create an object argument.
property var messageClickHandler: function ( sender , point ,
isProfileClick ,
isSticker = false ,
isImage = false ,
image = null ,
isEmoji = false ,
hideEmojiPicker = false ,
isReply = false ,
isRightClickOnImage = false ,
imageSource = "" ) {
2021-12-14 14:19:55 +00:00
2023-01-06 15:43:54 +00:00
if ( placeholderMessage || ! ( root . rootStore . mainModuleInst . activeSection . joined || isProfileClick ) ) {
2021-12-14 14:19:55 +00:00
return
}
2021-12-09 12:53:40 +00:00
2021-12-14 14:19:55 +00:00
messageContextMenu . myPublicKey = userProfile . pubKey
2022-12-26 16:25:16 +00:00
messageContextMenu . amIChatAdmin = root . amIChatAdmin
2023-01-12 09:28:45 +00:00
messageContextMenu . pinMessageAllowedForMembers = messageStore . isPinMessageAllowedForMembers
messageContextMenu . chatType = messageStore . chatType
2021-12-14 14:19:55 +00:00
messageContextMenu . messageId = root . messageId
2023-01-06 20:19:27 +00:00
messageContextMenu . unparsedText = root . unparsedText
2021-12-14 14:19:55 +00:00
messageContextMenu . messageSenderId = root . senderId
messageContextMenu . messageContentType = root . messageContentType
messageContextMenu . pinnedMessage = root . pinnedMessage
2022-09-12 16:42:58 +00:00
messageContextMenu . canPin = ! ! root . messageStore && root . messageStore . getNumberOfPinnedMessages ( ) < Constants . maxNumberOfPins
2021-12-14 14:19:55 +00:00
messageContextMenu . selectedUserPublicKey = root . senderId
messageContextMenu . selectedUserDisplayName = root . senderDisplayName
2022-10-06 20:15:15 +00:00
messageContextMenu . selectedUserIcon = root . senderIcon
2021-12-14 14:19:55 +00:00
messageContextMenu . imageSource = imageSource
messageContextMenu . isProfile = ! ! isProfileClick
messageContextMenu . isRightClickOnImage = isRightClickOnImage
2022-05-17 10:40:27 +00:00
messageContextMenu . isEmoji = isEmoji
2022-05-17 13:09:00 +00:00
messageContextMenu . isSticker = isSticker
2021-12-14 14:19:55 +00:00
messageContextMenu . hideEmojiPicker = hideEmojiPicker
2023-01-12 19:23:26 +00:00
if ( isReply ) {
if ( ! quotedMessageFrom ) {
// The responseTo message was deleted so we don't eneble to right click the unaviable profile
2021-12-14 14:19:55 +00:00
return
2023-01-12 19:23:26 +00:00
}
messageContextMenu . messageSenderId = quotedMessageFrom
messageContextMenu . selectedUserPublicKey = quotedMessageFrom
2023-01-10 11:29:24 +00:00
messageContextMenu . selectedUserDisplayName = quotedMessageAuthorDetailsDisplayName
messageContextMenu . selectedUserIcon = quotedMessageAuthorDetailsThumbnailImage
2021-12-14 14:19:55 +00:00
}
2021-12-09 12:53:40 +00:00
2022-07-05 10:12:27 +00:00
messageContextMenu . parent = sender ;
messageContextMenu . popup ( point ) ;
2020-06-17 12:53:51 +00:00
}
2022-01-12 12:55:26 +00:00
signal showReplyArea ( string messageId , string author )
2021-10-01 15:58:36 +00:00
2022-01-27 11:28:27 +00:00
function startMessageFoundAnimation ( ) {
2022-07-04 15:48:04 +00:00
root . item . startMessageFoundAnimation ( ) ;
2022-01-27 11:28:27 +00:00
}
2021-10-01 15:58:36 +00:00
2021-12-08 21:20:43 +00:00
signal openStickerPackPopup ( string stickerPackId )
2022-07-05 10:12:27 +00:00
z: ( typeof chatLogView === "undefined" ) ? 1 : ( chatLogView . count - index )
2022-12-05 07:52:41 +00:00
asynchronous: true
2022-07-05 10:12:27 +00:00
sourceComponent: {
switch ( messageContentType ) {
case Constants.messageContentType.chatIdentifier:
return channelIdentifierComponent
case Constants.messageContentType.fetchMoreMessagesButton:
return fetchMoreMessagesButtonComponent
case Constants.messageContentType.systemMessagePrivateGroupType:
return privateGroupHeaderComponent
case Constants.messageContentType.gapType:
return gapComponent
2022-12-05 07:52:41 +00:00
case Constants.messageContentType.newMessagesMarker:
return newMessagesMarkerComponent
2022-07-05 10:12:27 +00:00
default:
return messageComponent
}
}
QtObject {
id: d
readonly property int chatButtonSize: 32
2022-09-20 14:14:58 +00:00
readonly property bool isSingleImage: linkUrlsModel . count === 1 && linkUrlsModel . get ( 0 ) . isImage
&& ` < p > $ { linkUrlsModel . get ( 0 ) . link } < / p > ` = = = r o o t . m e s s a g e T e x t
property int unfurledLinksCount: 0
2022-07-05 10:12:27 +00:00
property string activeMessage
2023-01-06 15:43:54 +00:00
readonly property bool isMessageActive: d . activeMessage === root . messageId
2022-07-05 10:12:27 +00:00
function setMessageActive ( messageId , active ) {
// TODO: Is argument messageId actually needed?
// It was probably used with dynamic scoping,
// but not this method can be moved to private `d`.
// Probably that it was done this way, because `MessageView` is reused as delegate.
if ( active ) {
d . activeMessage = messageId ;
return ;
}
if ( d . activeMessage === messageId ) {
d . activeMessage = "" ;
return ;
}
}
2022-12-20 13:21:33 +00:00
function getShouldRepeatHeader ( messageTimeStamp , prevMessageTimeStamp , messageOutgoingStatus ) {
return ( ( messageTimeStamp - prevMessageTimeStamp ) / 60 / 1000 ) > Constants . repeatHeaderInterval
|| d . getIsExpired ( messageTimeStamp , messageOutgoingStatus )
}
function getIsExpired ( messageTimeStamp , messageOutgoingStatus ) {
return ( messageOutgoingStatus === Constants . sending && ( Math . floor ( messageTimeStamp ) + 180000 ) < Date . now ( ) ) || messageOutgoingStatus === Constants . expired
}
2022-07-05 10:12:27 +00:00
}
2022-09-20 14:14:58 +00:00
onLinkUrlsChanged: {
linkUrlsModel . clear ( )
if ( ! root . linkUrls ) {
return
}
root . linkUrls . split ( " " ) . forEach ( link = > {
linkUrlsModel . append ( { link , isImage: Utils . hasImageExtension ( link ) } )
} )
}
ListModel {
id: linkUrlsModel
}
2022-07-05 10:12:27 +00:00
Connections {
enabled: d . isMessageActive
target: root . messageContextMenu
2022-09-22 22:01:29 +00:00
function onClosed ( ) {
2022-07-05 10:12:27 +00:00
d . setMessageActive ( root . messageId , false )
}
}
2021-10-01 15:58:36 +00:00
2021-05-10 19:12:26 +00:00
Component {
id: gapComponent
2021-10-21 00:41:54 +00:00
GapComponent {
2022-02-25 10:42:32 +00:00
gapFrom: root . gapFrom
gapTo: root . gapTo
2021-10-21 00:41:54 +00:00
onClicked: {
2022-01-28 23:18:30 +00:00
messageStore . fillGaps ( messageId )
root . visible = false ;
root . height = 0 ;
2021-05-10 19:12:26 +00:00
}
}
}
2020-09-04 11:55:24 +00:00
Component {
id: fetchMoreMessagesButtonComponent
2021-10-21 00:41:54 +00:00
FetchMoreMessagesButton {
2021-12-10 16:11:18 +00:00
nextMessageIndex: root . nextMessageIndex
2023-01-10 11:29:24 +00:00
nextMsgTimestamp: root . nextMessageTimestamp
2021-10-21 00:41:54 +00:00
onTimerTriggered: {
2022-01-28 23:18:30 +00:00
messageStore . requestMoreMessages ( ) ;
2020-09-04 11:55:24 +00:00
}
}
}
2020-07-15 21:04:14 +00:00
Component {
id: channelIdentifierComponent
2021-12-10 16:11:18 +00:00
ChannelIdentifierView {
chatName: root . senderDisplayName
2022-04-01 11:46:32 +00:00
chatId: root . messageStore . getChatId ( )
2023-01-12 09:28:45 +00:00
chatType: root . messageStore . chatType
chatColor: root . messageStore . chatColor
2022-03-14 19:32:52 +00:00
chatEmoji: root . channelEmoji
2022-12-26 16:25:16 +00:00
amIChatAdmin: root . amIChatAdmin
2022-08-11 10:58:09 +00:00
chatIcon: {
2023-01-12 09:28:45 +00:00
if ( root . messageStore . chatType === Constants . chatType . privateGroupChat &&
root . messageStore . chatIcon !== "" ) {
return root . messageStore . chatIcon
2022-08-11 10:58:09 +00:00
}
2022-10-06 20:15:15 +00:00
return root . senderIcon
2022-08-11 10:58:09 +00:00
}
2020-06-10 18:23:18 +00:00
}
2020-05-27 22:59:17 +00:00
}
2020-07-15 21:04:14 +00:00
// Private group Messages
Component {
id: privateGroupHeaderComponent
StyledText {
2020-06-25 20:17:42 +00:00
wrapMode: Text . Wrap
2020-11-30 17:03:52 +00:00
text: {
2020-09-21 15:47:15 +00:00
return ` < html > ` +
2022-07-05 10:12:27 +00:00
` < head > ` +
` < style type = "text/css" > ` +
` a { ` +
2020-09-21 15:47:15 +00:00
` color: $ { Style . current . textColor } ; ` +
` text - decoration: none ; ` +
2022-07-05 10:12:27 +00:00
` } ` +
` < / s t y l e > ` +
` < / h e a d > ` +
` < body > ` +
` $ { messageText } ` +
` < / b o d y > ` +
` < / h t m l > ` ;
2020-09-21 15:47:15 +00:00
}
2020-07-15 21:04:14 +00:00
visible: isStatusMessage
2020-09-21 15:47:15 +00:00
font.pixelSize: 14
color: Style . current . secondaryText
2020-07-15 21:04:14 +00:00
width: parent . width - 120
horizontalAlignment: Text . AlignHCenter
anchors.horizontalCenter: parent . horizontalCenter
textFormat: Text . RichText
2020-12-07 23:38:53 +00:00
topPadding: root . prevMessageIndex === 1 ? Style.current.bigPadding : 0
2020-07-09 17:47:36 +00:00
}
2020-07-09 15:50:38 +00:00
}
2020-05-28 19:32:14 +00:00
2022-10-07 13:50:31 +00:00
2020-07-15 21:04:14 +00:00
Component {
2022-07-05 10:12:27 +00:00
id: messageComponent
2022-10-07 13:50:31 +00:00
ColumnLayout {
spacing: 0
2022-10-26 08:07:46 +00:00
function startMessageFoundAnimation ( ) {
delegate . startMessageFoundAnimation ( ) ;
}
2022-10-07 13:50:31 +00:00
StatusDateGroupLabel {
id: dateGroupLabel
Layout.fillWidth: true
Layout.topMargin: 16
Layout.bottomMargin: 16
messageTimestamp: root . messageTimestamp
2023-01-10 11:29:24 +00:00
previousMessageTimestamp: root . prevMessageIndex === - 1 ? 0 : root . prevMessageTimestamp
2022-10-07 13:50:31 +00:00
visible: text !== ""
2021-12-14 14:19:55 +00:00
}
2022-10-07 13:50:31 +00:00
StatusMessage {
id: delegate
Layout.fillWidth: true
2022-12-13 09:37:27 +00:00
2022-10-07 13:50:31 +00:00
function convertContentType ( value ) {
switch ( value ) {
case Constants.messageContentType.messageType:
return StatusMessage . ContentType . Text ;
case Constants.messageContentType.stickerType:
return StatusMessage . ContentType . Sticker ;
case Constants.messageContentType.emojiType:
return StatusMessage . ContentType . Emoji ;
case Constants.messageContentType.transactionType:
return StatusMessage . ContentType . Transaction ;
case Constants.messageContentType.imageType:
return StatusMessage . ContentType . Image ;
case Constants.messageContentType.audioType:
return StatusMessage . ContentType . Audio ;
case Constants.messageContentType.communityInviteType:
return StatusMessage . ContentType . Invitation ;
case Constants.messageContentType.fetchMoreMessagesButton:
case Constants.messageContentType.chatIdentifier:
case Constants.messageContentType.unknownContentType:
case Constants.messageContentType.statusType:
case Constants.messageContentType.systemMessagePrivateGroupType:
case Constants.messageContentType.gapType:
case Constants.messageContentType.editType:
default:
return StatusMessage . ContentType . Unknown ;
}
}
2022-10-07 14:17:24 +00:00
2022-10-07 13:50:31 +00:00
readonly property int contentType: convertContentType ( root . messageContentType )
2022-10-26 08:07:46 +00:00
readonly property bool isReply: root . responseToMessageWithId !== ""
2022-10-07 14:17:24 +00:00
2022-12-21 15:04:25 +00:00
property string originalMessageText: ""
function editCancelledHandler ( ) {
root . messageStore . setEditModeOff ( root . messageId )
}
2022-10-07 13:50:31 +00:00
function editCompletedHandler ( newMessageText ) {
2022-12-21 15:04:25 +00:00
if ( delegate . originalMessageText === newMessageText ) {
delegate . editCancelledHandler ( )
return
}
2022-10-07 13:50:31 +00:00
const message = root . rootStore . plainText ( StatusQUtils . Emoji . deparse ( newMessageText ) )
2022-12-21 15:04:25 +00:00
2022-10-07 13:50:31 +00:00
if ( message . length <= 0 )
return ;
2022-01-18 21:02:47 +00:00
2022-10-07 13:50:31 +00:00
const interpretedMessage = root . messageStore . interpretMessage ( message )
root . messageStore . setEditModeOff ( root . messageId )
2022-11-08 15:44:05 +00:00
root . messageStore . editMessage ( root . messageId , root . messageContentType , interpretedMessage )
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
2022-12-20 13:21:33 +00:00
function nextMessageHasHeader ( ) {
if ( ! root . nextMessageAsJsonObj ) {
return false
}
return root . senderId !== root . nextMessageAsJsonObj . senderId ||
d . getShouldRepeatHeader ( root . nextMessageAsJsonObj . timeStamp , root . messageTimestamp , root . nextMessageAsJsonObj . outgoingStatus ) ||
root . nextMessageAsJsonObj . responseToMessageWithId !== ""
}
2022-10-07 13:50:31 +00:00
pinnedMsgInfoText: root . isDiscordMessage ? qsTr ( "Pinned" ) : qsTr ( "Pinned by" )
reactionIcons: [
Style . svg ( "emojiReactions/heart" ) ,
Style . svg ( "emojiReactions/thumbsUp" ) ,
Style . svg ( "emojiReactions/thumbsDown" ) ,
Style . svg ( "emojiReactions/laughing" ) ,
Style . svg ( "emojiReactions/sad" ) ,
Style . svg ( "emojiReactions/angry" ) ,
]
timestamp: root . messageTimestamp
editMode: root . editModeOn
isAReply: delegate . isReply
isEdited: root . isEdited
hasMention: root . hasMention
isPinned: root . pinnedMessage
2022-12-13 09:37:27 +00:00
pinnedBy: {
if ( ! root . pinnedMessage || root . isDiscordMessage )
return ""
const contact = Utils . getContactDetailsAsJson ( root . messagePinnedBy , false )
const ensName = contact . ensVerified ? contact.name : ""
return ProfileUtils . displayName ( contact . localNickname , ensName , contact . displayName , contact . alias )
}
2022-10-07 13:50:31 +00:00
hasExpired: root . isExpired
2022-12-08 21:01:08 +00:00
isSending: root . isSending
resendError: root . resendError
2022-10-07 13:50:31 +00:00
reactionsModel: root . reactionsModel
2023-01-10 11:29:24 +00:00
showHeader: root . shouldRepeatHeader || dateGroupLabel . visible || isAReply ||
( root . prevMessageContentType !== Constants . messageContentType . systemMessagePrivateGroupType && root . senderId !== root . prevMessageSenderId )
2022-10-07 13:50:31 +00:00
isActiveMessage: d . isMessageActive
2022-12-20 13:21:33 +00:00
topPadding: showHeader ? Style.current.halfPadding : 2
bottomPadding: showHeader && nextMessageHasHeader ( ) ? Style.current.halfPadding : 2
2022-10-07 13:50:31 +00:00
disableHover: root . disableHover ||
2023-01-10 12:07:24 +00:00
( root . chatLogView && root . chatLogView . moving ) ||
2022-12-13 15:49:31 +00:00
( root . messageContextMenu && root . messageContextMenu . opened ) ||
2023-01-06 15:43:54 +00:00
Global . profilePopupOpened ||
Global . popupOpened
2022-10-07 13:50:31 +00:00
hideQuickActions: root . isChatBlocked ||
root . placeholderMessage ||
2022-11-21 16:07:18 +00:00
root . isInPinnedPopup ||
2022-11-29 09:04:47 +00:00
root . editModeOn ||
! root . rootStore . mainModuleInst . activeSection . joined
2022-10-07 13:50:31 +00:00
hideMessage: d . isSingleImage && d . unfurledLinksCount === 1
2023-01-06 15:43:54 +00:00
overrideBackground: root . placeholderMessage
2022-10-07 13:50:31 +00:00
profileClickable: ! root . isDiscordMessage
messageAttachments: root . messageAttachments
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
onEditCancelled: {
2022-12-21 15:04:25 +00:00
delegate . editCancelledHandler ( )
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
onEditCompleted: {
delegate . editCompletedHandler ( newMsgText )
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
onImageClicked: {
switch ( mouse . button ) {
case Qt.LeftButton:
root . imageClicked ( image , mouse ) ;
break ;
case Qt.RightButton:
root . messageClickHandler ( image , Qt . point ( mouse . x , mouse . y ) , false , false , true , image , false , true , false , true , imageSource )
break ;
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
onLinkActivated: {
if ( link . startsWith ( '//' ) ) {
const pubkey = link . replace ( "//" , "" ) ;
Global . openProfilePopup ( pubkey )
2022-10-20 09:05:10 +00:00
return
} else if ( link . startsWith ( '#' ) ) {
2022-10-07 13:50:31 +00:00
rootStore . chatCommunitySectionModule . switchToChannel ( link . replace ( "#" , "" ) )
2022-10-20 09:05:10 +00:00
return
2022-11-04 12:05:32 +00:00
} else if ( Utils . isStatusDeepLink ( link ) ) {
2022-10-20 09:05:10 +00:00
rootStore . activateStatusDeepLink ( link )
return
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
Global . openLink ( link )
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
onProfilePictureClicked: {
d . setMessageActive ( root . messageId , true ) ;
root . messageClickHandler ( sender , Qt . point ( mouse . x , mouse . y ) , true ) ;
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
onReplyProfileClicked: {
d . setMessageActive ( root . messageId , true ) ;
root . messageClickHandler ( sender , Qt . point ( mouse . x , mouse . y ) , true , false , false , null , false , false , true ) ;
}
2022-07-05 10:12:27 +00:00
2022-11-08 17:34:41 +00:00
onReplyMessageClicked: {
root . messageStore . messageModule . jumpToMessage ( root . responseToMessageWithId )
}
2022-10-07 13:50:31 +00:00
onSenderNameClicked: {
d . setMessageActive ( root . messageId , true ) ;
root . messageClickHandler ( sender , Qt . point ( mouse . x , mouse . y ) , true ) ;
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
onToggleReactionClicked: {
if ( root . isChatBlocked )
return
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
if ( ! root . messageStore ) {
console . error ( "Reaction can not be toggled, message store is not valid" )
return
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
root . messageStore . toggleReaction ( root . messageId , emojiId )
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
onAddReactionClicked: {
if ( root . isChatBlocked )
return ;
2022-07-05 10:12:27 +00:00
d . setMessageActive ( root . messageId , true ) ;
2022-10-07 13:50:31 +00:00
root . messageClickHandler ( sender , Qt . point ( mouse . x , mouse . y ) , false , false , false , null , true , false ) ;
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
onStickerClicked: {
root . openStickerPackPopup ( root . stickerPack ) ;
}
2022-12-08 21:01:08 +00:00
onResendClicked: {
root . messageStore . resendMessage ( root . messageId )
}
2022-10-07 13:50:31 +00:00
mouseArea {
2023-01-06 15:43:54 +00:00
acceptedButtons: Qt . RightButton
2022-10-07 13:50:31 +00:00
enabled: ! root . isChatBlocked &&
! root . placeholderMessage &&
delegate . contentType !== StatusMessage . ContentType . Image
onClicked: {
d . setMessageActive ( root . messageId , true ) ;
root . messageClickHandler ( this , Qt . point ( mouse . x , mouse . y ) ,
false , false , false , null , root . isEmoji , false , false , false , "" ) ;
2022-09-15 07:31:38 +00:00
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
messageDetails: StatusMessageDetails {
contentType: delegate . contentType
messageOriginInfo: isDiscordMessage ? qsTr ( "Imported from discord" ) : ""
messageText: root . messageText
messageContent: {
switch ( delegate . contentType )
{
case StatusMessage.ContentType.Sticker:
return root . sticker ;
case StatusMessage.ContentType.Image:
return root . messageImage ;
}
if ( root . isDiscordMessage && root . messageImage != "" ) {
return root . messageImage
}
return "" ;
}
amISender: root . amISender
sender.id: root . senderIsEnsVerified ? "" : Utils . getCompressedPk ( root . senderId )
sender.displayName: root . senderDisplayName
sender.secondaryName: root . senderOptionalName
sender.isEnsVerified: root . senderIsEnsVerified
sender.isContact: root . senderIsAdded
sender.trustIndicator: root . senderTrustStatus
sender . profileImage {
width: 40
height: 40
name: root . senderIcon || ""
assetSettings.isImage: root . isDiscordMessage || root . senderIcon . startsWith ( "data" )
pubkey: root . senderId
colorId: Utils . colorIdForPubkey ( root . senderId )
2023-01-10 11:29:24 +00:00
colorHash: root . senderColorHash
2022-12-01 10:24:25 +00:00
showRing: ! root . isDiscordMessage && ! root . senderIsEnsVerified
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
replyDetails: StatusMessageDetails {
2023-01-11 21:16:35 +00:00
messageText: {
if ( root . quotedMessageDeleted ) {
return qsTr ( "Message deleted" )
}
if ( ! root . quotedMessageText ) {
return qsTr ( "Unknown message. Try fetching more messages" )
}
return root . quotedMessageText
}
2023-01-12 19:23:26 +00:00
contentType: delegate . convertContentType ( root . quotedMessageContentType )
2022-10-07 13:50:31 +00:00
messageContent: {
2023-01-12 19:23:26 +00:00
if ( contentType !== StatusMessage . ContentType . Sticker && contentType !== StatusMessage . ContentType . Image ) {
return ""
}
let message = root . messageStore . getMessageByIdAsJson ( responseToMessageWithId )
2022-10-07 13:50:31 +00:00
switch ( contentType ) {
case StatusMessage.ContentType.Sticker:
2023-01-12 19:23:26 +00:00
return message . sticker ;
2022-10-07 13:50:31 +00:00
case StatusMessage.ContentType.Image:
2023-01-12 19:23:26 +00:00
return message . messageImage ;
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
return "" ;
}
2023-01-12 19:23:26 +00:00
amISender: root . quotedMessageFrom === userProfile . pubKey
sender.id: root . quotedMessageFrom
2023-01-10 11:29:24 +00:00
sender.isContact: quotedMessageAuthorDetailsIsContact
sender.displayName: quotedMessageAuthorDetailsDisplayName
sender.isEnsVerified: quotedMessageAuthorDetailsEnsVerified
sender.secondaryName: quotedMessageAuthorDetailsName || ""
2022-10-07 13:50:31 +00:00
sender . profileImage {
width: 20
height: 20
2023-01-10 11:29:24 +00:00
name: quotedMessageAuthorDetailsThumbnailImage
assetSettings.isImage: quotedMessageAuthorDetailsThumbnailImage
2023-01-12 19:23:26 +00:00
showRing: ( root . quotedMessageContentType !== Constants . messageContentType . discordMessageType ) && ! sender . isEnsVerified
pubkey: sender . id
colorId: Utils . colorIdForPubkey ( sender . id )
2023-01-10 11:29:24 +00:00
colorHash: quotedMessageAuthorDetailsColorHash
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
}
2022-01-18 21:02:47 +00:00
2023-01-06 15:43:54 +00:00
statusChatInput: Component {
StatusChatInput {
id: editTextInput
objectName: "editMessageInput"
2022-07-05 10:12:27 +00:00
2023-01-06 15:43:54 +00:00
readonly property string messageText: editTextInput . textInput . text
2022-07-05 10:12:27 +00:00
2023-01-06 15:43:54 +00:00
// TODO: Move this property and Escape handler to StatusChatInput
property bool suggestionsOpened: false
2022-07-05 10:12:27 +00:00
2023-01-06 15:43:54 +00:00
width: parent . width
2022-07-05 10:12:27 +00:00
2023-01-06 15:43:54 +00:00
Keys.onEscapePressed: {
if ( ! suggestionsOpened ) {
delegate . editCancelled ( )
}
suggestionsOpened = false
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
2023-01-06 15:43:54 +00:00
store: root . rootStore
usersStore: root . usersStore
emojiPopup: root . emojiPopup
stickersPopup: root . stickersPopup
messageContextMenu: root . messageContextMenu
2022-07-05 10:12:27 +00:00
2023-01-12 09:28:45 +00:00
chatType: root . messageStore . chatType
2023-01-06 15:43:54 +00:00
isEdit: true
2022-10-07 13:50:31 +00:00
2023-01-06 15:43:54 +00:00
onSendMessage: {
delegate . editCompletedHandler ( editTextInput . textInput . text )
2022-10-07 13:50:31 +00:00
}
2022-07-05 10:12:27 +00:00
2023-01-06 15:43:54 +00:00
Component.onCompleted: {
parseMessage ( root . messageText ) ;
delegate . originalMessageText = editTextInput . textInput . text
}
2022-07-05 10:12:27 +00:00
}
}
2022-12-01 10:24:25 +00:00
hasLinks: linkUrlsModel . count
2022-10-07 13:50:31 +00:00
linksComponent: Component {
LinksMessageView {
linksModel: linkUrlsModel
container: root
messageStore: root . messageStore
store: root . rootStore
isCurrentUser: root . amISender
onImageClicked: {
root . imageClicked ( image ) ;
}
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
Component.onCompleted: d . unfurledLinksCount = Qt . binding ( ( ) = > unfurledLinksCount )
Component.onDestruction: d . unfurledLinksCount = 0
2022-08-31 08:32:08 +00:00
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
transcationComponent: Component {
TransactionBubbleView {
transactionParams: root . transactionParams
store: root . rootStore
contactsStore: root . contactsStore
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
invitationComponent: Component {
InvitationBubbleView {
store: root . rootStore
communityId: root . communityId
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
quickActions: [
Loader {
2023-01-12 14:32:51 +00:00
active: ! root . isInPinnedPopup && delegate . hovered
2023-01-12 09:28:45 +00:00
visible: active
2022-10-07 13:50:31 +00:00
sourceComponent: StatusFlatRoundButton {
width: d . chatButtonSize
height: d . chatButtonSize
icon.name: "reaction-b"
type: StatusFlatRoundButton . Type . Tertiary
tooltip.text: qsTr ( "Add reaction" )
onClicked: {
d . setMessageActive ( root . messageId , true )
2023-01-16 11:13:33 +00:00
root . messageClickHandler ( delegate , mapToItem ( delegate , mouse . x , mouse . y ) , false , false , false , null , true , false )
2022-07-05 10:12:27 +00:00
}
}
2022-10-07 13:50:31 +00:00
} ,
Loader {
2023-01-12 14:32:51 +00:00
active: ! root . isInPinnedPopup && delegate . hovered
2023-01-12 09:28:45 +00:00
visible: active
2022-10-07 13:50:31 +00:00
sourceComponent: StatusFlatRoundButton {
objectName: "replyToMessageButton"
width: d . chatButtonSize
height: d . chatButtonSize
icon.name: "reply"
type: StatusFlatRoundButton . Type . Tertiary
tooltip.text: qsTr ( "Reply" )
onClicked: {
root . showReplyArea ( root . messageId , root . senderId )
if ( messageContextMenu . closeParentPopup ) {
messageContextMenu . closeParentPopup ( )
}
}
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
} ,
Loader {
2023-01-12 14:32:51 +00:00
active: ! root . isInPinnedPopup && root . isText && ! root . editModeOn && root . amISender && delegate . hovered
2022-10-07 13:50:31 +00:00
visible: active
sourceComponent: StatusFlatRoundButton {
objectName: "editMessageButton"
width: d . chatButtonSize
height: d . chatButtonSize
icon.name: "edit_pencil"
type: StatusFlatRoundButton . Type . Tertiary
tooltip.text: qsTr ( "Edit" )
onClicked: {
root . messageStore . setEditModeOn ( root . messageId )
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
}
} ,
Loader {
active: {
2023-01-12 14:32:51 +00:00
if ( ! delegate . hovered )
return false ;
2022-10-07 13:50:31 +00:00
if ( ! root . messageStore )
return false
2022-07-05 10:12:27 +00:00
2023-01-12 09:28:45 +00:00
const chatType = root . messageStore . chatType ;
const pinMessageAllowedForMembers = root . messageStore . isPinMessageAllowedForMembers
2022-07-05 10:12:27 +00:00
2022-10-07 13:50:31 +00:00
return chatType === Constants . chatType . oneToOne ||
2022-12-26 16:25:16 +00:00
chatType === Constants . chatType . privateGroupChat && root . amIChatAdmin ||
chatType === Constants . chatType . communityChat && ( root . amIChatAdmin || pinMessageAllowedForMembers ) ;
2022-07-05 10:12:27 +00:00
}
2023-01-12 09:28:45 +00:00
visible: active
2022-10-07 13:50:31 +00:00
sourceComponent: StatusFlatRoundButton {
objectName: "MessageView_toggleMessagePin"
width: d . chatButtonSize
height: d . chatButtonSize
icon.name: root . pinnedMessage ? "unpin" : "pin"
type: StatusFlatRoundButton . Type . Tertiary
tooltip.text: root . pinnedMessage ? qsTr ( "Unpin" ) : qsTr ( "Pin" )
onClicked: {
if ( root . pinnedMessage ) {
messageStore . unpinMessage ( root . messageId )
return ;
}
if ( ! ! root . messageStore && root . messageStore . getNumberOfPinnedMessages ( ) < Constants . maxNumberOfPins ) {
messageStore . pinMessage ( root . messageId )
return ;
}
if ( ! chatContentModule ) {
console . warn ( "error on open pinned messages limit reached from message context menu - chat content module is not set" )
return ;
}
Global . openPopup ( Global . pinnedMessagesPopup , {
store: root . rootStore ,
messageStore: messageStore ,
pinnedMessagesModel: chatContentModule . pinnedMessagesModel ,
messageToPin: root . messageId
} ) ;
2022-07-05 10:12:27 +00:00
}
2022-10-07 13:50:31 +00:00
}
} ,
Loader {
active: {
2023-01-12 14:32:51 +00:00
if ( ! delegate . hovered )
return false ;
2022-10-07 13:50:31 +00:00
if ( root . isInPinnedPopup )
return false ;
if ( ! root . messageStore )
return false ;
2022-12-26 16:25:16 +00:00
return ( root . amISender || root . amIChatAdmin ) &&
2022-10-07 13:50:31 +00:00
( messageContentType === Constants . messageContentType . messageType ||
messageContentType === Constants . messageContentType . stickerType ||
messageContentType === Constants . messageContentType . emojiType ||
messageContentType === Constants . messageContentType . imageType ||
messageContentType === Constants . messageContentType . audioType ) ;
}
2023-01-12 09:28:45 +00:00
visible: active
2022-10-07 13:50:31 +00:00
sourceComponent: StatusFlatRoundButton {
objectName: "chatDeleteMessageButton"
width: d . chatButtonSize
height: d . chatButtonSize
icon.name: "delete"
type: StatusFlatRoundButton . Type . Tertiary
tooltip.text: qsTr ( "Delete" )
onClicked: {
if ( ! localAccountSensitiveSettings . showDeleteMessageWarning ) {
messageStore . deleteMessage ( root . messageId )
}
else {
Global . openPopup ( deleteMessageConfirmationDialogComponent )
}
2022-07-05 10:12:27 +00:00
}
}
}
2022-10-07 13:50:31 +00:00
]
}
2022-07-05 10:12:27 +00:00
}
}
Component {
id: deleteMessageConfirmationDialogComponent
ConfirmationDialog {
2022-08-25 08:27:20 +00:00
confirmButtonObjectName: "chatButtonsPanelConfirmDeleteMessageButton"
2022-10-18 21:59:46 +00:00
header.title: qsTr ( "Confirm deleting this message" )
confirmationText: qsTr ( "Are you sure you want to delete this message? Be aware that other clients are not guaranteed to delete the message as well." )
2022-07-05 10:12:27 +00:00
height: 260
checkbox.visible: true
executeConfirm: function ( ) {
if ( checkbox . checked ) {
localAccountSensitiveSettings . showDeleteMessageWarning = false
}
close ( )
messageStore . deleteMessage ( root . messageId )
}
onClosed: {
destroy ( )
}
2020-07-10 15:37:23 +00:00
}
2020-05-27 22:59:17 +00:00
}
2022-12-05 07:52:41 +00:00
Component {
id: newMessagesMarkerComponent
NewMessagesMarker {
count: root . messageStore . newMessagesCount
timestamp: root . messageTimestamp
}
}
2020-05-27 22:59:17 +00:00
}