diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index 9559e08cf2..37f6521d02 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -25,6 +25,7 @@ type ResponseTo = UserRole + 14 PlainText = UserRole + 15 Index = UserRole + 16 + ImageUrls = UserRole + 17 QtObject: type @@ -82,6 +83,7 @@ QtObject: of ChatMessageRoles.OutgoingStatus: result = newQVariant(message.outgoingStatus) of ChatMessageRoles.ResponseTo: result = newQVariant(message.responseTo) of ChatMessageRoles.Index: result = newQVariant(index.row) + of ChatMessageRoles.ImageUrls: result = newQVariant(message.imageUrls) method roleNames(self: ChatMessageList): Table[int, string] = { @@ -100,7 +102,8 @@ QtObject: ChatMessageRoles.Id.int: "messageId", ChatMessageRoles.OutgoingStatus.int: "outgoingStatus", ChatMessageRoles.ResponseTo.int: "responseTo", - ChatMessageRoles.Index.int: "index" + ChatMessageRoles.Index.int: "index", + ChatMessageRoles.ImageUrls.int: "imageUrls" }.toTable proc getMessageIndex(self: ChatMessageList, messageId: string): int {.slot.} = diff --git a/src/signals/messages.nim b/src/signals/messages.nim index 675d8193c7..fe1109cdf6 100644 --- a/src/signals/messages.nim +++ b/src/signals/messages.nim @@ -1,4 +1,4 @@ -import json, random, sequtils, sugar +import json, random, re, strutils, sequtils, sugar import json_serialization import ../status/libstatus/accounts as status_accounts import ../status/libstatus/settings as status_settings @@ -140,7 +140,12 @@ proc toTextItem*(jsonText: JsonNode): TextItem = proc toMessage*(jsonMsg: JsonNode): Message = - result = Message( + let + regex = re(r"(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|].(?:jpg|jpeg|gif|png|svg))", flags = {reStudy, reIgnoreCase}) + text = jsonMsg{"text"}.getStr + imageUrls = findAll(text, regex) + + var message = Message( alias: jsonMsg{"alias"}.getStr, chatId: jsonMsg{"localChatId"}.getStr, clock: jsonMsg{"clock"}.getInt, @@ -162,8 +167,14 @@ proc toMessage*(jsonMsg: JsonNode): Message = outgoingStatus: $jsonMsg{"outgoingStatus"}.getStr, isCurrentUser: $jsonMsg{"outgoingStatus"}.getStr == "sending" or $jsonMsg{"outgoingStatus"}.getStr == "sent", stickerHash: "", - parsedText: @[] + parsedText: @[], + imageUrls: "" ) + + if imageUrls.len > 0: + message.imageUrls = imageUrls.join(" ") + + result = message if jsonMsg["parsedText"].kind != JNull: for text in jsonMsg["parsedText"]: diff --git a/src/status/chat/message.nim b/src/status/chat/message.nim index fdd936cd4c..491637d16f 100644 --- a/src/status/chat/message.nim +++ b/src/status/chat/message.nim @@ -42,6 +42,7 @@ type Message* = object isCurrentUser*: bool stickerHash*: string outgoingStatus*: string + imageUrls*: string proc `$`*(self: Message): string = result = fmt"Message(id:{self.id}, chatId:{self.chatId}, clock:{self.clock}, from:{self.fromAuthor}, type:{self.contentType})" diff --git a/ui/app/AppLayouts/Chat/ChatColumn.qml b/ui/app/AppLayouts/Chat/ChatColumn.qml index 01ec9cdb72..121984ef30 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn.qml @@ -7,8 +7,10 @@ import "./components" import "./ChatColumn" StackLayout { + id: chatColumnLayout property int chatGroupsListViewCount: 0 property bool isReply: false + property var appSettings Layout.fillHeight: true Layout.fillWidth: true Layout.minimumWidth: 300 @@ -36,6 +38,7 @@ StackLayout { ChatMessages { id: chatMessages messageList: chatsModel.messageList + appSettings: chatColumnLayout.appSettings } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml index 2803aea7de..00af7984fc 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml @@ -11,6 +11,7 @@ ScrollView { id: scrollView property var messageList: MessagesData {} + property var appSettings property bool loadingMessages: false property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight @@ -21,11 +22,23 @@ ScrollView { ScrollBar.vertical.policy: chatLogView.contentHeight > chatLogView.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + function scrollToBottom(force, caller) { + if (!true && !chatLogView.atYEnd) { + // User has scrolled up, we don't want to scroll back + return + } + if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) { + // If we have a caller, only accept its request if it's the last message + return + } + Qt.callLater( chatLogView.positionViewAtEnd ) + } + ListView { + id: chatLogView anchors.fill: parent spacing: 4 boundsBehavior: Flickable.StopAtBounds - id: chatLogView Layout.fillWidth: true Layout.fillHeight: true @@ -36,17 +49,11 @@ ScrollView { } onActiveChannelChanged: { - Qt.callLater( chatLogView.positionViewAtEnd ) + scrollToBottom(true) } onMessagePushed: { - if (!chatLogView.atYEnd) { - // User has scrolled up, we don't want to scroll back - return - } - - if(chatLogView.atYEnd) - Qt.callLater( chatLogView.positionViewAtEnd ) + scrollToBottom() } onMessageNotificationPushed: function(chatId, msg) { @@ -141,6 +148,8 @@ ScrollView { } return -1; } + appSettings: scrollView.appSettings + scrollToBottom: scrollView.scrollToBottom } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index c2ec6ffdb3..3021c2ac1f 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -28,7 +28,7 @@ Item { property string authorPrevMsg: "authorPrevMsg" property bool isEmoji: contentType === Constants.emojiType - property bool isMessage: contentType === Constants.messageType || contentType === Constants.stickerType + property bool isMessage: contentType === Constants.messageType || contentType === Constants.stickerType property bool isStatusMessage: contentType === Constants.systemMessagePrivateGroupType property bool isSticker: contentType === Constants.stickerType @@ -37,12 +37,15 @@ Item { property string repliedMessageContent: replyMessageIndex > -1 ? chatsModel.messageList.getMessageData(replyMessageIndex, "message") : ""; property var profileClick: function () {} - + property var scrollToBottom: function () {} + property var appSettings width: parent.width + anchors.right: !isCurrentUser ? undefined : parent.right + id: messageWrapper height: { switch(contentType){ case Constants.chatIdentifier: - return parent.parent.height - 100 + return channelIdentifier.height + channelIdentifier.verticalMargin case Constants.stickerType: return stickerId.height + 50 + (dateGroupLbl.visible ? 50 : 0) default: @@ -51,11 +54,11 @@ Item { } function linkify(inputText) { - //URLs starting with http://, https://, or ftp:// + // URLs starting with http://, https://, or ftp:// var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; var replacedText = inputText.replace(replacePattern1, "$1"); - //URLs starting with "www." (without // before it, or it'd re-link the ones done above). + // URLs starting with "www." (without // before it, or it'd re-link the ones done above). var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; replacedText = replacedText.replace(replacePattern2, "$1$2"); @@ -68,10 +71,13 @@ Item { } Item { + property int verticalMargin: 50 id: channelIdentifier visible: authorCurrentMsg == "" anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter + anchors.top: parent.top + anchors.topMargin: this.visible ? verticalMargin : 0 + height: this.visible ? childrenRect.height + verticalMargin : 0 Rectangle { id: circleId @@ -171,7 +177,6 @@ Item { } } } - } // Private group Messages @@ -363,7 +368,7 @@ Item { StyledTextEdit { id: chatText - textFormat: TextEdit.RichText + textFormat: Text.RichText text: { if(contentType === Constants.stickerType) return ""; let msg = linkify(message); @@ -468,17 +473,16 @@ Item { let hours = messageDate.getHours(); return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes) } - anchors.top: chatBox.bottom + anchors.top: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? imageChatBox.bottom : chatBox.bottom anchors.topMargin: 4 anchors.bottomMargin: Style.current.padding - anchors.right: chatBox.right + anchors.right: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? imageChatBox.right : chatBox.right anchors.rightMargin: isCurrentUser ? 5 : Style.current.padding font.pixelSize: 10 readOnly: true selectByMouse: true visible: (isEmoji || isMessage || isSticker) } - StyledTextEdit { id: sentMessage @@ -496,22 +500,74 @@ Item { readOnly: true visible: isCurrentUser && (isEmoji || isMessage || isSticker) } - - // This rectangle's only job is to mask the corner to make it less rounded... yep + Rectangle { - // TODO find a way to show the corner for stickers since they have a border - visible: isMessage || isEmoji - color: chatBox.color - width: 18 - height: 18 - anchors.bottom: chatBox.bottom - anchors.bottomMargin: 0 - anchors.left: !isCurrentUser ? chatBox.left : undefined - anchors.leftMargin: 0 - anchors.right: !isCurrentUser ? undefined : chatBox.right - anchors.rightMargin: 0 - radius: 4 - z: -1 + property int chatVerticalPadding: 12 + property int chatHorizontalPadding: 12 + property int imageWidth: 350 + + id: imageChatBox + visible: messageWrapper.appSettings.displayChatImages && imageUrls != "" + height: { + if (!imageChatBox.visible) { + return 0 + } + + let h = chatVerticalPadding + for (let i = 0; i < imageRepeater.count; i++) { + h += imageRepeater.itemAt(i).height + } + return h + chatVerticalPadding * imageRepeater.count + } + color: isCurrentUser ? Style.current.blue : Style.current.lightBlue + border.color: "transparent" + width: imageWidth+ 2 * chatHorizontalPadding + radius: 16 + anchors.left: !isCurrentUser ? chatImage.right : undefined + anchors.leftMargin: !isCurrentUser ? 8 : 0 + anchors.right: !isCurrentUser ? undefined : parent.right + anchors.rightMargin: !isCurrentUser ? 0 : Style.current.padding + anchors.top: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? chatBox.bottom : chatTime.bottom + anchors.topMargin: Style.current.smallPadding + + Repeater { + id: imageRepeater + model: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? imageUrls.split(" ") : [] + + Image { + id: imageMessage + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: (index == 0) ? parent.top: parent.children[index-1].bottom + anchors.topMargin: imageChatBox.chatVerticalPadding + sourceSize.width: imageChatBox.imageWidth + source: modelData + onStatusChanged: { + if (imageMessage.status == Image.Error) { + imageMessage.height = 0 + imageMessage.visible = false + imageChatBox.height = 0 + imageChatBox.visible = false + } else if (imageMessage.status == Image.Ready) { + messageWrapper.scrollToBottom(true, messageWrapper) + } + } + } + } + + // This rectangle's only job is to mask the corner to make it less rounded... yep + Rectangle { + color: parent.color + width: 18 + height: 18 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: !isCurrentUser ? parent.left : undefined + anchors.leftMargin: 0 + anchors.right: !isCurrentUser ? undefined : parent.right + anchors.rightMargin: 0 + radius: 4 + z: -1 + } } } diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index b6fce881ef..dcc01d471c 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -24,6 +24,7 @@ SplitView { ChatColumn { id: chatColumn chatGroupsListViewCount: contactsColumn.chatGroupsListViewCount + appSettings: chatView.appSettings } } diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 0d8c5ebc71..292c2f03a6 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -50,7 +50,9 @@ SplitView { NotificationsContainer {} - AdvancedContainer {} + AdvancedContainer { + appSettings: profileView.appSettings + } HelpContainer {} diff --git a/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml b/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml index f85bf19f23..b1073033d7 100644 --- a/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml +++ b/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml @@ -5,6 +5,7 @@ import "../../../../imports" import "../../../../shared" Item { + property var appSettings id: advancedContainer width: 200 height: 200 @@ -96,6 +97,7 @@ Item { } RowLayout { + id: nodeTabSettings anchors.top: browserTabSettings.bottom anchors.topMargin: 20 anchors.left: parent.left @@ -115,6 +117,25 @@ Item { text: qsTrId("under-development") } } + + RowLayout { + anchors.top: nodeTabSettings.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 24 + StyledText { + text: qsTr("Display images in chat automatically") + } + Switch { + checked: appSettings.displayChatImages + onCheckedChanged: function(value) { + advancedContainer.appSettings.displayChatImages = this.checked + } + } + StyledText { + text: qsTr("under development") + } + } } /*##^## diff --git a/ui/main.qml b/ui/main.qml index 6bbe5e5817..5098c219a5 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -61,6 +61,7 @@ ApplicationWindow { property var chatSplitView property var walletSplitView property var profileSplitView + property bool displayChatImages: false } SystemTrayIcon {