diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index ed14cca440..cd7af19637 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -35,6 +35,7 @@ type Alias = UserRole + 25 LocalName = UserRole + 26 CommunityId = UserRole + 27 + HasMention = UserRole + 28 QtObject: type @@ -154,6 +155,7 @@ QtObject: of ChatMessageRoles.EmojiReactions: result = newQVariant(self.getReactions(message.id)) of ChatMessageRoles.LinkUrls: result = newQVariant(message.linkUrls) of ChatMessageRoles.CommunityId: result = newQVariant(message.communityId) + of ChatMessageRoles.HasMention: result = newQVariant(message.hasMention) # Pass the command parameters as a JSON string of ChatMessageRoles.CommandParameters: result = newQVariant($(%*{ "id": message.commandParameters.id, @@ -195,6 +197,7 @@ QtObject: ChatMessageRoles.CommandParameters.int: "commandParameters", ChatMessageRoles.CommunityId.int: "communityId", ChatMessageRoles.Alias.int:"alias", + ChatMessageRoles.HasMention.int:"hasMention", ChatMessageRoles.LocalName.int:"localName" }.toTable diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 3080bd83e8..521f7863bc 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -1,9 +1,9 @@ -import json, times, strutils, sequtils, chronicles, json_serialization, algorithm, strformat +import json, times, strutils, sequtils, chronicles, json_serialization, algorithm, strformat, sugar import core, utils import ../chat/[chat, message] import ../signals/messages import ./types -import ./settings +import ./settings as status_settings proc buildFilter*(chat: Chat):JsonNode = if chat.chatType == ChatType.PrivateGroupChat: @@ -56,10 +56,14 @@ proc loadChats*(): seq[Chat] = result.sort(sortChats) proc parseChatMessagesResponse*(chatId: string, rpcResult: JsonNode): (string, seq[Message]) = + let pk = status_settings.getSetting[string](Setting.PublicKey, "0x0") var messages: seq[Message] = @[] + var msg: Message if rpcResult["messages"].kind != JNull: for jsonMsg in rpcResult["messages"]: - messages.add(jsonMsg.toMessage) + msg = jsonMsg.toMessage + msg.hasMention = concat(msg.parsedText.map(t => t.children.filter(c => c.textType == "mention" and c.literal == pk))).len > 0 + messages.add(msg) return (rpcResult{"cursor"}.getStr, messages) proc rpcChatMessages*(chatId: string, cursorVal: JsonNode, limit: int, success: var bool): string = diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml index b3789243c5..b93896f475 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml @@ -308,6 +308,7 @@ ScrollView { emojiReactions: model.emojiReactions linkUrls: model.linkUrls communityId: model.communityId + hasMention: model.hasMention prevMessageIndex: { // 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 diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index d86d0db8f0..d7a91d4507 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -23,6 +23,7 @@ Item { property string emojiReactions: "" property int prevMessageIndex: -1 property bool timeout: false + property bool hasMention: false property string linkUrls: "" property bool placeholderMessage: false property string communityId: "" @@ -36,7 +37,8 @@ Item { property bool isStatusMessage: contentType === Constants.systemMessagePrivateGroupType property bool isSticker: contentType === Constants.stickerType property bool isText: contentType === Constants.messageType - property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio || contentType === Constants.communityInviteType + property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio + || contentType === Constants.communityInviteType || contentType === Constants.transactionType property bool isExpired: (outgoingStatus == "sending" && (Math.floor(timestamp) + 180000) < Date.now()) property bool isStatusUpdate: false @@ -110,8 +112,6 @@ Item { return fetchMoreMessagesButtonComponent case Constants.systemMessagePrivateGroupType: return privateGroupHeaderComponent - case Constants.transactionType: - return transactionBubble case Constants.communityInviteType: return invitationBubble default: @@ -245,11 +245,6 @@ Item { } } - Component { - id: transactionBubble - TransactionBubble {} - } - Component { id: invitationBubble InvitationBubble {} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml index 2335a13536..011eafc1a5 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml @@ -14,7 +14,7 @@ Item { visible: contentType == Constants.messageType || isEmoji z: 51 - height: visible ? (showMoreLoader.active ? childrenRect.height - 10 : chatText.height) : 0 + implicitHeight: visible ? (showMoreLoader.active ? childrenRect.height - 10 : chatText.height) : 0 // This function is to avoid the binding loop warning function setWidths() { @@ -82,7 +82,9 @@ Item { `color: ${isCurrentUser && !appSettings.compactMode ? Style.current.white : Style.current.textColor};` + `}` + `a.mention {` + - `color: ${isCurrentUser ? Style.current.cyan : Style.current.turquoise};` + + `color: ${Style.current.mentionColor};` + + `background-color: ${Style.current.mentionBgColor};` + + `text-decoration: none;` + `}` + `del {` + `text-decoration: line-through;` + diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml index 26dabf6555..c0b75e6fe7 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml @@ -6,7 +6,7 @@ import "../../../../../imports" Item { property var clickMessage: function () {} - property int chatHorizontalPadding: 8 + property int chatHorizontalPadding: Style.current.halfPadding property int chatVerticalPadding: 7 property string linkUrls: "" property int contentType: 2 @@ -52,10 +52,13 @@ Item { id: messageContainer height: childrenRect.height + (chatName.visible || emojiReactionLoader.active ? Style.current.smallPadding : 0) - + (emojiReactionLoader.active ? emojiReactionLoader.height + Style.current.halfPadding : 0) + + (chatName.visible && emojiReactionLoader.active ? 5 : 0) + + (emojiReactionLoader.active ? emojiReactionLoader.height: 0) + + (retry.visible && !chatTime.visible ? Style.current.smallPadding : 0) width: parent.width - color: root.isHovered || isMessageActive ? Style.current.backgroundHover : Style.current.transparent + color: root.isHovered || isMessageActive ? (hasMention ? Style.current.mentionMessageHoverColor : Style.current.backgroundHover) : + (hasMention ? Style.current.mentionMessageColor : Style.current.transparent) // FIXME @jonathanr: Adding this breaks the first line. Need to fix the height somehow // DateGroup { @@ -79,130 +82,152 @@ Item { anchors.left: chatImage.right } - ChatReply { - id: chatReply - // anchors.top: chatName.visible ? chatName.bottom : (dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top) - anchors.top: chatName.visible ? chatName.bottom : parent.top - anchors.topMargin: chatName.visible && this.visible ? root.chatVerticalPadding : 0 - anchors.left: chatImage.right - anchors.leftMargin: root.chatHorizontalPadding - anchors.right: parent.right - anchors.rightMargin: root.chatHorizontalPadding - container: root.container - chatHorizontalPadding: root.chatHorizontalPadding - } - - ChatText { - id: chatText - anchors.top: chatReply.active ? chatReply.bottom : chatName.visible ? chatName.bottom : parent.top - anchors.left: parent.left - anchors.right: parent.right - // using a padding instead of a margin let's us select text more easily - textField.leftPadding: chatImage.anchors.leftMargin + chatImage.width + root.chatHorizontalPadding - textField.rightPadding: Style.current.bigPadding - } - - Loader { - id: chatImageContent - active: isImage - anchors.left: chatText.left - anchors.leftMargin: 8 - anchors.top: chatReply.bottom - z: 51 - - sourceComponent: Component { - ChatImage { - imageSource: image - imageWidth: 200 - onClicked: root.clickMessage(false, false, true, image) - container: root.container - } - } - } - - Loader { - id: stickerLoader - active: contentType === Constants.stickerType - anchors.left: chatText.left - anchors.top: chatName.visible ? chatName.bottom : parent.top - anchors.topMargin: this.visible && chatName.visible ? root.chatVerticalPadding : 0 - - sourceComponent: Component { - Rectangle { - id: stickerContainer - color: Style.current.transparent - border.color: Style.current.grey - border.width: 1 - radius: 16 - width: stickerId.width + 2 * root.chatVerticalPadding - height: stickerId.height + 2 * root.chatVerticalPadding - - Sticker { - id: stickerId - anchors.top: parent.top - anchors.topMargin: root.chatVerticalPadding - anchors.left: parent.left - anchors.leftMargin: root.chatVerticalPadding - contentType: root.contentType - container: root.container - } - } - } - } - - MessageMouseArea { - id: messageMouseArea - anchors.fill: stickerLoader.active ? stickerLoader : chatText - } - ChatTime { id: chatTime visible: authorCurrentMsg != authorPrevMsg anchors.verticalCenter: chatName.verticalCenter anchors.left: chatName.right anchors.leftMargin: 4 + color: Style.current.secondaryText } - SentMessage { - id: sentMessage - visible: isCurrentUser && !timeout && !isExpired && isMessage && outgoingStatus !== "sent" - anchors.verticalCenter: chatTime.verticalCenter - anchors.left: chatTime.right - anchors.leftMargin: Style.current.halfPadding - } + Item { + id: messageContent + height: childrenRect.height + Style.current.halfPadding + anchors.top: chatName.visible ? chatName.bottom : parent.top + anchors.left: chatImage.right + anchors.leftMargin: root.chatHorizontalPadding + anchors.right: parent.right + anchors.rightMargin: root.chatHorizontalPadding - Retry { - id: retry - anchors.right: chatTime.right - anchors.rightMargin: 5 - } + ChatReply { + id: chatReply + container: root.container + chatHorizontalPadding: root.chatHorizontalPadding + } - Loader { - id: linksLoader - active: !!root.linkUrls - anchors.left: chatText.left - anchors.leftMargin: 8 - anchors.top: chatText.bottom + ChatText { + readonly property int leftPadding: chatImage.anchors.leftMargin + chatImage.width + root.chatHorizontalPadding + id: chatText + anchors.top: chatReply.active ? chatReply.bottom : parent.top + anchors.left: parent.left + anchors.right: parent.right + // using a padding instead of a margin let's us select text more easily + anchors.leftMargin: -leftPadding + textField.leftPadding: leftPadding + textField.rightPadding: Style.current.bigPadding + } - sourceComponent: Component { - LinksMessage { - linkUrls: root.linkUrls - container: root.container - isCurrentUser: root.isCurrentUser + Loader { + id: chatImageContent + active: isImage + anchors.top: chatReply.bottom + z: 51 + + sourceComponent: Component { + ChatImage { + imageSource: image + imageWidth: 200 + onClicked: root.clickMessage(false, false, true, image) + container: root.container + } + } + } + + Loader { + id: stickerLoader + active: contentType === Constants.stickerType + anchors.top: parent.top + anchors.topMargin: active ? Style.current.halfPadding : 0 + sourceComponent: Component { + Rectangle { + id: stickerContainer + color: Style.current.transparent + border.color: root.isHovered ? Qt.darker(Style.current.darkGrey, 1.1) : Style.current.grey + border.width: 1 + radius: 16 + width: stickerId.width + 2 * root.chatVerticalPadding + height: stickerId.height + 2 * root.chatVerticalPadding + + Sticker { + id: stickerId + anchors.top: parent.top + anchors.topMargin: root.chatVerticalPadding + anchors.left: parent.left + anchors.leftMargin: root.chatVerticalPadding + contentType: root.contentType + container: root.container + } + } + } + } + + MessageMouseArea { + id: messageMouseArea + anchors.fill: stickerLoader.active ? stickerLoader : chatText + } + + Loader { + id: linksLoader + active: !!root.linkUrls + anchors.top: chatText.bottom + anchors.topMargin: active ? Style.current.halfPadding : 0 + + sourceComponent: Component { + LinksMessage { + linkUrls: root.linkUrls + container: root.container + isCurrentUser: root.isCurrentUser + } + } + } + + Loader { + id: audioPlayerLoader + active: isAudio + anchors.top: parent.top + anchors.topMargin: active ? Style.current.halfPadding : 0 + + sourceComponent: Component { + AudioPlayer { + audioSource: audio + } + } + } + + Loader { + id: transactionBubbleLoader + active: contentType === Constants.transactionType + anchors.top: parent.top + anchors.topMargin: active ? (chatName.visible ? 4 : 6) : 0 + sourceComponent: Component { + TransactionBubble {} } } } - Loader { - id: audioPlayerLoader - active: isAudio - anchors.top: chatName.visible ? chatName.bottom : parent.top - anchors.left: chatImage.right - sourceComponent: Component { - AudioPlayer { - audioSource: audio - } + Retry { + id: retry + anchors.left: chatTime.visible ? chatTime.right : messageContent.left + anchors.leftMargin: chatTime.visible ? chatHorizontalPadding : 0 + anchors.top: chatTime.visible ? undefined : messageContent.bottom + anchors.topMargin: chatTime.visible ? 0 : -4 + anchors.verticalCenter: chatTime.visible ? chatTime.verticalCenter : undefined + } + } + + Loader { + active: hasMention + height: messageContainer.height + anchors.left: messageContainer.left + + sourceComponent: Component { + Rectangle { + id: mentionBorder + color: Style.current.mentionColor + width: 2 + height: parent.height } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml index e5067870af..6ad149f882 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml @@ -113,7 +113,7 @@ Item { anchors.rightMargin: !root.isCurrentUser ? 0 : Style.current.padding anchors.top: authorCurrentMsg != authorPrevMsg && !root.isCurrentUser ? chatImage.top : (dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top) anchors.topMargin: 0 - visible: isMessage + visible: isMessage && contentType !== Constants.transactionType ChatReply { id: chatReply @@ -207,6 +207,18 @@ Item { } } + Loader { + id: transactionBubbleLoader + active: contentType === Constants.transactionType + anchors.left: !isCurrentUser ? chatImage.right : undefined + anchors.leftMargin: isCurrentUser ? 0 : Style.current.halfPadding + anchors.right: isCurrentUser ? parent.right : undefined + anchors.rightMargin: Style.current.padding + sourceComponent: Component { + TransactionBubble {} + } + } + Rectangle { id: dateTimeBackground visible: isImage @@ -269,8 +281,8 @@ Item { id: emojiReactionLoader active: emojiReactions !== "" sourceComponent: emojiReactionsComponent - anchors.left: !root.isCurrentUser ? chatBox.left : undefined - anchors.right: !root.isCurrentUser ? undefined : chatBox.right + anchors.left: chatBox.left + anchors.leftMargin: root.isCurrentUser ? Style.current.halfPadding : 1 anchors.top: chatBox.bottom anchors.topMargin: 2 } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml index 5a7bb60b5b..00a6019783 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml @@ -56,9 +56,6 @@ Item { } id: root - anchors.left: parent.left - anchors.leftMargin: isCurrentUser ? 0 : - appSettings.compactMode ? Style.current.padding : 48; width: rectangleBubble.width height: rectangleBubble.height @@ -72,11 +69,6 @@ Item { border.color: Style.current.border border.width: 1 - anchors.right: isCurrentUser ? parent.right : undefined - anchors.rightMargin: Style.current.padding - anchors.left: !isCurrentUser ? parent.left : undefined - anchors.leftMargin: Style.current.padding - StyledText { id: title color: Style.current.secondaryText diff --git a/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml b/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml index 22c9087160..ca8f72fc87 100644 --- a/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml +++ b/ui/app/AppLayouts/Chat/ContactsColumn/Channel.qml @@ -50,12 +50,12 @@ Rectangle { // Hide the box if it is filtered out property bool isVisible: searchStr === "" || name.includes(searchStr) visible: isVisible ? true : false - height: isVisible ? (!isCompact ? 64 : contactImage.height + Style.current.smallPadding * 2) : 0 + height: isVisible ? (!isCompact ? 64 : 40) : 0 StatusIdenticon { id: contactImage - height: !isCompact ? 40 : 20 - width: !isCompact ? 40 : 20 + height: !isCompact ? 40 : 28 + width: !isCompact ? 40 : 28 chatName: wrapper.name chatType: wrapper.chatType identicon: wrapper.profileImage || wrapper.identicon @@ -71,7 +71,7 @@ Rectangle { fillMode: Image.PreserveAspectFit source: "../../../img/channel-icon-" + (wrapper.chatType === Constants.chatTypePublic ? "public-chat.svg" : "group.svg") anchors.left: contactImage.right - anchors.leftMargin: Style.current.padding + anchors.leftMargin: !isCompact ? Style.current.padding : Style.current.smallPadding anchors.top: !isCompact ? parent.top : undefined anchors.topMargin: !isCompact ? Style.current.smallPadding : 0 anchors.verticalCenter: !isCompact ? undefined : parent.verticalCenter @@ -90,7 +90,8 @@ Rectangle { font.weight: Font.Medium font.pixelSize: 15 anchors.left: channelIcon.visible ? channelIcon.right : contactImage.right - anchors.leftMargin: channelIcon.visible ? 2 : Style.current.padding + anchors.leftMargin: channelIcon.visible ? 2 : + (!isCompact ? Style.current.padding : Style.current.halfPadding) anchors.top: !isCompact ? parent.top : undefined anchors.topMargin: !isCompact ? Style.current.smallPadding : 0 anchors.verticalCenter: !isCompact ? undefined : parent.verticalCenter @@ -124,12 +125,12 @@ Rectangle { StyledText { id: contactTime + visible: !isCompact text: Utils.formatDateTime(wrapper.timestamp, appSettings.locale) anchors.right: parent.right - anchors.rightMargin: !isCompact ? Style.current.padding : Style.current.smallPadding - anchors.top: !isCompact ? parent.top : undefined - anchors.topMargin: !isCompact ? Style.current.smallPadding : 0 - anchors.verticalCenter: !isCompact ? undefined : parent.verticalCenter + anchors.rightMargin: Style.current.padding + anchors.top: parent.top + anchors.topMargin: Style.current.smallPadding font.pixelSize: 11 color: Style.current.darkGrey } @@ -138,7 +139,7 @@ Rectangle { width: 22 height: 22 radius: 50 - anchors.right: !isCompact ? parent.right : contactTime.left + anchors.right: parent.right anchors.rightMargin: !isCompact ? Style.current.padding : Style.current.smallPadding anchors.bottom: !isCompact ? parent.bottom : undefined anchors.bottomMargin: !isCompact ? Style.current.smallPadding : 0 diff --git a/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml b/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml index 4b859e48d2..2eb620ac9b 100644 --- a/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml +++ b/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml @@ -21,7 +21,7 @@ Rectangle { ListView { id: chatGroupsListView - spacing: Style.current.halfPadding + spacing: appSettings.compactMode ? 4 : Style.current.halfPadding anchors.top: parent.top height: childrenRect.height visible: height > 50 diff --git a/ui/imports/Themes/DarkTheme.qml b/ui/imports/Themes/DarkTheme.qml index 7b9c020f04..463cfe1447 100644 --- a/ui/imports/Themes/DarkTheme.qml +++ b/ui/imports/Themes/DarkTheme.qml @@ -53,6 +53,10 @@ Theme { property color primarySelectionColor: "#b4c8ff" property color emojiReactionBackground: "#2d2823" property color emojiReactionBackgroundHovered: "#3a3632" + property color mentionColor: "#47B6D1" + property color mentionBgColor: Qt.rgba(71, 182, 209, 0.1) + property color mentionMessageColor: "#E5F8FD" + property color mentionMessageHoverColor: mentionBgColor property color buttonForegroundColor: blue property color buttonBackgroundColor: secondaryBackground diff --git a/ui/imports/Themes/LightTheme.qml b/ui/imports/Themes/LightTheme.qml index aaa3b007c8..62171adbd7 100644 --- a/ui/imports/Themes/LightTheme.qml +++ b/ui/imports/Themes/LightTheme.qml @@ -52,6 +52,10 @@ Theme { property color primarySelectionColor: "#b4c8ff" property color emojiReactionBackground: "#e2e6e9" property color emojiReactionBackgroundHovered: "#d7dadd" + property color mentionColor: "#0DA4C9" + property color mentionBgColor: "#D4F3FA" + property color mentionMessageColor: "#E5F8FD" + property color mentionMessageHoverColor: mentionBgColor property color buttonForegroundColor: blue property color buttonBackgroundColor: secondaryBackground diff --git a/ui/imports/Themes/Theme.qml b/ui/imports/Themes/Theme.qml index 735958fd12..aa9d2ca3bf 100644 --- a/ui/imports/Themes/Theme.qml +++ b/ui/imports/Themes/Theme.qml @@ -41,6 +41,10 @@ QtObject { property color primarySelectioncolor property color emojiReactionBackground property color emojiReactionBackgroundHovered + property color mentionColor + property color mentionBgColor + property color mentionMessageColor + property color mentionMessageHoverColor property color buttonForegroundColor property color buttonBackgroundColor diff --git a/ui/shared/status/StatusLetterIdenticon.qml b/ui/shared/status/StatusLetterIdenticon.qml index 9776fa9653..f40345cd65 100644 --- a/ui/shared/status/StatusLetterIdenticon.qml +++ b/ui/shared/status/StatusLetterIdenticon.qml @@ -23,7 +23,7 @@ Rectangle { text: (root.chatName.charAt(0) == "#" ? root.chatName.charAt(1) : root.chatName.charAt(0)).toUpperCase() opacity: 0.7 font.weight: Font.Bold - font.pixelSize: root.isCompact ? 14 : 21 + font.pixelSize: root.isCompact ? 15 : 21 color: "white" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter