diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index bb401cae1e..0264b541c2 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -595,7 +595,17 @@ QtObject: self.messageList[chat].markMessageAsSent(messageId) else: error "Message could not be marked as sent", chat, messageId - + + proc getMessageIndex(self: ChatsView, chatId: string, messageId: string): int {.slot.} = + if (not self.messageList.hasKey(chatId)): + return -1 + result = self.messageList[chatId].getMessageIndex(messageId) + + proc getMessageData(self: ChatsView, chatId: string, index: int, data: string): string {.slot.} = + if (not self.messageList.hasKey(chatId)): + return + + return self.messageList[chatId].getMessageData(index, data) proc getMessageList(self: ChatsView): QVariant {.slot.} = self.upsertChannel(self.activeChannel.id) diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index 28e93d88ad..c27ee10a8c 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -225,12 +225,12 @@ QtObject: ChatMessageRoles.GapTo.int:"gapTo" }.toTable - proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} = + proc getMessageIndex*(self: ChatMessageList, messageId: string): int {.slot.} = if not self.messageIndex.hasKey(messageId): return -1 result = self.messageIndex[messageId] # TODO: see how to use data() instead of this function - proc getMessageData(self: ChatMessageList, index: int, data: string): string {.slot.} = + proc getMessageData*(self: ChatMessageList, index: int, data: string): string {.slot.} = if index < 0 or index >= self.messages.len: return ("") let message = self.messages[index] diff --git a/src/status/chat/chat.nim b/src/status/chat/chat.nim index 79f5fb62dc..a755436ff7 100644 --- a/src/status/chat/chat.nim +++ b/src/status/chat/chat.nim @@ -16,6 +16,7 @@ type ActivityCenterNotificationType* {.pure.}= enum NewOneToOne = 1, NewPrivateGroupChat = 2, Mention = 3 + Reply = 4 proc isOneToOne*(self: ChatType): bool = self == ChatType.OneToOne proc isTimeline*(self: ChatType): bool = self == ChatType.Timeline diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ActivityCenter.qml b/ui/app/AppLayouts/Chat/ChatColumn/ActivityCenter.qml index d016f3b18a..0e0ac85153 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ActivityCenter.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ActivityCenter.qml @@ -16,6 +16,9 @@ Popup { ContactRequests } property int currentFilter: ActivityCenter.Filter.All + property bool hasMentions: false + property bool hasReplies: false + property bool hasContactRequests: contactList.count > 0 id: activityCenter modal: true @@ -111,16 +114,33 @@ Popup { property int idx: DelegateModel.itemsIndex + Component.onCompleted: { + switch (model.notificationType) { + case Constants.acitivtyCenterNotificationTypeMention: + if (!hasMentions) { + hasMentions = true + } + break + + case Constants.acitivtyCenterNotificationTypeReply: + if (!hasReplies) { + hasReplies = true + } + break + + } + } Loader { id: notifLoader anchors.top: parent.top active: !!sourceComponent width: parent.width + height: active && item.visible ? item.height : 0 sourceComponent: { switch (model.notificationType) { - // TODO add to constants (mention) - case 3: return messageNotificationComponent + case Constants.acitivtyCenterNotificationTypeMention:return messageNotificationComponent + case Constants.acitivtyCenterNotificationTypeReply: return messageNotificationComponent default: return null } } @@ -130,7 +150,9 @@ Popup { id: messageNotificationComponent Rectangle { - visible: activityCenter.currentFilter === ActivityCenter.Filter.All || activityCenter.currentFilter === ActivityCenter.Filter.Mentions + visible: activityCenter.currentFilter === ActivityCenter.Filter.All || + (model.notificationType === Constants.acitivtyCenterNotificationTypeMention && activityCenter.currentFilter === ActivityCenter.Filter.Mentions) || + (model.notificationType === Constants.acitivtyCenterNotificationTypeReply && activityCenter.currentFilter === ActivityCenter.Filter.Replies) width: parent.width height: childrenRect.height + Style.current.smallPadding color: model.read ? Style.current.transparent : Utils.setColorAlpha(Style.current.blue, 0.1) @@ -202,6 +224,8 @@ Popup { ActivityChannelBadge { name: model.name chatId: model.chatId + notificationType: model.notificationType + responseTo: model.message.responseTo anchors.top: notificationMessage.bottom anchors.left: parent.left anchors.leftMargin: 61 // TODO find a way to align with the text of the message diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterTopBar.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterTopBar.qml index 80f9f4ea92..b234370cc0 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterTopBar.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterTopBar.qml @@ -31,6 +31,7 @@ Item { id: mentionsBtn text: qsTr("Mentions") type: "secondary" + enabled: hasMentions size: "small" highlighted: activityCenter.currentFilter === ActivityCenter.Filter.Mentions onClicked: activityCenter.currentFilter = ActivityCenter.Filter.Mentions @@ -39,6 +40,7 @@ Item { StatusButton { id: repliesbtn text: qsTr("Replies") + enabled: hasReplies type: "secondary" size: "small" highlighted: activityCenter.currentFilter === ActivityCenter.Filter.Replies @@ -48,6 +50,7 @@ Item { StatusButton { id: contactRequestsBtn text: qsTr("Contact requests") + enabled: hasContactRequests type: "secondary" size: "small" highlighted: activityCenter.currentFilter === ActivityCenter.Filter.ContactRequests diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml index 7db2e9f03f..20ac994479 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml @@ -8,6 +8,8 @@ Rectangle { property string chatId: "" property string name: "channelName" property string identicon + property string responseTo + property int notificationType property int chatType: chatsModel.chats.getChannelType(chatId) property int realChatType: { if (chatType === Constants.chatTypeCommunity) { @@ -21,58 +23,115 @@ Rectangle { id: wrapper height: 24 - width: childrenRect.width + Style.current.smallPadding + width: childrenRect.width + 12 color: Style.current.transparent border.color: Style.current.borderSecondary border.width: 1 radius: 11 - Connections { - enabled: realChatType === Constants.chatTypeOneToOne - target: profileModel.contacts.list - onContactChanged: { - if (pubkey === wrapper.chatId) { - wrapper.profileImage = appMain.getProfileImage(wrapper.chatId) + Loader { + active: true + height: parent.height + sourceComponent: { + switch (model.notificationType) { + case Constants.acitivtyCenterNotificationTypeMention: return channelComponent + case Constants.acitivtyCenterNotificationTypeReply: return replyComponent + default: return channelComponent } } } - SVGImage { - id: channelIcon - width: 16 - height: 16 - fillMode: Image.PreserveAspectFit - source: "../../../../img/channel-icon-" + (wrapper.realChatType === Constants.chatTypePublic ? "public-chat.svg" : "group.svg") - anchors.left: parent.left - anchors.leftMargin: 4 - anchors.verticalCenter:parent.verticalCenter + Component { + id: replyComponent + + Item { + property int replyMessageIndex: chatsModel.getMessageIndex(chatId, responseTo) + property string repliedMessageContent: replyMessageIndex > -1 ? chatsModel.getMessageData(chatId, replyMessageIndex, "message") : ""; + + + width: childrenRect.width + height: parent.height + SVGImage { + id: replyIcon + width: 16 + height: 16 + source: "../../../../img/reply-small-arrow.svg" + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.verticalCenter:parent.verticalCenter + } + + StyledTextEdit { + text: Utils.getReplyMessageStyle(Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), Emoji.size.small), false, appSettings.useCompactMode) + textFormat: Text.RichText + height: 18 + width: implicitWidth > 300 ? 300 : implicitWidth + clip: true + anchors.left: replyIcon.right + anchors.leftMargin: 4 + color: Style.current.secondaryText + font.weight: Font.Medium + font.pixelSize: 13 + anchors.verticalCenter: parent.verticalCenter + selectByMouse: true + } + } } - StatusIdenticon { - id: contactImage - height: 16 - width: 16 - chatId: wrapper.chatId - chatName: wrapper.name - chatType: wrapper.realChatType - identicon: wrapper.profileImage || wrapper.identicon - anchors.left: channelIcon.right - anchors.leftMargin: 4 - anchors.verticalCenter: parent.verticalCenter - letterSize: 11 - } + Component { + id: channelComponent - StyledText { - id: contactInfo - text: wrapper.realChatType !== Constants.chatTypePublic ? - Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(wrapper.name))) : - "#" + Utils.filterXSS(wrapper.name) - anchors.left: contactImage.right - anchors.leftMargin: 4 - color: Style.current.secondaryText - font.weight: Font.Medium - font.pixelSize: 13 - anchors.verticalCenter: parent.verticalCenter - } + Item { + width: childrenRect.width + height: parent.height + Connections { + enabled: realChatType === Constants.chatTypeOneToOne + target: profileModel.contacts.list + onContactChanged: { + if (pubkey === wrapper.chatId) { + wrapper.profileImage = appMain.getProfileImage(wrapper.chatId) + } + } + } + + SVGImage { + id: channelIcon + width: 16 + height: 16 + fillMode: Image.PreserveAspectFit + source: "../../../../img/channel-icon-" + (wrapper.realChatType === Constants.chatTypePublic ? "public-chat.svg" : "group.svg") + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.verticalCenter:parent.verticalCenter + } + + StatusIdenticon { + id: contactImage + height: 16 + width: 16 + chatId: wrapper.chatId + chatName: wrapper.name + chatType: wrapper.realChatType + identicon: wrapper.profileImage || wrapper.identicon + anchors.left: channelIcon.right + anchors.leftMargin: 4 + anchors.verticalCenter: parent.verticalCenter + letterSize: 11 + } + + StyledText { + id: contactInfo + text: wrapper.realChatType !== Constants.chatTypePublic ? + Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(wrapper.name))) : + "#" + Utils.filterXSS(wrapper.name) + anchors.left: contactImage.right + anchors.leftMargin: 4 + color: Style.current.secondaryText + font.weight: Font.Medium + font.pixelSize: 13 + anchors.verticalCenter: parent.verticalCenter + } + } + } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml index a8c8edab56..75ac8f88a4 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml @@ -17,7 +17,7 @@ Loader { property int nameMargin: 6 id: root - active: responseTo != "" && replyMessageIndex > -1 + active: responseTo !== "" && replyMessageIndex > -1 && !activityCenterMessage sourceComponent: Component { Item { @@ -141,19 +141,7 @@ Loader { Component.onCompleted: textFieldImplicitWidth = implicitWidth anchors.top: lblReplyAuthor.bottom anchors.topMargin: nameMargin - text: ``+ - ``+ - ``+ - `${Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), "26x26")}`+ - ``+ - `` + text: Utils.getReplyMessageStyle(Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), Emoji.size.small), isCurrentUser, appSettings.useCompactMode) textFormat: Text.RichText color: root.elementsColor readOnly: true diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml index fc4491acf8..c0c04c44d3 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml @@ -11,7 +11,7 @@ Item { property alias textField: chatText id: root - visible: contentType == Constants.messageType || isEmoji + visible: contentType === Constants.messageType || isEmoji z: 51 implicitHeight: visible ? (showMoreLoader.active ? childrenRect.height - 10 : chatText.height) : 0 diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml index f6909c9a98..768e81d4ad 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml @@ -24,10 +24,10 @@ StyledText { let yesterday = new Date() yesterday.setDate(now.getDate()-1) - var currentMsgDate = new Date(parseInt(messageTimestamp, 10)); - var prevMsgDate = previousMessageTimestamp === "" ? new Date(0) : new Date(parseInt(previousMessageTimestamp, 10)); + let currentMsgDate = new Date(parseInt(messageTimestamp, 10)); + let prevMsgDate = previousMessageTimestamp === "" ? undefined : new Date(parseInt(previousMessageTimestamp, 10)); - if (currentMsgDate.getDay() === prevMsgDate.getDay()) { + if (!!prevMsgDate && currentMsgDate.getDay() === prevMsgDate.getDay()) { return "" } diff --git a/ui/app/img/reply-corner.svg b/ui/app/img/reply-corner.svg deleted file mode 100644 index e37cc468a5..0000000000 --- a/ui/app/img/reply-corner.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/app/img/reply-small-arrow.svg b/ui/app/img/reply-small-arrow.svg new file mode 100644 index 0000000000..f415114863 --- /dev/null +++ b/ui/app/img/reply-small-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/imports/Constants.qml b/ui/imports/Constants.qml index 57d1a085c6..57e2961d73 100644 --- a/ui/imports/Constants.qml +++ b/ui/imports/Constants.qml @@ -13,6 +13,10 @@ QtObject { readonly property int communityChatInvitationOnlyAccess: 2 readonly property int communityChatOnRequestAccess: 3 + + readonly property int acitivtyCenterNotificationTypeMention: 3 + readonly property int acitivtyCenterNotificationTypeReply: 4 + readonly property int maxNbDaysToFetch: 30 readonly property int fetchRangeLast24Hours: 86400 readonly property int fetchRangeLast2Days: 172800 diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml index 8d1318e850..73f264cd09 100644 --- a/ui/imports/Utils.qml +++ b/ui/imports/Utils.qml @@ -89,6 +89,22 @@ QtObject { `${msg}` } + function getReplyMessageStyle(msg, isCurrentUser, useCompactMode) { + return ``+ + ``+ + ``+ + `${msg}`+ + ``+ + `` + } + function getAppSectionIndex(section) { let sectionId = -1 switch (section) {