From 0f0b239f2ac31485ba3c3c8df3d8edac0242f987 Mon Sep 17 00:00:00 2001 From: Alexandra Betouni Date: Fri, 16 Jul 2021 18:02:47 +0300 Subject: [PATCH] [#2386] Fixed inline emoji reaction menu not showing on top of message * Also replaced usage of synamic scoping with properties, signals and functions where possible Closes #2386 --- ui/app/AppLayouts/Chat/ChatColumn.qml | 865 +++++++++--------- .../Chat/ChatColumn/ChatMessages.qml | 5 +- ui/app/AppLayouts/Chat/ChatColumn/Message.qml | 54 +- .../MessageComponents/ChatButtons.qml | 7 +- .../MessageComponents/CompactMessage.qml | 11 +- .../MessageComponents/EmojiReactions.qml | 9 +- .../Chat/components/MessageContextMenu.qml | 39 +- 7 files changed, 486 insertions(+), 504 deletions(-) diff --git a/ui/app/AppLayouts/Chat/ChatColumn.qml b/ui/app/AppLayouts/Chat/ChatColumn.qml index 03a070f93e..ffdbe0530c 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn.qml @@ -18,40 +18,38 @@ import "./ChatColumn/ChatComponents" import "./data" import "../Wallet" -StackLayout { + +Item { id: chatColumnLayout + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumWidth: 300 property alias pinnedMessagesPopupComponent: pinnedMessagesPopupComponent - property int chatGroupsListViewCount: 0 - property bool isReply: false property bool isImage: false - property bool isExtendedInput: isReply || isImage - property bool isConnected: false property string contactToRemove: "" - property bool showUsers: false - property var doNotShowAddToContactBannerToThose: ([]) - - property var onActivated: function () { - chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason) - } - property string activeChatId: chatsModel.channelView.activeChannel.id property bool isBlocked: profileModel.contacts.isContactBlocked(activeChatId) property bool isContact: profileModel.contacts.isAdded(activeChatId) property bool contactRequestReceived: profileModel.contacts.contactRequestReceived(activeChatId) - property string currentNotificationChatId property string currentNotificationCommunityId property alias input: chatInput - property string hoveredMessage property string activeMessage + property var currentTime: 0 + property var idMap: ({}) + property var suggestionsObj: ([]) + property Timer timer: Timer { } + property var onActivated: function () { + chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason) + } function setHovered(messageId, hovered) { if (hovered) { @@ -69,47 +67,6 @@ StackLayout { } } - Component.onCompleted: { - chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason) - } - - property var currentTime: 0 - - Timer { - interval: 60000; // 1 min - running: true; - repeat: true - triggeredOnStart: true - onTriggered: { - chatColumnLayout.currentTime = Date.now() - } - } - - Layout.fillHeight: true - Layout.fillWidth: true - Layout.minimumWidth: 300 - - currentIndex: chatsModel.channelView.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1 - - Component { - id: pinnedMessagesPopupComponent - PinnedMessagesPopup { - id: pinnedMessagesPopup - onClosed: destroy() - } - } - - MessageContextMenu { - id: messageContextMenu - } - - StatusImageModal { - id: imagePopup - } - - property var idMap: ({}) - property var suggestionsObj: ([]) - function addSuggestionFromMessageList(i){ const contactAddr = chatsModel.messageView.messageList.getMessageData(i, "publicKey"); if(idMap[contactAddr]) return; @@ -155,7 +112,6 @@ StackLayout { isImage = false; let replyMessageIndex = chatsModel.messageView.messageList.getMessageIndex(SelectedMessage.messageId); if (replyMessageIndex === -1) return; - let userName = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "userName") let message = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "message") let identicon = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "identicon") @@ -180,25 +136,6 @@ StackLayout { txModalLoader.close() } - Connections { - target: profileModel.contacts - onContactListChanged: { - isBlocked = profileModel.contacts.isContactBlocked(activeChatId); - } - onContactBlocked: { - chatsModel.messageView.removeMessagesByUserId(publicKey) - } - } - - Connections { - target: chatsModel.channelView - onActiveChannelChanged: { - chatsModel.messageView.hideLoadingIndicator() - SelectedMessage.reset(); - chatColumn.isReply = false; - } - } - function clickOnNotification() { // So far we're just showing this app as the top most window. Once we decide about the way // how to notify the app what channle should be displayed within the app when user clicks @@ -210,34 +147,41 @@ StackLayout { applicationWindow.requestActivate() } - Connections { - target: systemTray - onMessageClicked: function () { - clickOnNotification() - } - } - - Timer { - id: timer - } - function positionAtMessage(messageId) { stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].item.scrollToMessage(messageId) } - - ColumnLayout { - spacing: 0 - StatusChatToolBar { - id: topBar - Layout.fillWidth: true + Timer { + interval: 60000; // 1 min + running: true + repeat: true + triggeredOnStart: true + onTriggered: { + chatColumnLayout.currentTime = Date.now() + } + } - property string chatId: chatsModel.channelView.activeChannel.id - property string profileImage: appMain.getProfileImage(chatId) || "" + StackLayout { + anchors.fill: parent + currentIndex: chatsModel.channelView.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1 - chatInfoButton.title: Utils.removeStatusEns(chatsModel.channelView.activeChannel.name) - chatInfoButton.subTitle: { - switch (chatsModel.channelView.activeChannel.chatType) { + StatusImageModal { + id: imagePopup + } + + ColumnLayout { + spacing: 0 + + StatusChatToolBar { + id: topBar + Layout.fillWidth: true + + property string chatId: chatsModel.channelView.activeChannel.id + property string profileImage: appMain.getProfileImage(chatId) || "" + + chatInfoButton.title: Utils.removeStatusEns(chatsModel.channelView.activeChannel.name) + chatInfoButton.subTitle: { + switch (chatsModel.channelView.activeChannel.chatType) { case Constants.chatTypeOneToOne: return (profileModel.contacts.isAdded(topBar.chatId) ? profileModel.contacts.contactRequestReceived(topBar.chatId) ? @@ -259,113 +203,125 @@ StackLayout { case Constants.chatTypeCommunity: default: return "" + } } - } - chatInfoButton.image.source: profileImage || chatsModel.channelView.activeChannel.identicon - chatInfoButton.image.isIdenticon: !!!profileImage && chatsModel.channelView.activeChannel.identicon - chatInfoButton.icon.color: chatsModel.channelView.activeChannel.color - chatInfoButton.type: chatsModel.channelView.activeChannel.chatType - chatInfoButton.pinnedMessagesCount: chatsModel.messageView.pinnedMessagesList.count - chatInfoButton.muted: chatsModel.channelView.activeChannel.muted + chatInfoButton.image.source: profileImage || chatsModel.channelView.activeChannel.identicon + chatInfoButton.image.isIdenticon: !!!profileImage && chatsModel.channelView.activeChannel.identicon + chatInfoButton.icon.color: chatsModel.channelView.activeChannel.color + chatInfoButton.type: chatsModel.channelView.activeChannel.chatType + chatInfoButton.pinnedMessagesCount: chatsModel.messageView.pinnedMessagesList.count + chatInfoButton.muted: chatsModel.channelView.activeChannel.muted - chatInfoButton.onPinnedMessagesCountClicked: openPopup(pinnedMessagesPopupComponent) - chatInfoButton.onUnmute: chatsModel.channelView.unmuteChatItem(chatsModel.channelView.activeChannel.id) + chatInfoButton.onPinnedMessagesCountClicked: openPopup(pinnedMessagesPopupComponent) + chatInfoButton.onUnmute: chatsModel.channelView.unmuteChatItem(chatsModel.channelView.activeChannel.id) - chatInfoButton.sensor.enabled: chatsModel.channelView.activeChannel.chatType !== Constants.chatTypePublic && - chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeCommunity - chatInfoButton.onClicked: { - switch (chatsModel.channelView.activeChannel.chatType) { + chatInfoButton.sensor.enabled: chatsModel.channelView.activeChannel.chatType !== Constants.chatTypePublic && + chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeCommunity + chatInfoButton.onClicked: { + switch (chatsModel.channelView.activeChannel.chatType) { case Constants.chatTypePrivateGroupChat: openPopup(groupInfoPopupComponent, {channelType: GroupInfoPopup.ChannelType.ActiveChannel}) break; case Constants.chatTypeOneToOne: openProfilePopup(chatsModel.userNameOrAlias(chatsModel.channelView.activeChannel.id), - chatsModel.channelView.activeChannel.id, profileImage || chatsModel.channelView.activeChannel.identicon, - "", chatsModel.channelView.activeChannel.nickname) + chatsModel.channelView.activeChannel.id, profileImage || chatsModel.channelView.activeChannel.identicon, + "", chatsModel.channelView.activeChannel.nickname) break; + } } - } - membersButton.visible: appSettings.showOnlineUsers && chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne - notificationButton.visible: appSettings.isActivityCenterEnabled - notificationCount: chatsModel.activityNotificationList.unreadCount + membersButton.visible: appSettings.showOnlineUsers && chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne + notificationButton.visible: appSettings.isActivityCenterEnabled + notificationCount: chatsModel.activityNotificationList.unreadCount - onSearchButtonClicked: searchPopup.open() - SearchPopup { - id: searchPopup - } - - onMembersButtonClicked: showUsers = !showUsers - onNotificationButtonClicked: activityCenter.open() - - popupMenu: ChatContextMenu { - openHandler: { - chatItem = chatsModel.channelView.activeChannel + onSearchButtonClicked: searchPopup.open() + SearchPopup { + id: searchPopup } - } - } - Rectangle { - Component.onCompleted: { - isConnected = chatsModel.isOnline - if(!isConnected){ - connectedStatusRect.visible = true - } - } + onMembersButtonClicked: showUsers = !showUsers + onNotificationButtonClicked: activityCenter.open() - id: connectedStatusRect - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - z: 60 - height: 40 - color: isConnected ? Style.current.green : Style.current.darkGrey - visible: false - Text { - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - color: Style.current.white - id: connectedStatusLbl - text: isConnected ? - //% "Connected" - qsTrId("connected") : - //% "Disconnected" - qsTrId("disconnected") - } - - Connections { - target: chatsModel - onOnlineStatusChanged: { - if (connected == isConnected) return; - isConnected = connected; - if(isConnected){ - timer.setTimeout(function(){ - connectedStatusRect.visible = false; - }, 5000); - } else { - connectedStatusRect.visible = true; + popupMenu: ChatContextMenu { + openHandler: { + chatItem = chatsModel.channelView.activeChannel } } } - } - AddToContactBanner { - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true - } + Rectangle { + id: connectedStatusRect + Layout.fillWidth: true + height: 40 + Layout.alignment: Qt.AlignHCenter + z: 60 + visible: false + color: isConnected ? Style.current.green : Style.current.darkGrey + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: Style.current.white + id: connectedStatusLbl + text: isConnected ? + //% "Connected" + qsTrId("connected") : + //% "Disconnected" + qsTrId("disconnected") + } - StackLayout { - id: stackLayoutChatMessages - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - Repeater { - model: chatsModel.messageView - Loader { - active: false - sourceComponent: ChatMessages { - id: chatMessages - messageList: model.messages - currentTime: chatColumnLayout.currentTime + Connections { + target: chatsModel + onOnlineStatusChanged: { + if (connected == isConnected) return; + isConnected = connected; + if(isConnected){ + timer.setTimeout(function(){ + connectedStatusRect.visible = false; + }, 5000); + } else { + connectedStatusRect.visible = true; + } + } + } + Component.onCompleted: { + isConnected = chatsModel.isOnline + if(!isConnected){ + connectedStatusRect.visible = true + } + } + } + + AddToContactBanner { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + } + + StackLayout { + id: stackLayoutChatMessages + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + Repeater { + model: chatsModel.messageView + Loader { + active: false + sourceComponent: ChatMessages { + messageList: model.messages + currentTime: chatColumnLayout.currentTime + messageContextMenuInst: MessageContextMenu { + reactionModel: EmojiReactions { } + } + } + } + } + + Connections { + target: chatsModel.channelView + onActiveChannelChanged: { + stackLayoutChatMessages.currentIndex = chatsModel.messageView.getMessageListIndex(chatsModel.channelView.activeChannelIndex) + if(stackLayoutChatMessages.currentIndex > -1 && !stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active){ + stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active = true; + } } } } @@ -373,293 +329,320 @@ StackLayout { Connections { target: chatsModel.channelView onActiveChannelChanged: { - stackLayoutChatMessages.currentIndex = chatsModel.messageView.getMessageListIndex(chatsModel.channelView.activeChannelIndex) - if(stackLayoutChatMessages.currentIndex > -1 && !stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active){ - stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active = true; + chatInput.suggestions.hide(); + chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason) + populateSuggestions(); + } + } + + Connections { + target: chatsModel.messageView + onMessagePushed: { + addSuggestionFromMessageList(messageIndex); + } + } + + Connections { + target: profileModel + onContactsChanged: { + populateSuggestions(); + } + } + + ChatRequestMessage { + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + Layout.fillWidth: true + Layout.bottomMargin: Style.current.bigPadding + } + + Item { + id: inputArea + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + Layout.fillWidth: true + Layout.preferredWidth: parent.width + height: chatInput.height + Layout.preferredHeight: height + + Connections { + target: chatsModel.messageView + onLoadingMessagesChanged: + if(value){ + loadingMessagesIndicator.active = true + } else { + timer.setTimeout(function(){ + loadingMessagesIndicator.active = false; + }, 5000); + } + } + + Loader { + id: loadingMessagesIndicator + active: chatsModel.messageView.loadingMessages + sourceComponent: loadingIndicator + anchors.right: parent.right + anchors.bottom: chatInput.top + anchors.rightMargin: Style.current.padding + anchors.bottomMargin: Style.current.padding + } + + Component { + id: loadingIndicator + LoadingAnimation { } + } + + StatusChatInput { + id: chatInput + visible: { + if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypePrivateGroupChat) { + return chatsModel.channelView.activeChannel.isMember + } + if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypeOneToOne) { + return isContact && contactRequestReceived + } + const community = chatsModel.communities.activeCommunity + return !community.active || + community.access === Constants.communityChatPublicAccess || + community.admin || + chatsModel.channelView.activeChannel.canPost + } + enabled: !isBlocked + chatInputPlaceholder: isBlocked ? + //% "This user has been blocked." + qsTrId("this-user-has-been-blocked-") : + //% "Type a message." + qsTrId("type-a-message-") + anchors.bottom: parent.bottom + recentStickers: chatsModel.stickers.recent + stickerPackList: chatsModel.stickers.stickerPacks + chatType: chatsModel.channelView.activeChannel.chatType + onSendTransactionCommandButtonClicked: { + if (chatsModel.channelView.activeChannel.ensVerified) { + txModalLoader.sourceComponent = cmpSendTransactionWithEns + } else { + txModalLoader.sourceComponent = cmpSendTransactionNoEns + } + txModalLoader.item.open() + } + onReceiveTransactionCommandButtonClicked: { + txModalLoader.sourceComponent = cmpReceiveTransaction + txModalLoader.item.open() + } + onStickerSelected: { + chatsModel.stickers.send(hashId, packId) + } + onSendMessage: { + if (chatInput.fileUrls.length > 0){ + chatsModel.sendImages(JSON.stringify(fileUrls)); + } + let msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text)) + if (msg.length > 0){ + msg = chatInput.interpretMessage(msg) + chatsModel.messageView.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj)); + if(event) event.accepted = true + sendMessageSound.stop(); + Qt.callLater(sendMessageSound.play); + + chatInput.textInput.clear(); + chatInput.textInput.textFormat = TextEdit.PlainText; + chatInput.textInput.textFormat = TextEdit.RichText; + } } } } } - EmojiReactions { - id: reactionModel + EmptyChat { } + + Loader { + id: txModalLoader + function close() { + if (!this.item) { + return + } + this.item.close() + this.closed() + } + function closed() { + this.sourceComponent = undefined + } + } + + Component { + id: cmpSendTransactionNoEns + ChatCommandModal { + id: sendTransactionNoEns + onClosed: { + txModalLoader.closed() + } + sendChatCommand: chatColumnLayout.requestAddressForTransaction + isRequested: false + //% "Send" + commandTitle: qsTrId("command-button-send") + title: commandTitle + //% "Request Address" + finalButtonLabel: qsTrId("request-address") + selectRecipient.selectedRecipient: { + return { + address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet + alias: chatsModel.channelView.activeChannel.alias, + identicon: chatsModel.channelView.activeChannel.identicon, + name: chatsModel.channelView.activeChannel.name, + type: RecipientSelector.Type.Contact + } + } + selectRecipient.selectedType: RecipientSelector.Type.Contact + selectRecipient.readOnly: true + } + } + + Component { + id: cmpReceiveTransaction + ChatCommandModal { + id: receiveTransaction + onClosed: { + txModalLoader.closed() + } + sendChatCommand: chatColumnLayout.requestTransaction + isRequested: true + //% "Request" + commandTitle: qsTrId("wallet-request") + title: commandTitle + //% "Request" + finalButtonLabel: qsTrId("wallet-request") + selectRecipient.selectedRecipient: { + return { + address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet + alias: chatsModel.channelView.activeChannel.alias, + identicon: chatsModel.channelView.activeChannel.identicon, + name: chatsModel.channelView.activeChannel.name, + type: RecipientSelector.Type.Contact + } + } + selectRecipient.selectedType: RecipientSelector.Type.Contact + selectRecipient.readOnly: true + } + } + + Component { + id: cmpSendTransactionWithEns + SendModal { + id: sendTransactionWithEns + onOpened: { + walletModel.gasView.getGasPricePredictions() + } + onClosed: { + txModalLoader.closed() + } + selectRecipient.readOnly: true + selectRecipient.selectedRecipient: { + return { + address: "", + alias: chatsModel.channelView.activeChannel.alias, + identicon: chatsModel.channelView.activeChannel.identicon, + name: chatsModel.channelView.activeChannel.name, + type: RecipientSelector.Type.Contact, + ensVerified: true + } + } + selectRecipient.selectedType: RecipientSelector.Type.Contact + } + } + + ActivityCenter { + id: activityCenter + height: chatColumnLayout.height - (topBar.height * 2) // TODO get screen size + y: topBar.height + } + + Connections { + target: profileModel.contacts + onContactListChanged: { + isBlocked = profileModel.contacts.isContactBlocked(activeChatId); + } + onContactBlocked: { + chatsModel.messageView.removeMessagesByUserId(publicKey) + } } Connections { target: chatsModel.channelView onActiveChannelChanged: { - chatInput.suggestions.hide(); - chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason) - populateSuggestions(); + chatsModel.messageView.hideLoadingIndicator() + SelectedMessage.reset(); + chatColumn.isReply = false; + } + } + + Connections { + target: systemTray + onMessageClicked: function () { + clickOnNotification() + } + } + + Component { + id: pinnedMessagesPopupComponent + PinnedMessagesPopup { + id: pinnedMessagesPopup + onClosed: destroy() } } Connections { target: chatsModel.messageView - onMessagePushed: { - addSuggestionFromMessageList(messageIndex); - } - } - Connections { - target: profileModel - onContactsChanged: { - populateSuggestions(); - } - } + onMessageNotificationPushed: function(chatId, msg, contentType, chatType, timestamp, identicon, username, hasMention, isAddedContact, channelName) { + if (contentType == Constants.editType) + return; - ChatRequestMessage { - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - Layout.fillWidth: true - Layout.bottomMargin: Style.current.bigPadding - } + if (appSettings.notificationSetting == Constants.notifyAllMessages || + (appSettings.notificationSetting == Constants.notifyJustMentions && hasMention)) { + if (chatId === chatsModel.channelView.activeChannel.id && applicationWindow.active === true) { + // Do not show the notif if we are in the channel already and the window is active and focused + return + } - Item { - id: inputArea - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - Layout.fillWidth: true - Layout.preferredWidth: parent.width - height: chatInput.height - Layout.preferredHeight: height + chatColumnLayout.currentNotificationChatId = chatId + chatColumnLayout.currentNotificationCommunityId = null - Connections { - target: chatsModel.messageView - onLoadingMessagesChanged: - if(value){ - loadingMessagesIndicator.active = true + let name; + if (appSettings.notificationMessagePreviewSetting === Constants.notificationPreviewAnonymous) { + name = "Status" + } else if (chatType === Constants.chatTypePublic) { + name = chatId } else { - timer.setTimeout(function(){ - loadingMessagesIndicator.active = false; - }, 5000); + name = chatType === Constants.chatTypePrivateGroupChat ? Utils.filterXSS(channelName) : Utils.removeStatusEns(username) } - } - Loader { - id: loadingMessagesIndicator - active: chatsModel.messageView.loadingMessages - sourceComponent: loadingIndicator - anchors.right: parent.right - anchors.bottom: chatInput.top - anchors.rightMargin: Style.current.padding - anchors.bottomMargin: Style.current.padding - } - - Component { - id: loadingIndicator - LoadingAnimation {} - } - - StatusChatInput { - id: chatInput - visible: { - if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypePrivateGroupChat) { - return chatsModel.channelView.activeChannel.isMember - } - if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypeOneToOne) { - return isContact && contactRequestReceived - } - const community = chatsModel.communities.activeCommunity - return !community.active || - community.access === Constants.communityChatPublicAccess || - community.admin || - chatsModel.channelView.activeChannel.canPost - } - enabled: !isBlocked - chatInputPlaceholder: isBlocked ? - //% "This user has been blocked." - qsTrId("this-user-has-been-blocked-") : - //% "Type a message." - qsTrId("type-a-message-") - anchors.bottom: parent.bottom - recentStickers: chatsModel.stickers.recent - stickerPackList: chatsModel.stickers.stickerPacks - chatType: chatsModel.channelView.activeChannel.chatType - onSendTransactionCommandButtonClicked: { - if (chatsModel.channelView.activeChannel.ensVerified) { - txModalLoader.sourceComponent = cmpSendTransactionWithEns + let message; + if (appSettings.notificationMessagePreviewSetting > Constants.notificationPreviewNameOnly) { + switch(contentType){ + //% "Image" + case Constants.imageType: message = qsTrId("image"); break + //% "Sticker" + case Constants.stickerType: message = qsTrId("sticker"); break + default: message = msg // don't parse emojis here as it emits HTML + } } else { - txModalLoader.sourceComponent = cmpSendTransactionNoEns + //% "You have a new message" + message = qsTrId("you-have-a-new-message") } - txModalLoader.item.open() - } - onReceiveTransactionCommandButtonClicked: { - txModalLoader.sourceComponent = cmpReceiveTransaction - txModalLoader.item.open() - } - onStickerSelected: { - chatsModel.stickers.send(hashId, packId) - } - onSendMessage: { - if (chatInput.fileUrls.length > 0){ - chatsModel.sendImages(JSON.stringify(fileUrls)); - } - let msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text)) - if (msg.length > 0){ - msg = chatInput.interpretMessage(msg) - chatsModel.messageView.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj)); - if(event) event.accepted = true - sendMessageSound.stop(); - Qt.callLater(sendMessageSound.play); - chatInput.textInput.clear(); - chatInput.textInput.textFormat = TextEdit.PlainText; - chatInput.textInput.textFormat = TextEdit.RichText; + currentlyHasANotification = true + if (appSettings.useOSNotifications && systemTray.supportsMessages) { + systemTray.showMessage(name, + message, + SystemTrayIcon.NoIcon, + Constants.notificationPopupTTL) + } else { + notificationWindow.notifyUser(chatId, name, message, chatType, identicon, chatColumnLayout.clickOnNotification) } } } } - } - EmptyChat {} - - Loader { - id: txModalLoader - function close() { - if (!this.item) { - return - } - this.item.close() - this.closed() - } - function closed() { - this.sourceComponent = undefined - } - } - Component { - id: cmpSendTransactionNoEns - ChatCommandModal { - id: sendTransactionNoEns - onClosed: { - txModalLoader.closed() - } - sendChatCommand: chatColumnLayout.requestAddressForTransaction - isRequested: false - //% "Send" - commandTitle: qsTrId("command-button-send") - title: commandTitle - //% "Request Address" - finalButtonLabel: qsTrId("request-address") - selectRecipient.selectedRecipient: { - return { - address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet - alias: chatsModel.channelView.activeChannel.alias, - identicon: chatsModel.channelView.activeChannel.identicon, - name: chatsModel.channelView.activeChannel.name, - type: RecipientSelector.Type.Contact - } - } - selectRecipient.selectedType: RecipientSelector.Type.Contact - selectRecipient.readOnly: true - } - } - Component { - id: cmpReceiveTransaction - ChatCommandModal { - id: receiveTransaction - onClosed: { - txModalLoader.closed() - } - sendChatCommand: chatColumnLayout.requestTransaction - isRequested: true - //% "Request" - commandTitle: qsTrId("wallet-request") - title: commandTitle - //% "Request" - finalButtonLabel: qsTrId("wallet-request") - selectRecipient.selectedRecipient: { - return { - address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet - alias: chatsModel.channelView.activeChannel.alias, - identicon: chatsModel.channelView.activeChannel.identicon, - name: chatsModel.channelView.activeChannel.name, - type: RecipientSelector.Type.Contact - } - } - selectRecipient.selectedType: RecipientSelector.Type.Contact - selectRecipient.readOnly: true - } - } - Component { - id: cmpSendTransactionWithEns - SendModal { - id: sendTransactionWithEns - onOpened: { - walletModel.gasView.getGasPricePredictions() - } - onClosed: { - txModalLoader.closed() - } - selectRecipient.readOnly: true - selectRecipient.selectedRecipient: { - return { - address: "", - alias: chatsModel.channelView.activeChannel.alias, - identicon: chatsModel.channelView.activeChannel.identicon, - name: chatsModel.channelView.activeChannel.name, - type: RecipientSelector.Type.Contact, - ensVerified: true - } - } - selectRecipient.selectedType: RecipientSelector.Type.Contact - } - } - - ActivityCenter { - id: activityCenter - height: chatColumnLayout.height - (topBar.height * 2) // TODO get screen size - y: topBar.height - } - - Connections { - target: chatsModel.messageView - - onMessageNotificationPushed: function(chatId, msg, contentType, chatType, timestamp, identicon, username, hasMention, isAddedContact, channelName) { - if (contentType == Constants.editType) - return; - - if (appSettings.notificationSetting == Constants.notifyAllMessages || - (appSettings.notificationSetting == Constants.notifyJustMentions && hasMention)) { - if (chatId === chatsModel.channelView.activeChannel.id && applicationWindow.active === true) { - // Do not show the notif if we are in the channel already and the window is active and focused - return - } - - chatColumnLayout.currentNotificationChatId = chatId - chatColumnLayout.currentNotificationCommunityId = null - - let name; - if (appSettings.notificationMessagePreviewSetting === Constants.notificationPreviewAnonymous) { - name = "Status" - } else if (chatType === Constants.chatTypePublic) { - name = chatId - } else { - name = chatType === Constants.chatTypePrivateGroupChat ? Utils.filterXSS(channelName) : Utils.removeStatusEns(username) - } - - let message; - if (appSettings.notificationMessagePreviewSetting > Constants.notificationPreviewNameOnly) { - switch(contentType){ - //% "Image" - case Constants.imageType: message = qsTrId("image"); break - //% "Sticker" - case Constants.stickerType: message = qsTrId("sticker"); break - default: message = msg // don't parse emojis here as it emits HTML - } - } else { - //% "You have a new message" - message = qsTrId("you-have-a-new-message") - } - - currentlyHasANotification = true - if (appSettings.useOSNotifications && systemTray.supportsMessages) { - systemTray.showMessage(name, - message, - SystemTrayIcon.NoIcon, - Constants.notificationPopupTTL) - } else { - notificationWindow.notifyUser(chatId, name, message, chatType, identicon, chatColumnLayout.clickOnNotification) - } - } + Component.onCompleted: { + chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason) } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml index 5c90172d06..10158c8de1 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml @@ -19,6 +19,7 @@ SplitView { property alias chatLogView: chatLogView property alias scrollToMessage: chatLogView.scrollToMessage + property var messageContextMenuInst property var messageList: MessagesData {} property bool loadingMessages: false property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight @@ -248,11 +249,9 @@ SplitView { } } - model: messageListDelegate section.property: "sectionIdentifier" section.criteria: ViewSection.FullString - } MessageDialog { @@ -317,6 +316,8 @@ SplitView { z = index; } } + messageContextMenu: svRoot.messageContextMenuInst + // This is used in order to have access to the previous message and determine the timestamp // we can't rely on the index because the sequence of messages is not ordered on the nim side prevMessageIndex: { diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index d1664188af..ab288c4090 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -7,6 +7,17 @@ import "./MessageComponents" import "../components" Item { + id: root + width: parent.width + anchors.right: !isCurrentUser ? undefined : parent.right + height: { + switch (contentType) { + case Constants.chatIdentifier: + return (childrenRect.height + 50); + default: return childrenRect.height; + } + } + z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index) property string fromAuthor: "0x0011223344556677889910" property string userName: "Jotaro Kujo" property string alias: "" @@ -42,15 +53,7 @@ Item { property string replaces: "" property bool isEdited: false property bool showEdit: true - - z: { - if (typeof chatLogView === "undefined") { - return 1 - } - - return chatLogView.count - index - } - + property var messageContextMenu property string displayUserName: { if (isCurrentUser) { //% "You" @@ -183,17 +186,6 @@ Item { } } - id: root - width: parent.width - anchors.right: !isCurrentUser ? undefined : parent.right - height: { - switch(contentType) { - case Constants.chatIdentifier: - return childrenRect.height + 50 - default: return childrenRect.height - } - } - property var clickMessage: function(isProfileClick, isSticker = false, isImage = false, image = null, emojiOnly = false, hideEmojiPicker = false) { if (placeholderMessage || activityCenterMessage) { return @@ -210,17 +202,17 @@ 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.hideEmojiPicker = hideEmojiPicker - messageContextMenu.pinnedMessage = pinnedMessage - messageContextMenu.show(userName, fromAuthor, root.profileImageSource || identicon, plainText, nickname, emojiReactionsModel) + messageContextMenu.messageId = root.messageId; + messageContextMenu.linkUrls = root.linkUrls; + messageContextMenu.isProfile = !!isProfileClick; + messageContextMenu.isSticker = isSticker; + messageContextMenu.emojiOnly = emojiOnly; + 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 + messageContextMenu.x = messageContextMenu.x - messageContextMenu.width / 2; } } @@ -446,6 +438,10 @@ Item { contentType: root.contentType showEdit: root.showEdit container: root + messageContextMenu: root.messageContextMenu + onAddEmoji: { + root.clickMessage(isProfileClick, isSticker, isImage , image, emojiOnly, hideEmojiPicker); + } } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatButtons.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatButtons.qml index fa64743bd1..f9708898ed 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatButtons.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatButtons.qml @@ -5,13 +5,14 @@ import "../../../../../shared/status" import "../../../../../imports" Rectangle { + id: buttonsContainer property bool parentIsHovered: false property bool showEdit: true signal hoverChanged(bool hovered) property int containerMargin: 2 property int contentType: 2 + property var messageContextMenu - id: buttonsContainer visible: !activityCenterMessage && (buttonsContainer.parentIsHovered || isMessageActive) && contentType !== Constants.transactionType @@ -65,10 +66,10 @@ Rectangle { setMessageActive(messageId, true) clickMessage(false, false, false, null, true) if (!forceHoverHandler) { - messageContextMenu.x = buttonsContainer.x + buttonsContainer.width - messageContextMenu.width + buttonsContainer.messageContextMenu.x = buttonsContainer.x + buttonsContainer.width - buttonsContainer.messageContextMenu.width // The Math.max is to make sure that the menu is rendered - messageContextMenu.y -= Math.max(messageContextMenu.emojiContainer.height, 56) + Style.current.padding + buttonsContainer.messageContextMenu.y -= Math.max(buttonsContainer.messageContextMenu.emojiContainer.height, 56) + Style.current.padding } } onHoveredChanged: { diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml index 8bb7f38b9c..808af06ea1 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml @@ -5,6 +5,7 @@ import "../../../../../shared/status" import "../../../../../imports" Item { + id: root property var clickMessage: function () {} property int chatHorizontalPadding: Style.current.halfPadding property int chatVerticalPadding: 7 @@ -16,8 +17,8 @@ Item { property bool isMessageActive: typeof activeMessage !== "undefined" && activeMessage === messageId property bool headerRepeatCondition: (authorCurrentMsg !== authorPrevMsg || shouldRepeatHeader || dateGroupLbl.visible || chatReply.active) property bool showEdit: true - - id: root + property var messageContextMenu + signal addEmoji(bool isProfileClick, bool isSticker, bool isImage , var image, bool emojiOnly, bool hideEmojiPicker) width: parent.width height: messageContainer.height + messageContainer.anchors.topMargin @@ -42,6 +43,7 @@ Item { // This is not exactly like the design because the hover becomes messed up with the buttons on top of another Message anchors.topMargin: -Style.current.halfPadding showEdit: root.showEdit + messageContextMenu: root.messageContextMenu } Loader { @@ -410,6 +412,11 @@ Item { sourceComponent: Component { EmojiReactions { onHoverChanged: setHovered(messageId, hovered) + onAddEmojiClicked: { + root.addEmoji(false, false, false, null, true, false); + messageContextMenu.x = (messageContainer.chatText.textField.leftPadding + 4); + messageContextMenu.y -= (56 + Style.current.padding); + } } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml index 0db1e72b03..daa7b32a33 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml @@ -6,13 +6,14 @@ import "../../../../../shared/status" import "../../../../../imports" Item { - property int imageMargin: 4 - signal hoverChanged(bool hovered) - id: root height: 20 width: childrenRect.width + property int imageMargin: 4 + signal hoverChanged(bool hovered) + signal addEmojiClicked() + function lastTwoItems(nodes) { //% " and " return nodes.join(qsTrId("-and-")); @@ -210,7 +211,7 @@ Item { if (typeof isMessageActive !== "undefined") { setMessageActive(messageId, true) } - clickMessage(false, false, false, null, true) + root.addEmojiClicked(); } } diff --git a/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml b/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml index 85e8fe2895..ab715f5152 100644 --- a/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml +++ b/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml @@ -8,6 +8,9 @@ import "../../../../shared/status" import "./" PopupMenu { + id: messageContextMenu + width: messageContextMenu.isProfile ? profileHeader.width : emojiContainer.width + property string messageId property bool isProfile: false property bool isSticker: false @@ -18,10 +21,6 @@ PopupMenu { property bool isCurrentUser: false property string linkUrls: "" property alias emojiContainer: emojiContainer - - id: messageContextMenu - width: messageContextMenu.isProfile ? profileHeader.width : emojiContainer.width - property var identicon: "" property var userName: "" property string nickname: "" @@ -29,14 +28,13 @@ PopupMenu { property var text: "" property var emojiReactionsReactedByUser: [] property var onClickEdit: function(){} + property var reactionModel - subMenuIcons: [ - { + subMenuIcons: [{ source: Qt.resolvedUrl("../../../../shared/img/copy-to-clipboard-icon"), width: 16, height: 16 - } - ] + }] function show(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam, emojiReactionsModel) { userName = userNameParam || "" @@ -50,7 +48,7 @@ PopupMenu { newEmojiReactions[emojiReaction.emojiId] = emojiReaction.currentUserReacted }) } - emojiReactionsReactedByUser = newEmojiReactions + emojiReactionsReactedByUser = newEmojiReactions; const numLinkUrls = messageContextMenu.linkUrls.split(" ").length copyLinkMenu.enabled = numLinkUrls > 1 @@ -60,10 +58,9 @@ PopupMenu { Item { id: emojiContainer - visible: !hideEmojiPicker && (messageContextMenu.emojiOnly || !messageContextMenu.isProfile) width: emojiRow.width height: visible ? emojiRow.height : 0 - + visible: !hideEmojiPicker && (messageContextMenu.emojiOnly || !messageContextMenu.isProfile) Row { id: emojiRow spacing: Style.current.smallPadding @@ -85,15 +82,16 @@ PopupMenu { } } - Rectangle { - property bool hovered: false - + Item { id: profileHeader visible: messageContextMenu.isProfile width: 200 height: visible ? profileImage.height + username.height + Style.current.padding : 0 - color: hovered ? Style.current.backgroundHover : Style.current.transparent - + Rectangle { + anchors.fill: parent + visible: mouseArea.containsMouse + color: Style.current.backgroundHover + } StatusImageIdenticon { id: profileImage source: identicon @@ -120,15 +118,10 @@ PopupMenu { } MouseArea { - cursorShape: Qt.PointingHandCursor + id: mouseArea anchors.fill: parent hoverEnabled: true - onEntered: { - profileHeader.hovered = true - } - onExited: { - profileHeader.hovered = false - } + cursorShape: Qt.PointingHandCursor onClicked: { openProfilePopup(userName, fromAuthor, identicon); messageContextMenu.close()