Update to latest version to see a nice image here!
") - - message: Utils.removeGifUrls(root.message) - anchors.top: parent.top - anchors.topMargin: isEmoji ? 2 : 0 - anchors.left: parent.left - anchors.right: parent.right - textField.rightPadding: Style.current.bigPadding - onLinkActivated: { - if (activityCenterMessage) { - root.clickMessage(false, isSticker, false, null, false, false, false, false, "") - } - } - } - - Loader { - id: chatImageContent - active: isImage - anchors.top: chatText.visible ? chatText.bottom : parent.top - anchors.topMargin: active ? 6 : 0 - z: 51 - sourceComponent: Component { - StatusChatImage { - playing: root.messageStore.playAnimation - imageSource: messageImage - imageWidth: 200 - isActiveChannel: root.isActiveChannel - onClicked: { - if (mouse.button === Qt.LeftButton) { - root.imageClicked(image) - } - else if (mouse.button === Qt.RightButton) { - // Set parent, X & Y positions for the messageContextMenu - root.messageContextMenu.parent = root - - const mappedPos = root.mapFromItem( - this, - mouse.x + Style.current.smallPadding, - mouse.y - Style.current.smallPadding) - - root.messageContextMenu.setXPosition = function() { return mappedPos.x } - root.messageContextMenu.setYPosition = function() { return mappedPos.y } - root.clickMessage(false, false, true, image, false, true, false, true, imageSource) - } - } - container: root.container - } - } - } - - Loader { - id: stickerLoader - active: contentType === Constants.messageContentType.stickerType - anchors.top: parent.top - anchors.topMargin: active ? Style.current.halfPadding : 0 - sourceComponent: Component { - Rectangle { - id: stickerContainer - color: Style.current.transparent - border.color: isHovered ? Qt.darker(Style.current.border, 1.1) : Style.current.border - border.width: 1 - radius: 16 - width: stickerId.width + 2 * chatVerticalPadding - height: stickerId.height + 2 * chatVerticalPadding - - StatusSticker { - id: stickerId - anchors.top: parent.top - anchors.topMargin: chatVerticalPadding - anchors.left: parent.left - anchors.leftMargin: chatVerticalPadding - contentType: root.contentType - stickerData: root.sticker - onLoaded: { - if(!root.messageStore) - return - // Not refactored yet - // root.messageStore.scrollToBottom(true, root.container) - } - } - } - } - } - - MessageMouseArea { - id: messageMouseArea - anchors.fill: stickerLoader.active ? stickerLoader : chatText - z: activityCenterMessage ? chatText.z + 1 : chatText.z -1 - enabled: !root.isChatBlocked && !placeholderMessage - messageContextMenu: root.messageContextMenu - messageContextMenuParent: root - isHovered: root.isHovered - isMessageActive: root.isMessageActive - isActivityCenterMessage: activityCenterMessage - stickersLoaded: root.stickersLoaded - onClickMessage: { - root.clickMessage(isProfileClick, isSticker, isImage, null, false, false, false, false, ""); - } - onOpenStickerPackPopup: { - root.openStickerPackPopup(root.stickerPack); - } - - onSetMessageActive: { - root.setMessageActive(messageId, active); - } - } - - Loader { - id: linksLoader - active: !!linkUrls - height: item ? item.height : 0 - anchors.top: chatText.bottom - anchors.topMargin: active ? Style.current.halfPadding : 0 - - sourceComponent: Component { - LinksMessageView { - linkUrls: root.linkUrls - container: root.container - messageStore: root.messageStore - store: root.store - isCurrentUser: root.amISender - } - } - } - - Loader { - id: audioPlayerLoader - active: isAudio - anchors.top: parent.top - anchors.topMargin: active ? Style.current.halfPadding : 0 - - sourceComponent: Component { - AudioPlayerPanel { - audioSource: audio - } - } - } - - Loader { - id: transactionBubbleLoader - active: contentType === Constants.messageContentType.transactionType - anchors.top: parent.top - anchors.topMargin: active ? (chatName.visible ? 4 : 6) : 0 - sourceComponent: Component { - TransactionBubbleView { - transactionParams: root.transactionParams - store: root.store - contactsStore: root.contactsStore - } - } - } - - Loader { - active: contentType === Constants.messageContentType.communityInviteType - anchors.left: parent.left - anchors.top: parent.top - anchors.topMargin: active ? 8 : 0 - sourceComponent: Component { - id: invitationBubble - InvitationBubbleView { - store: root.store - communityId: root.communityId - } - } - } - } - - - Retry { - id: retry - height: visible ? implicitHeight : 0 - anchors.left: chatTime.visible ? chatTime.right : messageContent.left - anchors.leftMargin: chatTime.visible ? chatHorizontalPadding : 0 - anchors.top: chatTime.visible ? chatTime.top : messageContent.bottom - anchors.topMargin: chatTime.visible ? 0 : -4 - anchors.bottom: chatTime.visible ? chatTime.bottom : undefined - isCurrentUser: root.amISender - isExpired: isExpired - timeout: timeout - onClicked: { - // Not Refactored Yet -// rootStore.chatsModelInst.messageView.resendMessage(chatId, messageId) - } - } - } - - Loader { - active: !activityCenterMessage && (hasMention || pinnedMessage) - height: messageContainer.height - anchors.left: messageContainer.left - anchors.top: messageContainer.top - - sourceComponent: Component { - Rectangle { - id: mentionBorder - color: pinnedMessage ? Style.current.pinnedMessageBorder : Style.current.mentionColor - width: 2 - height: parent.height - } - } - } - - HoverHandler { - enabled: !activityCenterMessage && !chatLogView.flickingVertically && - (forceHoverHandler || (typeof root.messageContextMenu !== "undefined" && typeof Global.profilePopupOpened !== "undefined" && - !root.messageContextMenu.opened && !Global.profilePopupOpened && !Global.popupOpened)) - onHoveredChanged: { - setHovered(messageId, hovered); - } - } - - Loader { - id: emojiReactionLoader - active: reactionsModel.count > 0 - anchors.bottom: messageContainer.bottom - anchors.bottomMargin: Style.current.halfPadding - anchors.left: messageContainer.left - anchors.leftMargin: messageContainer.messageContent.anchors.leftMargin - - sourceComponent: Component { - EmojiReactionsPanel { - id: emojiRect - store: root.messageStore - emojiReactionsModel: reactionsModel - onHoverChanged: { - setHovered(messageId, hovered) - } - isMessageActive: isMessageActive - isCurrentUser: root.amISender - onAddEmojiClicked: { - if(root.isChatBlocked) - return - - // First set parent, X & Y positions for the messageContextMenu - root.messageContextMenu.parent = emojiRect - root.messageContextMenu.setXPosition = function() { return (root.messageContextMenu.parent.x + root.messageContextMenu.parent.width + 4) } - root.messageContextMenu.setYPosition = function() { return (-root.messageContextMenu.height - 4) } - - // Second, add emoji that also triggers setXYPosition methods / open popup: - root.addEmoji(false, false, false, null, true, false); - } - - onToggleReaction: { - if(root.isChatBlocked) - return - - if(!root.messageStore) - { - console.error("reaction cannot be toggled, message store is not valid") - return - } - - root.messageStore.toggleReaction(messageId, emojiID) - } - - onSetMessageActive: { - root.setMessageActive(messageId, active); - } - } - } - } -} diff --git a/ui/imports/shared/views/chat/MessageContextMenuView.qml b/ui/imports/shared/views/chat/MessageContextMenuView.qml index d8a327f46c..580301ef9a 100644 --- a/ui/imports/shared/views/chat/MessageContextMenuView.qml +++ b/ui/imports/shared/views/chat/MessageContextMenuView.qml @@ -102,9 +102,6 @@ StatusPopupMenu { readonly property bool userTrustIsUnknown: d.contactDetails && d.contactDetails.trustStatus === Constants.trustStatus.unknown readonly property bool userIsUntrustworthy: d.contactDetails && d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy - property var setXPosition: function() {return 0} - property var setYPosition: function() {return 0} - property var emojiReactionsReactedByUser: [] signal openProfileClicked(string publicKey, string state) @@ -142,14 +139,6 @@ StatusPopupMenu { d.contactDetails = {} } - onHeightChanged: { root.y = setYPosition(); } - onWidthChanged: { root.x = setXPosition(); } - onOpened: { - // Trigger x and y position: - x = setXPosition() - y = setYPosition() - } - width: Math.max(emojiContainer.visible ? emojiContainer.width : 0, (root.isRightClickOnImage && !root.pinnedPopup) ? 176 : 230) @@ -202,7 +191,8 @@ StatusPopupMenu { displayName: root.selectedUserDisplayName pubkey: root.selectedUserPublicKey icon: root.selectedUserIcon - trustStatus: d.contactDetails.trustStatus + trustStatus: d.contactDetails && d.contactDetails.trustStatus ? d.contactDetails.trustStatus + : Constants.trustStatus.unknown isContact: root.isMyMutualContact isCurrentUser: root.isMe } diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index 434b6fb0f3..9d432b0161 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -1,38 +1,24 @@ import QtQuick 2.13 -import StatusQ.Components 0.1 - import utils 1.0 import shared.panels 1.0 import shared.status 1.0 import shared.controls 1.0 +import shared.popups 1.0 import shared.panels.chat 1.0 import shared.views.chat 1.0 import shared.controls.chat 1.0 +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 + Loader { id: root - width: parent.width - z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index) - - sourceComponent: { - switch(contentType) { - case Constants.messageContentType.chatIdentifier: - return channelIdentifierComponent - case Constants.messageContentType.fetchMoreMessagesButton: - return fetchMoreMessagesButtonComponent - case Constants.messageContentType.systemMessagePrivateGroupType: - return privateGroupHeaderComponent - case Constants.messageContentType.gapType: - return gapComponent - default: - return compactMessageComponent - - } - } - - property var store + property var rootStore property var messageStore property var usersStore property var contactsStore @@ -54,30 +40,37 @@ Loader { property string senderId: "" property string senderDisplayName: "" property string senderLocalName: "" + property string senderEnsName: "" property string senderIcon: "" property bool amISender: false property bool senderIsAdded: false property int senderTrustStatus: Constants.trustStatus.unknown readonly property string senderIconToShow: { if ((!senderIsAdded && - Global.privacyModuleInst.profilePicturesVisibility !== - Constants.profilePicturesVisibility.everyone)) { + Global.privacyModuleInst.profilePicturesVisibility !== + Constants.profilePicturesVisibility.everyone)) { return "" } return senderIcon } - property string message: "" + property string messageText: "" property string messageImage: "" - property string messageTimestamp: "" + property double messageTimestamp: 0 // We use double, because QML's int is too small property string messageOutgoingStatus: "" property int messageContentType: 1 property bool pinnedMessage: false property string messagePinnedBy: "" property var reactionsModel: [] property string linkUrls: "" - property bool isInPinnedPopup: false // The pinned popup limits the number of buttons shown property var transactionParams + // 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 + property bool activityCenterMessage: false + property bool activityCenterMessageRead: true + property int gapFrom: 0 property int gapTo: 0 @@ -86,94 +79,63 @@ Loader { property int nextMessageIndex: -1 property var nextMessageAsJsonObj - property string hoveredMessage - property string activeMessage - property bool isHovered: typeof hoveredMessage !== "undefined" && hoveredMessage === messageId - property bool isMessageActive: typeof activeMessage !== "undefined" && activeMessage === messageId - property bool editModeOn: false + property bool isEdited: false - function setHovered(messageId, hovered) { - if (hovered) { - hoveredMessage = messageId; - } else if (hoveredMessage === messageId) { - hoveredMessage = ""; - } - } + property string responseTo: responseToMessageWithId // Legacy - property string responseTo: responseToMessageWithId property bool isCurrentUser: amISender - property int contentType: messageContentType - property string timestamp: messageTimestamp property string displayUserName: senderDisplayName property string outgoingStatus: messageOutgoingStatus property string authorCurrentMsg: senderId property string authorPrevMsg: { if(!prevMessageAsJsonObj || - // The system message for private groups appear as created by the group host, but it shouldn't - prevMessageAsJsonObj.contentType === Constants.messageContentType.systemMessagePrivateGroupType) { + // The system message for private groups appear as created by the group host, but it shouldn't + prevMessageAsJsonObj.contentType === Constants.messageContentType.systemMessagePrivateGroupType) { return "" } return prevMessageAsJsonObj.senderId } - property string prevMsgTimestamp: { - if(!prevMessageAsJsonObj) - return "" + property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0 + property double nextMsgTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.timestamp : 0 - return prevMessageAsJsonObj.timestamp - } - property string nextMsgTimestamp: { - if(!nextMessageAsJsonObj) - return "" + property bool shouldRepeatHeader: ((messageTimestamp - prevMsgTimestamp) / 60 / 1000) > Constants.repeatHeaderInterval - return nextMessageAsJsonObj.timestamp - } - - property bool shouldRepeatHeader: ((parseInt(timestamp, 10) - parseInt(prevMsgTimestamp, 10)) / 60 / 1000) > Constants.repeatHeaderInterval - - ////////////////////////////////////// - //TODO CHECCK - REMOVE - property string plainText: "That's right. We're friends... Of justice, that is." - property string emojiReactions: "" - property bool timeout: false property bool hasMention: false - property bool placeholderMessage: false - property bool activityCenterMessage: false - property bool read: true - property bool forceHoverHandler: false // Used to force the HoverHandler to be active (useful for messages in popups) - property string replaces: "" - property bool isEdited: false property bool stickersLoaded: false - ////////////////////////////////////// + property string sticker: "Qme8vJtyrEHxABcSVGPF95PtozDgUyfr1xGjePmFdZgk9v" property int stickerPack: -1 - property bool isEmoji: contentType === Constants.messageContentType.emojiType - property bool isImage: contentType === Constants.messageContentType.imageType - property bool isAudio: contentType === Constants.messageContentType.audioType - property bool isStatusMessage: contentType === Constants.messageContentType.systemMessagePrivateGroupType - property bool isSticker: contentType === Constants.messageContentType.stickerType - property bool isText: contentType === Constants.messageContentType.messageType || contentType === Constants.messageContentType.editType - property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio - || contentType === Constants.messageContentType.communityInviteType || contentType === Constants.messageContentType.transactionType - property bool isExpired: (outgoingStatus === "sending" && (Math.floor(timestamp) + 180000) < Date.now()) + property bool isEmoji: messageContentType === Constants.messageContentType.emojiType + property bool isImage: messageContentType === Constants.messageContentType.imageType + property bool isAudio: messageContentType === Constants.messageContentType.audioType + property bool isStatusMessage: messageContentType === Constants.messageContentType.systemMessagePrivateGroupType + property bool isSticker: messageContentType === Constants.messageContentType.stickerType + property bool isText: messageContentType === Constants.messageContentType.messageType || messageContentType === Constants.messageContentType.editType + property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio + || messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType + + property bool isExpired: (outgoingStatus === "sending" && (Math.floor(messageTimestamp) + 180000) < Date.now()) property int statusAgeEpoch: 0 signal imageClicked(var image) property var scrollToBottom: function () {} - property var clickMessage: function(isProfileClick, - isSticker = false, - isImage = false, - image = null, - isEmoji = false, - hideEmojiPicker = false, - isReply = false, - isRightClickOnImage = false, - imageSource = "") { + // 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 = "") { if (placeholderMessage || activityCenterMessage) { return @@ -188,7 +150,7 @@ Loader { messageContextMenu.messageSenderId = root.senderId messageContextMenu.messageContentType = root.messageContentType messageContextMenu.pinnedMessage = root.pinnedMessage - messageContextMenu.canPin = messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins + messageContextMenu.canPin = d.canPin messageContextMenu.selectedUserPublicKey = root.senderId messageContextMenu.selectedUserDisplayName = root.senderDisplayName @@ -202,7 +164,7 @@ Loader { messageContextMenu.isSticker = isSticker messageContextMenu.hideEmojiPicker = hideEmojiPicker - if(isReply){ + if (isReply){ let obj = messageStore.getMessageByIdAsJson(responseTo) if(!obj) return @@ -213,15 +175,16 @@ Loader { messageContextMenu.selectedUserIcon = obj.senderIcon } - messageContextMenu.popup() + messageContextMenu.parent = sender; + messageContextMenu.popup(point); } signal showReplyArea(string messageId, string author) -// function showReactionAuthors(fromAccounts, emojiId) { -// return root.rootStore.showReactionAuthors(fromAccounts, emojiId) -// } + // function showReactionAuthors(fromAccounts, emojiId) { + // return root.rootStore.showReactionAuthors(fromAccounts, emojiId) + // } function startMessageFoundAnimation() { root.item.startMessageFoundAnimation(); @@ -231,23 +194,76 @@ Loader { signal openStickerPackPopup(string stickerPackId) // Not Refactored Yet -// Connections { -// enabled: (!placeholderMessage && !!root.rootStore) -// target: !!root.rootStore ? root.rootStore.allContacts : null -// onContactChanged: { -// if (pubkey === fromAuthor) { -// const img = appMain.getProfileImage(userPubKey, isCurrentUser, useLargeImage) -// if (img) { -// profileImageSource = img -// } -// } else if (replyMessageIndex > -1 && pubkey === repliedMessageAuthorPubkey) { -// const imgReply = appMain.getProfileImage(repliedMessageAuthorPubkey, repliedMessageAuthorIsCurrentUser, false) -// if (imgReply) { -// repliedMessageUserImage = imgReply -// } -// } -// } -// } + // Connections { + // enabled: (!placeholderMessage && !!root.rootStore) + // target: !!root.rootStore ? root.rootStore.allContacts : null + // onContactChanged: { + // if (pubkey === fromAuthor) { + // const img = appMain.getProfileImage(userPubKey, isCurrentUser, useLargeImage) + // if (img) { + // profileImageSource = img + // } + // } else if (replyMessageIndex > -1 && pubkey === repliedMessageAuthorPubkey) { + // const imgReply = appMain.getProfileImage(repliedMessageAuthorPubkey, repliedMessageAuthorIsCurrentUser, false) + // if (imgReply) { + // repliedMessageUserImage = imgReply + // } + // } + // } + // } + + z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index) + + 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 + default: + return messageComponent + } + } + + QtObject { + id: d + + readonly property bool canPin: !!messageStore && + messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins + readonly property int chatButtonSize: 32 + + property string activeMessage + readonly property bool isMessageActive: typeof activeMessage !== "undefined" && activeMessage === messageId + + 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; + } + } + } + + Connections { + enabled: d.isMessageActive + target: root.messageContextMenu + onClosed: { + d.setMessageActive(root.messageId, false) + } + } Component { id: gapComponent @@ -293,18 +309,18 @@ Loader { wrapMode: Text.Wrap text: { return ``+ - ``+ - ``+ - ``+ - ``+ - `${message}`+ - ``+ - ``; + `}`+ + ``+ + ``+ + ``+ + `${messageText}`+ + ``+ + `