From 6a0a75888bc00ad15dba6c7790986d16f3d5a7b6 Mon Sep 17 00:00:00 2001 From: emizzle Date: Fri, 11 Dec 2020 11:53:44 +1100 Subject: [PATCH] feat: whitelist gifs (no url extension needed) Fixes #1377. Fixes #1479. Two sites have been added to the whitelist: giphy.com and tenor.com. `imageUrls` in its entirety has been removed and instead all links are being handle through the message `linkUrls`. This prevents double-handling of urls that may or may not be images. The logic to automatically show links previews works like this: 1. If the setting "display chat images" is enabled, all links that *contain* ".png", ".jpg", ".jpeg", ".svg", ".gif" will be automatically shown. If the URL doesn't contain the extension, we are not downloading it. This was meant to be somewhat of a security compromise as we do not want to download each and every link posted in a message just to find out its true content type. 2. If the above setting is *disabled*, then we follow the whitelist settings for tenor and giphy. This allows us to preview gifs that do not have a file extension in their url. feat: bump status-go to the commit that supports the new whitelist (https://github.com/status-im/status-go/pull/2094), and also lets us get link preview data from urls in the whitelist. NOTE: this commit was branched off status-go `develop`, so once it is merged, and we update this PR to the new commit, we will effectively be getting status-go develop changes. We *could* base that status-go PR off of master if it makes things easier. fix: height on settings update issue feat: move date/time of message below links fix: layout issues when changing setting `neverAskAboutUnfurlingAgain` feat: Add MessageBorder component to aid in showing rounded corners with different radius --- src/app/chat/views/message_list.nim | 3 - src/app/profile/view.nim | 14 +- src/status/chat/message.nim | 1 - src/status/signals/messages.nim | 7 - .../Chat/ChatColumn/ChatMessages.qml | 1 - ui/app/AppLayouts/Chat/ChatColumn/Message.qml | 4 +- .../MessageComponents/ChatReply.qml | 1 + .../MessageComponents/CompactMessage.qml | 26 +- .../MessageComponents/ImageLoader.qml | 2 +- .../MessageComponents/ImageMessage.qml | 59 ---- .../MessageComponents/LinksMessage.qml | 270 ++++++++++-------- .../MessageComponents/MessageBorder.qml | 95 ++++++ .../MessageComponents/NormalMessage.qml | 36 +-- .../Profile/Sections/PrivacyContainer.qml | 67 ++--- ui/imports/Utils.qml | 11 + ui/main.qml | 38 ++- ui/nim-status-client.pro | 1 - vendor/status-go | 2 +- 18 files changed, 342 insertions(+), 296 deletions(-) delete mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageBorder.qml diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index ed7a1132ad..9a83727678 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -25,7 +25,6 @@ type ResponseTo = UserRole + 14 PlainText = UserRole + 15 Index = UserRole + 16 - ImageUrls = UserRole + 17 Timeout = UserRole + 18 Image = UserRole + 19 Audio = UserRole + 20 @@ -133,7 +132,6 @@ 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) of ChatMessageRoles.Timeout: result = newQVariant(self.timedoutMessages.contains(message.id)) of ChatMessageRoles.Image: result = newQVariant(message.image) of ChatMessageRoles.Audio: result = newQVariant(message.audio) @@ -172,7 +170,6 @@ QtObject: ChatMessageRoles.OutgoingStatus.int: "outgoingStatus", ChatMessageRoles.ResponseTo.int: "responseTo", ChatMessageRoles.Index.int: "index", - ChatMessageRoles.ImageUrls.int: "imageUrls", ChatMessageRoles.Timeout.int: "timeout", ChatMessageRoles.Image.int: "image", ChatMessageRoles.Audio.int: "audio", diff --git a/src/app/profile/view.nim b/src/app/profile/view.nim index a76c6604a3..72704f09f5 100644 --- a/src/app/profile/view.nim +++ b/src/app/profile/view.nim @@ -1,5 +1,6 @@ import NimQml, sequtils, strutils, sugar, os, json, chronicles import views/[mailservers_list, ens_manager, contacts, devices, mailservers, mnemonic, network, fleets, profile_info, device_list, dapp_list] +import chronicles import ../chat/views/channels_list import ../../status/profile/profile import ../../status/profile as status_profile @@ -15,6 +16,11 @@ import ../../status/libstatus/accounts/constants as accountConstants import qrcode/qrcode import ../utils/image_utils +logScope: + topics = "profile-view" + +const UNKNOWN_ACCOUNT = "unknownAccount" + QtObject: type ProfileView* = ref object of QObject profile*: ProfileInfoView @@ -71,7 +77,7 @@ QtObject: proc getProfileSettingsFile(self: ProfileView): string {.slot.} = let address = if (self.profile.address == ""): - "unknownAccount" + UNKNOWN_ACCOUNT else: self.profile.address @@ -90,6 +96,12 @@ QtObject: self.profile.setProfile(profile) self.profileChanged() self.profileSettingsFileChanged() + # Remove old 'unknownAccount' settings file if it was created + let unknownSettingsPath = os.joinPath(accountConstants.DATADIR, "qt", UNKNOWN_ACCOUNT) + if (not unknownSettingsPath.tryRemoveFile): + # Only fails if the file exists and an there was an error removing it + # More info: https://nim-lang.org/docs/os.html#tryRemoveFile%2Cstring + warn "Failed to remove unused settings file", file=unknownSettingsPath QtProperty[QVariant] profile: read = getProfile diff --git a/src/status/chat/message.nim b/src/status/chat/message.nim index 5404a88857..7a7bac12d7 100644 --- a/src/status/chat/message.nim +++ b/src/status/chat/message.nim @@ -57,7 +57,6 @@ type Message* = object isCurrentUser*: bool stickerHash*: string outgoingStatus*: string - imageUrls*: string linkUrls*: string image*: string audio*: string diff --git a/src/status/signals/messages.nim b/src/status/signals/messages.nim index 1f536c3a32..e1fec82a46 100644 --- a/src/status/signals/messages.nim +++ b/src/status/signals/messages.nim @@ -198,7 +198,6 @@ proc toMessage*(jsonMsg: JsonNode): Message = isCurrentUser: $jsonMsg{"outgoingStatus"}.getStr == "sending" or $jsonMsg{"outgoingStatus"}.getStr == "sent", stickerHash: "", parsedText: @[], - imageUrls: "", linkUrls: "", image: $jsonMsg{"image"}.getStr, audio: $jsonMsg{"audio"}.getStr, @@ -210,12 +209,6 @@ proc toMessage*(jsonMsg: JsonNode): Message = for text in jsonMsg["parsedText"]: message.parsedText.add(text.toTextItem) - message.imageUrls = concat(message.parsedText.map(t => t.children.filter(c => c.textType == "link"))) - .filter(t => [".png", ".jpg", ".jpeg", ".svg", ".gif"].any(ext => t.destination.endsWith(ext))) - .map(t => t.destination) - .join(" ") - - message.linkUrls = concat(message.parsedText.map(t => t.children.filter(c => c.textType == "link"))) .filter(t => t.destination.startsWith("http")) .map(t => t.destination) diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml index c8b5ca28a6..e1ba1e8e21 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml @@ -300,7 +300,6 @@ ScrollView { messageId: model.messageId emojiReactions: model.emojiReactions linkUrls: model.linkUrls - imageUrls: model.imageUrls 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 305b591476..01051be50e 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -24,7 +24,6 @@ Item { property int prevMessageIndex: -1 property bool timeout: false property string linkUrls: "" - property string imageUrls: "" property bool placeholderMessage: false property string authorCurrentMsg: "authorCurrentMsg" @@ -226,7 +225,6 @@ Item { NormalMessage { clickMessage: root.clickMessage linkUrls: root.linkUrls - imageUrls: root.imageUrls isCurrentUser: root.isCurrentUser contentType: root.contentType container: root @@ -247,7 +245,7 @@ Item { CompactMessage { clickMessage: root.clickMessage linkUrls: root.linkUrls - imageUrls: root.imageUrls + isCurrentUser: root.isCurrentUser contentType: root.contentType container: root } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml index 70aa8b41ab..79a3ec75fb 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml @@ -9,6 +9,7 @@ Loader { property bool longReply: false property color elementsColor: isCurrentUser ? Style.current.chatReplyCurrentUser : Style.current.secondaryText property var container + property int chatHorizontalPadding id: root active: responseTo != "" && replyMessageIndex > -1 diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml index 6bdfeb38be..a51e69e40b 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml @@ -6,11 +6,10 @@ Item { property var clickMessage: function () {} property int chatHorizontalPadding: 12 property int chatVerticalPadding: 7 - property string imageUrls: "" - property bool showImages: appSettings.displayChatImages && root.imageUrls != "" property string linkUrls: "" property int contentType: 2 property var container + property bool isCurrentUser: false id: root anchors.top: parent.top @@ -49,6 +48,7 @@ Item { anchors.right: parent.right anchors.rightMargin: root.chatHorizontalPadding container: root.container + chatHorizontalPadding: root.chatHorizontalPadding } ChatText { @@ -136,26 +136,6 @@ Item { anchors.rightMargin: 5 } - Loader { - id: imageLoader - active: root.showImages - anchors.left: chatText.left - anchors.leftMargin: 8 - anchors.top: chatText.bottom - - sourceComponent: Component { - ImageMessage { - color: Style.current.transparent - chatHorizontalPadding: 0 - imageUrls: root.imageUrls - onClicked: { - root.clickMessage(false, false, true, image) - } - container: root.container - } - } - } - Loader { id: linksLoader active: !!root.linkUrls @@ -166,6 +146,8 @@ Item { sourceComponent: Component { LinksMessage { linkUrls: root.linkUrls + container: root.container + isCurrentUser: root.isCurrentUser } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml index d6b1963c56..fd231f1695 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml @@ -9,7 +9,7 @@ Item { property bool isCurrentUser: false property url source property bool playing: true - property bool isAnimated: !!source && source.toString().endsWith('.gif') + property bool isAnimated: true signal clicked(var image) property var container diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml deleted file mode 100644 index 5e46075f30..0000000000 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml +++ /dev/null @@ -1,59 +0,0 @@ -import QtQuick 2.3 -import "../../../../../imports" - -Rectangle { - property int chatVerticalPadding: 12 - property int chatHorizontalPadding: 12 - property bool isCurrentUser: false - signal clicked(var image) - property var container - property string imageUrls: "" - - id: imageChatBox - height: { - let h = appSettings.compactMode ? 0 : chatVerticalPadding - for (let i = 0; i < imageRepeater.count; i++) { - h += imageRepeater.itemAt(i).height - } - return h + chatVerticalPadding * imageRepeater.count - } - color: "transparent" - border.color: "transparent" - width: { - let w = 0 - for (let i = 0; i < imageRepeater.count; i++) { - if (imageRepeater.itemAt(i).width > w) { - w = imageRepeater.itemAt(i).width - } - } - return w + 2 * chatHorizontalPadding - } - - radius: 16 - - Repeater { - id: imageRepeater - model: { - if (!root.imageUrls) { - return [] - } - - return root.imageUrls.split(" ") - } - - ImageLoader { - verticalPadding: imageChatBox.chatVerticalPadding - anchors.top: (index === 0) ? parent.top: parent.children[index-1].bottom - anchors.topMargin: verticalPadding - anchors.horizontalCenter: parent.horizontalCenter - source: modelData - isCurrentUser: imageChatBox.isCurrentUser - onClicked: { - imageChatBox.clicked(image) - } - container: imageChatBox.container - } - } - - RectangleCorner {} -} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml index 0e8311e97a..69d2d6784a 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml @@ -1,172 +1,186 @@ -import QtQuick 2.3 +import QtQuick 2.13 import QtGraphicalEffects 1.13 +import QtQuick.Layouts 1.13 import "../../../../../imports" import "../../../../../shared" import "../../../../../shared/status" +import "./" as MessageComponents import "../../../Profile/LeftTab/constants.js" as ProfileConstants -Item { +Column { id: root property string linkUrls: "" + property var container + property bool isCurrentUser: false + spacing: Style.current.halfPadding - height: { - let h = 0 - for (let i = 0; i < linksRepeater.count; i++) { - h += linksRepeater.itemAt(i).height - } - return h - } - width: { - let w = 0 - for (let i = 0; i < linksRepeater.count; i++) { - if (linksRepeater.itemAt(i).width > w) { - w = linksRepeater.itemAt(i).width + ListModel { + id: linksModel + Component.onCompleted: { + if (!root.linkUrls) { + return } + root.linkUrls.split(" ").forEach(link => { + linksModel.append({link}) + }) } - return w } Repeater { id: linksRepeater - model: { - if (!root.linkUrls) { - return [] - } - - return root.linkUrls.split(" ") - } - + model: linksModel // doesn't work with a JSON object model! delegate: Loader { - property string linkString: modelData - - // This connection is needed because since the white list is an array, when something in it changes, - // The whole object is still the same (reference), so the normal signal is not sent + id: linkMessageLoader + property var linkData + property int linkWidth: linksRepeater.width + active: true + Connections { - target: applicationWindow - onWhitelistChanged: { + target: appSettings + onWhitelistedUnfurlingSitesChanged: { + linkMessageLoader.sourceComponent = undefined + linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent() + } + onNeverAskAboutUnfurlingAgainChanged: { + linkMessageLoader.sourceComponent = undefined + linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent() + } + onDisplayChatImagesChanged: { + linkMessageLoader.sourceComponent = undefined linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent() } } function getSourceComponent() { - let linkExists = false - let linkWhiteListed = false - Object.keys(appSettings.whitelistedUnfurlingSites).some(function (site) { - // Check if our link contains the string part of the url - // TODO this might become not a reliable way to check since youtube has mutliple ways of being shown - if (modelData.includes(site)) { - linkExists = true - // check if it was enabled - linkWhiteListed = appSettings.whitelistedUnfurlingSites[site] === true - return true + // Reset the height in case we set it to 0 below. See note below + // for more information + this.height = undefined + if (appSettings.displayChatImages && Utils.hasImageExtension(link)) { + linkData = { + thumbnailUrl: link } - return - }) - - if (linkWhiteListed) { - return unfurledLinkComponent + return unfurledImageComponent } - if (linkExists && !appSettings.neverAskAboutUnfurlingAgain) { + let linkWhiteListed = false + const linkHostname = Utils.getHostname(link) + const linkExists = Object.keys(appSettings.whitelistedUnfurlingSites).some(function(whitelistedHostname) { + const exists = linkHostname.endsWith(whitelistedHostname) + if (exists) { + linkWhiteListed = appSettings.whitelistedUnfurlingSites[whitelistedHostname] === true + } + return exists + }) + if (!linkWhiteListed && linkExists && !appSettings.neverAskAboutUnfurlingAgain) { return enableLinkComponent } - - return + if (linkWhiteListed) { + const data = chatsModel.getLinkPreviewData(link) + linkData = JSON.parse(data) + if (linkData.error) { + console.error(linkData.error) + return undefined + } + if (linkData.contentType.startsWith("image/")) { + return unfurledImageComponent + } + if (linkData.site && linkData.title) { + linkData.address = link + return unfurledLinkComponent + } + } + // setting the height to 0 allows the "enable link" dialog to + // disappear correctly when appSettings.neverAskAboutUnfurlingAgain + // is true. The height is reset at the top of this method. + this.height = 0 + return undefined } + Component.onCompleted: { + // putting this is onCompleted prevents automatic binding, where + // QML warns of a binding loop detected + this.sourceComponent = getSourceComponent() + } + } + } - id: linkMessageLoader - active: true - sourceComponent: getSourceComponent() + Component { + id: unfurledImageComponent + + MessageBorder { + width: linkImage.width + height: linkImage.height + isCurrentUser: root.isCurrentUser + MessageComponents.ImageLoader { + id: linkImage + anchors.centerIn: parent + container: root.container + source: linkData.thumbnailUrl + imageWidth: 300 + isCurrentUser: root.isCurrentUser + } } } Component { id: unfurledLinkComponent - Loader { - property var linkData: { - const data = chatsModel.getLinkPreviewData(linkString) - const result = JSON.parse(data) - if (result.error) { - console.error(result.error) - return undefined - } - return result + MessageBorder { + width: linkImage.width + 2 + height: linkImage.height + (Style.current.smallPadding * 2) + linkTitle.height + 2 + linkSite.height + isCurrentUser: root.isCurrentUser + + MessageComponents.ImageLoader { + id: linkImage + container: root.container + source: linkData.thumbnailUrl + imageWidth: 300 + isCurrentUser: root.isCurrentUser + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 1 + } + StyledText { + id: linkTitle + text: linkData.title + font.pixelSize: 13 + font.weight: Font.Medium + elide: Text.ElideRight + anchors.left: parent.left + anchors.right: parent.right + anchors.top: linkImage.bottom + anchors.rightMargin: Style.current.smallPadding + anchors.leftMargin: Style.current.smallPadding + anchors.topMargin: Style.current.smallPadding } - active: linkData !== undefined && !!linkData.title - sourceComponent: Component { - Rectangle { - id: rectangle - width: 300 - height: childrenRect.height + Style.current.halfPadding - radius: 16 - clip: true - border.width: 1 - border.color: Style.current.border - color:Style.current.background - // TODO the clip doesnt seem to work. Find another way to have rounded corners and wait for designs - Image { - id: linkImage - source: linkData.thumbnailUrl - fillMode: Image.PreserveAspectFit - width: parent.width + StyledText { + id: linkSite + text: linkData.site + font.pixelSize: 12 + font.weight: Font.Thin + color: Style.current.secondaryText + anchors.top: linkTitle.bottom + anchors.topMargin: 2 + anchors.left: linkTitle.left + anchors.bottomMargin: Style.current.smallPadding + } - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Item { - width: linkImage.width - height: linkImage.height - Rectangle { - anchors.centerIn: parent - width: linkImage.width - height: linkImage.height - radius: 16 - } - } - } - } - - StyledText { - id: linkTitle - text: linkData.title - font.pixelSize: 13 - font.weight: Font.Medium - elide: Text.ElideRight - anchors.left: parent.left - anchors.right: parent.right - anchors.top: linkImage.bottom - anchors.rightMargin: Style.current.smallPadding - anchors.leftMargin: Style.current.smallPadding - anchors.topMargin: Style.current.smallPadding - } - - StyledText { - id: linkSite - text: linkData.site - font.pixelSize: 12 - font.weight: Font.Thin - color: Style.current.secondaryText - anchors.top: linkTitle.bottom - anchors.topMargin: 2 - anchors.left: linkTitle.left - } - - MouseArea { - anchors.top: linkImage.top - anchors.left: linkImage.left - anchors.right: linkImage.right - anchors.bottom: linkSite.bottom - cursorShape: Qt.PointingHandCursor - onClicked: Qt.openUrlExternally(linkString) - } - } + MouseArea { + anchors.top: linkImage.top + anchors.left: linkImage.left + anchors.right: linkImage.right + anchors.bottom: linkSite.bottom + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally(linkData.address) } } } + + Component { id: enableLinkComponent Rectangle { + id: enableLinkRoot width: 300 height: childrenRect.height + Style.current.smallPadding radius: 16 @@ -182,6 +196,10 @@ Item { anchors.topMargin: Style.current.smallPadding anchors.right: parent.right anchors.rightMargin: Style.current.smallPadding + onClicked: { + enableLinkRoot.height = 0 + enableLinkRoot.visible = false + } } Image { @@ -231,7 +249,6 @@ Item { profileLayoutContainer.changeProfileSection(ProfileConstants.PRIVACY_AND_SECURITY) } width: parent.width -// height: 43 anchors.top: sep1.bottom } @@ -248,7 +265,6 @@ Item { appSettings.neverAskAboutUnfurlingAgain = true } width: parent.width -// height: 43 anchors.top: sep2.bottom } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageBorder.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageBorder.qml new file mode 100644 index 0000000000..698c84c773 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageBorder.qml @@ -0,0 +1,95 @@ +import QtQuick 2.13 +import QtGraphicalEffects 1.13 +import QtQuick.Layouts 1.13 +import "./" as MessageComponents +import "../../../../../imports" +import "../../../../../shared" + +Item { + id: root + default property alias inner: contents.children + property bool isCurrentUser: false + readonly property int smallCorner: Style.current.radius / 2 + readonly property int bigCorner: Style.current.radius * 2 + readonly property int fakeCornerSize: bigCorner * 2 + + Rectangle { + width: parent.width + 2 + height: parent.height + 2 + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: -1 + anchors.leftMargin: -1 + radius: root.bigCorner + border.width: 2 + border.color: Style.current.border + } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.bottomMargin: -1 + anchors.leftMargin: -1 + width: root.fakeCornerSize + height: root.fakeCornerSize + radius: root.smallCorner + visible: !root.isCurrentUser + border.width: 2 + border.color: Style.current.border + } + Rectangle { + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.bottomMargin: -1 + anchors.rightMargin: -1 + width: root.fakeCornerSize + height: root.fakeCornerSize + radius: root.smallCorner + visible: root.isCurrentUser + border.width: 2 + border.color: Style.current.border + } + + Rectangle { + anchors.fill: parent + color: Style.current.background + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Item { + width: root.width + height: root.height + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + width: parent.width + height: parent.height + radius: root.bigCorner + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + width: root.fakeCornerSize + height: root.fakeCornerSize + radius: root.smallCorner + visible: !root.isCurrentUser + } + Rectangle { + anchors.bottom: parent.bottom + anchors.right: parent.right + width: root.fakeCornerSize + height: root.fakeCornerSize + radius: root.smallCorner + visible: root.isCurrentUser + } + } + } + + Item { + id: contents + width: root.width + height: root.height + } + } +} \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml index a8bd96690a..31b444f820 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml @@ -4,8 +4,6 @@ import "../../../../../imports" Item { property var clickMessage: function () {} - property string imageUrls: "" - property bool showImages: appSettings.displayChatImages && root.imageUrls !== "" property string linkUrls: "" property bool isCurrentUser: false property int contentType: 2 @@ -127,6 +125,7 @@ Item { anchors.right: parent.right anchors.rightMargin: chatBox.chatHorizontalPadding container: root.container + chatHorizontalPadding: chatBox.chatHorizontalPadding } ChatText { @@ -210,10 +209,10 @@ Item { ChatTime { id: chatTime - anchors.top: root.showImages ? imageLoader.bottom : chatBox.bottom + anchors.top: linksLoader.active ? linksLoader.bottom : chatBox.bottom anchors.topMargin: 4 anchors.bottomMargin: Style.current.padding - anchors.right: root.showImages ? imageLoader.right : chatBox.right + anchors.right: linksLoader.active ? linksLoader.right : chatBox.right anchors.rightMargin: root.isCurrentUser ? 5 : Style.current.padding } @@ -234,30 +233,6 @@ Item { anchors.bottomMargin: Style.current.padding } - Loader { - id: imageLoader - active: root.showImages - sourceComponent: imageComponent - anchors.left: !root.isCurrentUser ? chatImage.right : undefined - anchors.leftMargin: !root.isCurrentUser ? 8 : 0 - anchors.right: !root.isCurrentUser ? undefined : parent.right - anchors.rightMargin: !root.isCurrentUser ? 0 : Style.current.padding - anchors.top: chatBox.bottom - anchors.topMargin: Style.current.smallPadding - } - - Component { - id: imageComponent - ImageMessage { - isCurrentUser: root.isCurrentUser - container: root.container - imageUrls: root.imageUrls - onClicked: { - root.clickMessage(false, false, true, image) - } - } - } - Loader { id: linksLoader active: !!root.linkUrls @@ -266,11 +241,14 @@ Item { anchors.right: !root.isCurrentUser ? undefined : parent.right anchors.rightMargin: !root.isCurrentUser ? 0 : Style.current.padding anchors.top: chatBox.bottom - anchors.topMargin: Style.current.smallPadding + anchors.topMargin: Style.current.halfPadding + anchors.bottomMargin: Style.current.halfPadding sourceComponent: Component { LinksMessage { linkUrls: root.linkUrls + container: root.container + isCurrentUser: root.isCurrentUser } } } diff --git a/ui/app/AppLayouts/Profile/Sections/PrivacyContainer.qml b/ui/app/AppLayouts/Profile/Sections/PrivacyContainer.qml index 08c6abef14..86570e6aa6 100644 --- a/ui/app/AppLayouts/Profile/Sections/PrivacyContainer.qml +++ b/ui/app/AppLayouts/Profile/Sections/PrivacyContainer.qml @@ -16,27 +16,6 @@ Item { id: previewableSites } - Component.onCompleted: { - const sites = profileModel.getLinkPreviewWhitelist() - try { - const sitesJSON = JSON.parse(sites) - let settingUpdadted = false - sitesJSON.forEach(function (site) { - if (appSettings.whitelistedUnfurlingSites[site.address] === undefined) { - appSettings.whitelistedUnfurlingSites[site.address] = false - settingUpdadted = true - } - - previewableSites.append(site) - }) - if (settingUpdadted) { - applicationWindow.whitelistChanged() - } - } catch (e) { - console.error('Could not parse the whitelist for sites', e) - } - } - property Component dappListPopup: DappList { onClosed: destroy() } @@ -153,23 +132,34 @@ Item { //% "Privacy" text: qsTrId("privacy") } - RowLayout { - id: displayImageSettings - StyledText { - //% "Display images in chat automatically" - text: qsTrId("display-images-in-chat-automatically") + spacing: Style.current.padding + width: parent.width + Column { + Layout.fillWidth: true + StyledText { + //% "Display images in chat automatically" + text: qsTrId("display-images-in-chat-automatically") + font.pixelSize: 15 + font.weight: Font.Medium + } + StyledText { + width: parent.width + text: qsTr("All images (links that contain an image extension) will be downloaded and displayed, regardless of the whitelist settings below") + font.pixelSize: 15 + font.weight: Font.Thin + color: Style.current.secondaryText + wrapMode: Text.WordWrap + } } StatusSwitch { + id: displayChatImagesSwitch + Layout.rightMargin: 0 checked: appSettings.displayChatImages onCheckedChanged: function (value) { appSettings.displayChatImages = this.checked } } - StyledText { - //% "under development" - text: qsTrId("under-development") - } } StatusSectionHeadline { @@ -185,6 +175,17 @@ Item { text: qsTr("Websites") } + Connections { + target: applicationWindow + onSettingsLoaded: { + let whitelist = JSON.parse(profileModel.getLinkPreviewWhitelist()) + whitelist.forEach(entry => { + entry.isWhitelisted = appSettings.whitelistedUnfurlingSites[entry.address] || false + previewableSites.append(entry) + }) + } + } + ListView { id: sitesListView width: parent.width @@ -214,9 +215,11 @@ Item { } StatusSwitch { - checked: !!appSettings.whitelistedUnfurlingSites[address] + checked: !!isWhitelisted onCheckedChanged: function () { - changeUnfurlingWhitelist(address, this.checked) + const settings = appSettings.whitelistedUnfurlingSites + settings[address] = this.checked + appSettings.whitelistedUnfurlingSites = settings } anchors.verticalCenter: siteTitle.bottom anchors.right: parent.right diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml index e0b3f6639a..186eec74ae 100644 --- a/ui/imports/Utils.qml +++ b/ui/imports/Utils.qml @@ -222,4 +222,15 @@ QtObject { return [false, ""]; } } + + function getHostname(url) { + const rgx = /\:\/\/(?:[a-zA-Z0-9\-]*\.{1,}){1,}[a-zA-Z0-9]*/i + const matches = rgx.exec(url) + if (!matches || !matches.length) return "" + return matches[0].substring(3) + } + + function hasImageExtension(url) { + return [".png", ".jpg", ".jpeg", ".svg", ".gif"].some(ext => url.includes(ext)) + } } diff --git a/ui/main.qml b/ui/main.qml index e07544c941..27dc676337 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -162,20 +162,42 @@ ApplicationWindow { property bool compatibilityMode: defaultAppSettings.compatibilityMode } - signal whitelistChanged() - - function changeUnfurlingWhitelist(site, enabled) { - appSettings.whitelistedUnfurlingSites[site] = enabled - applicationWindow.whitelistChanged() - } - Connections { target: profileModel onProfileSettingsFileChanged: { - settingsLoaded() if (appSettings.locale !== "en") { profileModel.changeLocale(appSettings.locale) } + const whitelist = profileModel.getLinkPreviewWhitelist() + try { + const whiteListedSites = JSON.parse(whitelist) + let settingsUpdated = false + const settings = appSettings.whitelistedUnfurlingSites + const whitelistedHostnames = [] + + // Add whitelisted sites in to app settings that are not already there + whiteListedSites.forEach(site => { + if (!settings.hasOwnProperty(site.address)) { + settings[site.address] = false + settingsUpdated = true + } + whitelistedHostnames.push(site.address) + }) + // Remove any whitelisted sites from app settings that don't exist in the + // whitelist from status-go + Object.keys(settings).forEach(settingsHostname => { + if (!whitelistedHostnames.includes(settingsHostname)) { + delete settings[settingsHostname] + settingsUpdated = true + } + }) + if (settingsUpdated) { + appSettings.whitelistedUnfurlingSites = settings + } + } catch (e) { + console.error('Could not parse the whitelist for sites', e) + } + applicationWindow.settingsLoaded() } } diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index e196d4e396..31ef38c6b8 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -150,7 +150,6 @@ DISTFILES += \ app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml \ app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml \ app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml \ - app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml \ app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml \ app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml \ app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml \ diff --git a/vendor/status-go b/vendor/status-go index cdca42b90f..149877a939 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit cdca42b90f7fd5f302a5eb31802b9ae526d566d7 +Subproject commit 149877a939656f3780fb73f5add26e5e205cf26f