mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-10 06:16:32 +00:00
f9f860a215
LinksMessageView component will receive the urls from nim as string and it will only forward the string to getLinkPreviewData slot implemented in nim together with some settings (supported img extensions and unfurling preferences) On nim side the urls will be parsed and validated using the settings received from qml. Images are now validated before sending them to the UI using the HEAD request.
390 lines
15 KiB
QML
390 lines
15 KiB
QML
import QtQuick 2.15
|
|
import QtGraphicalEffects 1.13
|
|
import QtQuick.Layouts 1.13
|
|
|
|
import utils 1.0
|
|
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Controls 0.1
|
|
|
|
import shared.status 1.0
|
|
import shared.panels 1.0
|
|
import shared.stores 1.0
|
|
import shared.controls.chat 1.0
|
|
|
|
Column {
|
|
id: root
|
|
|
|
property var store
|
|
property var messageStore
|
|
property var container
|
|
|
|
//receiving space separated url list
|
|
property string links: ""
|
|
readonly property alias unfurledLinksCount: d.unfurledLinksCount
|
|
readonly property alias unfurledImagesCount: d.unfurledImagesCount
|
|
property bool isCurrentUser: false
|
|
|
|
signal imageClicked(var image)
|
|
signal linksLoaded()
|
|
|
|
spacing: 4
|
|
|
|
Repeater {
|
|
id: linksRepeater
|
|
model: linksModel
|
|
delegate: Loader {
|
|
id: linkMessageLoader
|
|
required property var result
|
|
required property string link
|
|
required property int index
|
|
required property bool unfurl
|
|
required property bool success
|
|
required property bool isStatusDeepLink
|
|
readonly property bool isImage: result.contentType ? result.contentType.startsWith("image/") : false
|
|
readonly property bool neverAskAboutUnfurlingAgain: RootStore.neverAskAboutUnfurlingAgain
|
|
|
|
active: success
|
|
asynchronous: true
|
|
StateGroup {
|
|
//Using StateGroup as a warkardound for https://bugreports.qt.io/browse/QTBUG-47796
|
|
id: linkPreviewLoaderState
|
|
states:[
|
|
State {
|
|
name: "neverAskAboutUnfurling"
|
|
when: !unfurl && neverAskAboutUnfurlingAgain
|
|
PropertyChanges { target: linkMessageLoader; sourceComponent: undefined; }
|
|
StateChangeScript { name: "removeFromModel"; script: linksModel.remove(index)}
|
|
},
|
|
State {
|
|
name: "askToEnableUnfurling"
|
|
when: !unfurl && !neverAskAboutUnfurlingAgain
|
|
PropertyChanges { target: linkMessageLoader; sourceComponent: enableLinkComponent }
|
|
},
|
|
State {
|
|
name: "loadImage"
|
|
when: unfurl && isImage
|
|
PropertyChanges { target: linkMessageLoader; sourceComponent: unfurledImageComponent }
|
|
},
|
|
State {
|
|
name: "loadLinkPreview"
|
|
when: unfurl && !isImage && !isStatusDeepLink
|
|
PropertyChanges { target: linkMessageLoader; sourceComponent: unfurledLinkComponent }
|
|
},
|
|
State {
|
|
name: "statusInvitation"
|
|
when: unfurl && isStatusDeepLink
|
|
PropertyChanges { target: linkMessageLoader; sourceComponent: invitationBubble }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
property bool hasImageLink: false
|
|
property int unfurledLinksCount: 0
|
|
property int unfurledImagesCount: 0
|
|
readonly property string uuid: Utils.uuid()
|
|
readonly property string whiteListedImgExtensions: Constants.acceptedImageExtensions.toString()
|
|
readonly property string whiteListedUrls: JSON.stringify(localAccountSensitiveSettings.whitelistedUnfurlingSites)
|
|
readonly property string getLinkPreviewDataId: messageStore.messageModule.getLinkPreviewData(root.links, d.uuid, whiteListedUrls, whiteListedImgExtensions, localAccountSensitiveSettings.displayChatImages)
|
|
onGetLinkPreviewDataIdChanged: { linkFetchConnections.enabled = true }
|
|
}
|
|
|
|
Connections {
|
|
id: linkFetchConnections
|
|
enabled: false
|
|
target: root.messageStore.messageModule
|
|
function onLinkPreviewDataWasReceived(previewData, uuid) {
|
|
if(d.uuid != uuid) return
|
|
linkFetchConnections.enabled = false
|
|
try { linksModel.rawData = JSON.parse(previewData) }
|
|
catch(e) { console.warn("error parsing link preview data", previewData) }
|
|
}
|
|
}
|
|
|
|
ListModel {
|
|
id: linksModel
|
|
property var rawData
|
|
onRawDataChanged: {
|
|
linksModel.clear()
|
|
rawData.links.forEach((link) => {
|
|
linksModel.append(link)
|
|
})
|
|
root.linksLoaded()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: unfurledImageComponent
|
|
|
|
MessageBorder {
|
|
width: linkImage.width
|
|
height: linkImage.height
|
|
isCurrentUser: root.isCurrentUser
|
|
|
|
StatusChatImageLoader {
|
|
id: linkImage
|
|
readonly property bool globalAnimationEnabled: root.messageStore.playAnimation
|
|
property bool localAnimationEnabled: true
|
|
objectName: "LinksMessageView_unfurledImageComponent_linkImage"
|
|
anchors.centerIn: parent
|
|
container: root.container
|
|
source: result.thumbnailUrl
|
|
imageWidth: 300
|
|
isCurrentUser: root.isCurrentUser
|
|
playing: globalAnimationEnabled && localAnimationEnabled
|
|
isOnline: root.store.mainModuleInst.isOnline
|
|
asynchronous: true
|
|
isAnimated: result.contentType ? result.contentType.toLowerCase().endsWith("gif") : false
|
|
onClicked: isAnimated && !playing ? localAnimationEnabled = true : root.imageClicked(linkImage.imageAlias)
|
|
Loader {
|
|
width: 45
|
|
height: 38
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 12
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 12
|
|
active: linkImage.isAnimated && !linkImage.playing
|
|
sourceComponent: Item {
|
|
anchors.fill: parent
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: "black"
|
|
radius: Style.current.radius
|
|
opacity: .4
|
|
}
|
|
StatusBaseText {
|
|
anchors.centerIn: parent
|
|
text: "GIF"
|
|
font.pixelSize: 13
|
|
}
|
|
}
|
|
}
|
|
Timer {
|
|
id: animationPlayingTimer
|
|
interval: 10000
|
|
running: linkImage.isAnimated && linkImage.playing
|
|
onTriggered: { linkImage.localAnimationEnabled = false }
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: d.unfurledImagesCount++
|
|
Component.onDestruction: d.unfurledImagesCount--
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: invitationBubble
|
|
InvitationBubbleView {
|
|
property var invitationData: root.store.getLinkDataForStatusLinks(link)
|
|
onInvitationDataChanged: { if(!invitationData) linksModel.remove(index) }
|
|
store: root.store
|
|
communityId: invitationData ? invitationData.communityId : ""
|
|
anchors.left: parent.left
|
|
visible: invitationData && !invitationData.fetching
|
|
|
|
Connections {
|
|
enabled: invitationData && invitationData.fetching
|
|
target: root.store.communitiesModuleInst
|
|
function onCommunityAdded(communityId: string) {
|
|
if (communityId !== invitationData.communityId) return
|
|
invitationData = root.store.getLinkDataForStatusLinks(link)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: unfurledLinkComponent
|
|
MessageBorder {
|
|
id: unfurledLink
|
|
width: linkImage.visible ? linkImage.width + 2 : 300
|
|
height: {
|
|
if (linkImage.visible) {
|
|
return linkImage.height + (Style.current.smallPadding * 2) + (linkTitle.height + 2 + linkSite.height)
|
|
}
|
|
return (Style.current.smallPadding * 2) + linkTitle.height + 2 + linkSite.height
|
|
}
|
|
isCurrentUser: root.isCurrentUser
|
|
|
|
StatusChatImageLoader {
|
|
id: linkImage
|
|
objectName: "LinksMessageView_unfurledLinkComponent_linkImage"
|
|
container: root.container
|
|
source: result.thumbnailUrl
|
|
visible: result.thumbnailUrl.length
|
|
readonly property int previewWidth: parseInt(result.width)
|
|
imageWidth: Math.min(300, previewWidth > 0 ? previewWidth : 300)
|
|
isCurrentUser: root.isCurrentUser
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
isOnline: root.store.mainModuleInst.isOnline
|
|
asynchronous: true
|
|
onClicked: {
|
|
if (!!result.callback) {
|
|
return result.callback()
|
|
}
|
|
Global.openLink(result.address)
|
|
}
|
|
}
|
|
|
|
StatusBaseText {
|
|
id: linkTitle
|
|
text: result.title
|
|
font.pixelSize: 13
|
|
font.weight: Font.Medium
|
|
wrapMode: Text.Wrap
|
|
anchors.top: linkImage.visible ? linkImage.bottom : parent.top
|
|
anchors.topMargin: Style.current.smallPadding
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.leftMargin: Style.current.smallPadding
|
|
anchors.rightMargin: Style.current.smallPadding
|
|
}
|
|
|
|
StatusBaseText {
|
|
id: linkSite
|
|
text: result.site
|
|
font.pixelSize: 12
|
|
font.weight: Font.Thin
|
|
color: Theme.palette.baseColor1
|
|
anchors.top: linkTitle.bottom
|
|
anchors.topMargin: 2
|
|
anchors.left: linkTitle.left
|
|
anchors.bottomMargin: Style.current.halfPadding
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.top: linkImage.visible ? linkImage.top : linkTitle.top
|
|
anchors.left: linkImage.visible ? linkImage.left : linkTitle.left
|
|
anchors.right: linkImage.visible ? linkImage.right : linkTitle.right
|
|
anchors.bottom: linkSite.bottom
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
if (!!result.callback) {
|
|
return result.callback()
|
|
}
|
|
Global.openLink(link)
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: d.unfurledLinksCount++
|
|
Component.onDestruction: d.unfurledLinksCount--
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: enableLinkComponent
|
|
Rectangle {
|
|
id: enableLinkRoot
|
|
width: 300
|
|
height: childrenRect.height + Style.current.smallPadding
|
|
radius: 16
|
|
border.width: 1
|
|
border.color: Style.current.border
|
|
color: Style.current.background
|
|
|
|
StatusFlatRoundButton {
|
|
anchors.top: parent.top
|
|
anchors.topMargin: Style.current.smallPadding
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Style.current.smallPadding
|
|
icon.width: 20
|
|
icon.height: 20
|
|
icon.name: "close-circle"
|
|
onClicked: linksModel.remove(index)
|
|
}
|
|
|
|
Image {
|
|
id: unfurlingImage
|
|
source: Style.png("unfurling-image")
|
|
width: 132
|
|
height: 94
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: Style.current.smallPadding
|
|
}
|
|
|
|
StatusBaseText {
|
|
id: enableText
|
|
text: isImage ? qsTr("Enable automatic image unfurling") :
|
|
qsTr("Enable link previews in chat?")
|
|
horizontalAlignment: Text.AlignHCenter
|
|
width: parent.width
|
|
wrapMode: Text.WordWrap
|
|
anchors.top: unfurlingImage.bottom
|
|
anchors.topMargin: Style.current.halfPadding
|
|
color: Theme.palette.directColor1
|
|
}
|
|
|
|
StatusBaseText {
|
|
id: infoText
|
|
text: qsTr("Once enabled, links posted in the chat may share your metadata with their owners")
|
|
horizontalAlignment: Text.AlignHCenter
|
|
width: parent.width
|
|
wrapMode: Text.WordWrap
|
|
anchors.top: enableText.bottom
|
|
font.pixelSize: 13
|
|
color: Theme.palette.baseColor1
|
|
}
|
|
|
|
Separator {
|
|
id: sep1
|
|
anchors.top: infoText.bottom
|
|
anchors.topMargin: Style.current.smallPadding
|
|
}
|
|
|
|
StatusFlatButton {
|
|
id: enableBtn
|
|
objectName: "LinksMessageView_enableBtn"
|
|
text: qsTr("Enable in Settings")
|
|
onClicked: {
|
|
Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.messaging);
|
|
}
|
|
width: parent.width
|
|
anchors.top: sep1.bottom
|
|
Component.onCompleted: {
|
|
background.radius = 0;
|
|
}
|
|
}
|
|
|
|
Separator {
|
|
id: sep2
|
|
anchors.top: enableBtn.bottom
|
|
anchors.topMargin: 0
|
|
}
|
|
|
|
Item {
|
|
width: parent.width
|
|
height: 44
|
|
anchors.top: sep2.bottom
|
|
clip: true
|
|
StatusFlatButton {
|
|
id: dontAskBtn
|
|
width: parent.width
|
|
height: (parent.height+Style.current.padding)
|
|
anchors.top: parent.top
|
|
anchors.topMargin: -Style.current.padding
|
|
contentItem: Item {
|
|
StatusBaseText {
|
|
anchors.centerIn: parent
|
|
anchors.verticalCenterOffset: Style.current.halfPadding
|
|
font: dontAskBtn.font
|
|
color: dontAskBtn.enabled ? dontAskBtn.textColor : dontAskBtn.disabledTextColor
|
|
text: qsTr("Don't ask me again")
|
|
}
|
|
}
|
|
onClicked: RootStore.setNeverAskAboutUnfurlingAgain(true)
|
|
Component.onCompleted: {
|
|
background.radius = Style.current.padding;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|