[#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
This commit is contained in:
Alexandra Betouni 2021-07-16 18:02:47 +03:00 committed by Iuri Matias
parent 3c6571ac52
commit 0f0b239f2a
7 changed files with 486 additions and 504 deletions

View File

@ -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)
}
}
}

View File

@ -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: {

View File

@ -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);
}
}
}
}

View File

@ -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: {

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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()