feat(LinkPreviews): Integrate Link previews with the backend (#12523)

* feat(StatusQ): Adding numberToLocaleStringInCompactForm function to LocaleUtils

This function will format the number in a compact form
E.g: 1000 -> 1K; 1000000 -> 1M; 1100000 -> 1.1M

+ adding tests

fix(statusQ): Update numberToLocaleStringInCompactForm to return the locale number when greter than 999T

fix(StatusQ): extend the test_numberToLocaleStringInCompactForm with new data

* feat(LinkPreviews): Update the link preview area in StatusChatInput to use the new model

Changes:
1. Create a new component `LinkPreviewMiniCardDelegate.qml` that filters the model data to properly fill the link preview card with the needed data based on the preview type
2. Update storybook pages
3. Small updates to LinkPreviewMiniCard

* feat(LinkPreviews): Update the link previews in message history to use the new backend

Changes:
1. Create delegate items for LinkPreviewCard and gif link preview to clean the LinksMessageView component and filter the model data based on the preview type
2. Remove UserProfileCard and reuse the LinkPreviewCard to display contacts link previews
3. Update LinkPreviewCard so that it can accommodate status link previews (communities, channels, contacts). The generic properties (title, description, footer) have been dropped and replaced with specialised properties for each preview type.
4. Fix LinkPreviewCard layout to better accommodate different content variants (missing properties, long/short title, missing description, missing icon)
5. Fixing the link preview context menu and click actions

fix: Move inline components to separate files

Fixing the linux builds using Qt 5.15.2 affected by this bug:
https://bugreports.qt.io/browse/QTBUG-89180

* fix: Align LinkPreviewMiniCard implementation with LinkPreviewCard and remove state based model filtering
This commit is contained in:
Alex Jbanca 2023-10-25 18:20:02 +03:00 committed by GitHub
parent 5c4dd60f1e
commit 4a30d13bdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1272 additions and 821 deletions

View File

@ -4,6 +4,7 @@ import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Storybook 1.0
import Models 1.0
import StatusQ.Core.Theme 0.1
import shared.controls.chat 1.0
@ -30,7 +31,7 @@ SplitView {
anchors.centerIn: parent
width: parent.width
imagePreviewArray: ["https://picsum.photos/200/300?random=1", "https://picsum.photos/200/300?random=1"]
linkPreviewModel: showLinkPreviewSettings ? emptyModel : linkPreviewListModel
linkPreviewModel: showLinkPreviewSettings ? emptyModel : mockedLinkPreviewModel
showLinkPreviewSettings: !linkPreviewEnabledSwitch.checked
visible: hasContent
@ -78,123 +79,8 @@ SplitView {
id: emptyModel
}
ListModel {
id: linkPreviewListModel
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
immutable: false
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=1"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: false
immutable: false
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=2"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
immutable: false
hostname: ""
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=3"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=4"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
immutable: false
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=5"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=6"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
immutable: false
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=7"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
immutable: false
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=8"
thumbnailDataUri: ""
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
immutable: false
hostname: "youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: ""
linkType: 0
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://picsum.photos/480/360?random=9"
thumbnailDataUri: ""
}
LinkPreviewModel {
id: mockedLinkPreviewModel
}
}

View File

@ -2,39 +2,145 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 1.0
import StatusQ.Core.Theme 0.1
import shared.controls.chat 1.0
import utils 1.0
SplitView {
id: root
property alias logoSettings: previewCard.logoSettings
property string ytBannerQuality: "hqdefault"
property string image: Style.png("tokens/SOCKS")
property string banner: rawImageCheck.checked ? rawImageCheck.rawImageData : "https://img.youtube.com/vi/yHN1M7vcPKU/%1.jpg".arg(root.ytBannerQuality)
property bool globalUtilsReady: false
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
// globalUtilsInst mock
QtObject {
function getEmojiHashAsJson(publicKey) {
return JSON.stringify(["👨🏻‍🍼", "🏃🏿‍♂️", "🌇", "🤶🏿", "🏮","🤷🏻‍♂️", "🤦🏻", "📣", "🤎", "👷🏽", "😺", "🥞", "🔃", "🧝🏽‍♂️"])
}
function getColorId(publicKey) { return 4 }
LinkPreviewCard {
id: previewCard
bannerImageSource: "https://img.youtube.com/vi/yHN1M7vcPKU/%1.jpg".arg(root.ytBannerQuality)
title: titleInput.text
description: descriptionInput.text
footer: footerInput.text
logoSettings.name: Style.png("tokens/SOCKS")
logoSettings.isImage: true
logoSettings.isLetterIdenticon: false
function getCompressedPk(publicKey) { return "zx3sh" + publicKey }
function getColorHashAsJson(publicKey) {
return JSON.stringify([{4: 0, segmentLength: 1},
{5: 19, segmentLength: 2}])
}
function isCompressedPubKey(publicKey) { return true }
Component.onCompleted: {
Utils.globalUtilsInst = this
root.globalUtilsReady = true
}
Component.onDestruction: {
root.globalUtilsReady = false
Utils.globalUtilsInst = {}
}
}
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
LinkPreviewCard {
id: previewCard
type: 1
linkData {
title: titleInput.text
description: descriptionInput.text
domain: footerInput.text
thumbnail: root.banner
image: root.image
}
userData {
name: userNameInput.text
publicKey: "zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
bio: bioInput.text
image: root.image
ensVerified: false
}
communityData {
name: titleInput.text
description: descriptionInput.text
banner: root.banner
image: root.image
membersCount: parseInt(membersCountInput.text)
activeMembersCount: parseInt(activeMembersCountInput.text)
color: "orchid"
}
channelData {
name: titleInput.text
description: descriptionInput.text
emoji: ""
color: "blue"
communityData {
name: "Community" + titleInput.text
description: "Community" + descriptionInput.text
banner: root.banner
image: root.image
membersCount: parseInt(membersCountInput.text)
activeMembersCount: parseInt(activeMembersCountInput.text)
color: "orchid"
}
}
}
}
ScrollView {
SplitView.preferredWidth: 500
SplitView.fillHeight: true
leftPadding: 10
ColumnLayout {
id: layout
spacing: 24
ColumnLayout {
Label {
Layout.fillWidth: true
text: "Card type"
}
RadioButton {
text: qsTr("Link")
checked: previewCard.type === Constants.LinkPreviewType.Standard
onToggled: previewCard.type = Constants.LinkPreviewType.Standard
}
RadioButton {
text: qsTr("Contact")
checked: previewCard.type === Constants.LinkPreviewType.StatusContact
onToggled: previewCard.type = Constants.LinkPreviewType.StatusContact
}
RadioButton {
text: qsTr("Community")
checked: previewCard.type === Constants.LinkPreviewType.StatusCommunity
onToggled: {
previewCard.type = Constants.LinkPreviewType.StatusCommunity
titleInput.text = "Socks"
descriptionInput.text = "Community description goes here. If blank it will enable multi line title."
}
}
RadioButton {
text: qsTr("Channel")
checked: previewCard.type === Constants.LinkPreviewType.StatusCommunityChannel
onToggled: {
previewCard.type = Constants.LinkPreviewType.StatusCommunityChannel
titleInput.text = "general"
descriptionInput.text = "Channel description goes here. If blank it will enable multi line title."
}
}
}
ColumnLayout {
visible: previewCard.type !== Constants.LinkPreviewType.StatusContact
Label {
text: "Title"
}
@ -45,7 +151,7 @@ SplitView {
Layout.fillWidth: true
text: "What Is Web3? A Decentralized Internet Via Blockchain Technology That Will Revolutionise All Sectors- Decrypt (@decryptmedia) August 31 2021"
}
Label {
text: "Description"
}
@ -65,77 +171,125 @@ SplitView {
onClicked: descriptionInput.text = "Link description goes here. If blank it will enable multi line title."
}
}
ColumnLayout {
visible: previewCard.type === Constants.LinkPreviewType.Standard
Label {
text: "Footer"
}
TextField {
id: footerInput
Layout.fillHeight: true
Layout.fillWidth: true
text: "X"
}
}
ColumnLayout {
visible: previewCard.type === Constants.LinkPreviewType.StatusCommunity
Label {
text: "MembersCount"
}
TextField {
id: membersCountInput
Layout.fillHeight: true
Layout.fillWidth: true
inputMethodHints: Qt.ImhDigitsOnly
text: "629200"
}
TextField {
id: activeMembersCountInput
Layout.fillHeight: true
Layout.fillWidth: true
inputMethodHints: Qt.ImhDigitsOnly
text: "112100"
}
}
}
ColumnLayout {
visible: previewCard.type === Constants.LinkPreviewType.StatusContact
Label {
text: "Footer"
text: "User name"
}
TextField {
id: footerInput
id: userNameInput
Layout.fillHeight: true
Layout.fillWidth: true
text: footerTypeCommunity.footerRichText
text: "Test user name"
}
Label {
text: "Bio"
}
RowLayout {
TextField {
id: bioInput
Layout.fillHeight: true
Layout.fillWidth: true
text: "User bio description goes here. If blank it will enable multi line title."
}
Button {
text: "clear"
onClicked: bioInput.text = ""
}
Button {
text: "Set"
onClicked: bioInput.text = "User bio description goes here. If blank it will enable multi line title."
}
}
}
ColumnLayout {
visible: previewCard.type === Constants.LinkPreviewType.StatusCommunityChannel
Label {
Layout.fillWidth: true
text: "Logo"
text: "channel settings"
}
RadioButton {
text: qsTr("No logo")
checked: root.logoSettings.name === "" && root.logoSettings.emoji === ""
onToggled: {
root.logoSettings.name = ""
root.logoSettings.emoji = ""
root.logoSettings.isImage = false
root.logoSettings.isLetterIdenticon = false
}
}
RadioButton {
readonly property string rawImageData: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAcCAYAAACdz7SqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAM2SURBVHgBtVbNbtNAEJ7ZpBQ4pRGF9kQqWqkBRNwnwLlxI9y4NX2CiiOntE9QeINw49a8QdwT3NhKQCKaSj4WUVXmABRqe5hxE+PGTuyk5ZOSXe/ftzs/3y5CBiw/NEzw/cdAaCJAifgXdCA4QGAjggbEvbMf0LJt7aSth6lkHjW4akIG8GI2/1k5H7e7XW2PGRdHqWQU8jdoNytZIrnC7YNPupnUnxtuWF01SjhD77hqwPQosNlrxdt34OTb172xpELoKvrA1QW4EqCZRJyLEnpI7ZBQggThlGvXYVLI3HAeE88vfj85Pno/6FaDiqeoEUZlMA9bvc/7cxyxVa6/SeM5j2Tcdn/hnHsNly520s7KAyN0V17+7pWNGhHVhxYJTNLraosLi8e0kMBxT0FH00IW830oeT/ButBertjRQ5BPO1xUQ1IE2oQUHHZ0K6mdI1RzoSEdpqRg76O2lPgSElKDdz919JYMoxA95QDow7qUykWoxTo5z2YIXsGUsLV2CPD1cDu7MODiQKKnsVmI1jhFyQJvFrb6URxFQWJAYYIZSEF6tKZATitFQpehEm1PkCraWYCE+8Nt5ENBwX8EAd2NNaKQxu0ukVuCqwATQHwnjhphShMuiSAVKZ527E6bzYt78Q3SulxvcAm44K8ntXMqagmkJDUpzNwMZGsqBDqLuDXcLvkvqajcWWgm+ZUI6svlym5fsbITlh9tsgi0Ezs5//vkMtBocqSJOZw84ZrHPiXFJ6UwECx5A/FbqNXX2hAiefkzqCNRha1Wi8yJgddeCk4qHzkK1aMgdypfshYRbkTGm3z0Rs6LW0REgDXVEMuMI0TE5kDlgkv8+PjIKRYXfzPxEyH2EYzDzv7L4q1FHsvpg8Gkt186OlGp5uYXZMjzkYS8txwfQnj63//APmzDIF1yWJVrCDJgeZVfjTjCj0KicC3qlny0053FZ/k/PFnyy6P2yv1Kk1T/1eCGF/pEYCncGI6DCzIo/uGnRvg8CfzE5MEPoQGT4Pz5Uj3oxp+hMe0V4oOOrssOMfmWyMJo5X1cG2WZkYIvO2Tn85sGXwg5B5Q9kiKMas5DntPr6Oq4+/gvs8hkkbAzoC8AAAAASUVORK5CYII="
text: qsTr("Raw image")
checked: root.logoSettings.name === rawImageData
onToggled: {
root.logoSettings.name = rawImageData
root.logoSettings.isImage = true
root.logoSettings.isLetterIdenticon = false
}
}
RadioButton {
text: qsTr("QRC asset: SOCKS")
checked: root.logoSettings.name = Style.png("tokens/SOCKS")
onToggled:{
root.logoSettings.name = Style.png("tokens/SOCKS")
root.logoSettings.isImage = true
root.logoSettings.isLetterIdenticon = false
}
}
RadioButton {
text: qsTr("Letter identicon")
checked: root.logoSettings.name = "github.com"
onToggled: {
root.logoSettings.name = "github.com"
root.logoSettings.emoji = ""
root.logoSettings.isLetterIdenticon = true
root.logoSettings.color = "blue"
}
}
RadioButton {
CheckBox {
text: qsTr("Emoji")
checked: root.logoSettings.emoji === "👋"
onToggled: {
root.logoSettings.emoji = "👋"
root.logoSettings.isLetterIdenticon = true
root.logoSettings.color = "orchid"
}
checked: previewCard.channelData.emoji === "👋"
onToggled: previewCard.channelData.emoji = checked ? "👋" : ""
}
RadioButton {
text: qsTr("Blue channel color")
checked: previewCard.channelData.color === "blue"
onToggled: previewCard.channelData.color = "blue"
}
RadioButton {
text: qsTr("Red channel color")
checked: previewCard.channelData.color === "red"
onToggled: previewCard.channelData.color = "red"
}
}
Label {
Layout.fillWidth: true
text: "Logo"
}
RadioButton {
text: qsTr("no image")
checked: root.image === ""
onToggled: root.image = ""
}
RadioButton {
readonly property string rawImageData: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAcCAYAAACdz7SqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAM2SURBVHgBtVbNbtNAEJ7ZpBQ4pRGF9kQqWqkBRNwnwLlxI9y4NX2CiiOntE9QeINw49a8QdwT3NhKQCKaSj4WUVXmABRqe5hxE+PGTuyk5ZOSXe/ftzs/3y5CBiw/NEzw/cdAaCJAifgXdCA4QGAjggbEvbMf0LJt7aSth6lkHjW4akIG8GI2/1k5H7e7XW2PGRdHqWQU8jdoNytZIrnC7YNPupnUnxtuWF01SjhD77hqwPQosNlrxdt34OTb172xpELoKvrA1QW4EqCZRJyLEnpI7ZBQggThlGvXYVLI3HAeE88vfj85Pno/6FaDiqeoEUZlMA9bvc/7cxyxVa6/SeM5j2Tcdn/hnHsNly520s7KAyN0V17+7pWNGhHVhxYJTNLraosLi8e0kMBxT0FH00IW830oeT/ButBertjRQ5BPO1xUQ1IE2oQUHHZ0K6mdI1RzoSEdpqRg76O2lPgSElKDdz919JYMoxA95QDow7qUykWoxTo5z2YIXsGUsLV2CPD1cDu7MODiQKKnsVmI1jhFyQJvFrb6URxFQWJAYYIZSEF6tKZATitFQpehEm1PkCraWYCE+8Nt5ENBwX8EAd2NNaKQxu0ukVuCqwATQHwnjhphShMuiSAVKZ527E6bzYt78Q3SulxvcAm44K8ntXMqagmkJDUpzNwMZGsqBDqLuDXcLvkvqajcWWgm+ZUI6svlym5fsbITlh9tsgi0Ezs5//vkMtBocqSJOZw84ZrHPiXFJ6UwECx5A/FbqNXX2hAiefkzqCNRha1Wi8yJgddeCk4qHzkK1aMgdypfshYRbkTGm3z0Rs6LW0REgDXVEMuMI0TE5kDlgkv8+PjIKRYXfzPxEyH2EYzDzv7L4q1FHsvpg8Gkt186OlGp5uYXZMjzkYS8txwfQnj63//APmzDIF1yWJVrCDJgeZVfjTjCj0KicC3qlny0053FZ/k/PFnyy6P2yv1Kk1T/1eCGF/pEYCncGI6DCzIo/uGnRvg8CfzE5MEPoQGT4Pz5Uj3oxp+hMe0V4oOOrssOMfmWyMJo5X1cG2WZkYIvO2Tn85sGXwg5B5Q9kiKMas5DntPr6Oq4+/gvs8hkkbAzoC8AAAAASUVORK5CYII="
text: qsTr("Raw image")
checked: root.image === rawImageData
onToggled: root.image = rawImageData
}
RadioButton {
text: qsTr("QRC asset: SOCKS")
checked: root.image === Style.png("tokens/SOCKS")
onToggled: root.image = Style.png("tokens/SOCKS")
}
ColumnLayout {
visible: previewCard.type !== Constants.LinkPreviewType.StatusContact
Label {
Layout.fillWidth: true
text: "Banner size"
@ -157,29 +311,17 @@ SplitView {
}
}
CheckBox {
id: rawImageCheck
readonly property string rawImageData: "data:image/jpeg;base64,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"
text: qsTr("Raw image banner")
checked: root.banner === rawImageData
}
ColumnLayout {
Label {
Layout.fillWidth: true
text: "Footer type"
}
RadioButton {
id: footerTypeCommunity
property string footerRichText: `<img src="%1" width="16" height="16" style="vertical-align: top" ></img><font color="%2"> 629.2K </font> <img src="%3" width="16" height="16" style="vertical-align: top" ><font color="%2">112.1K</font>`.arg(Style.svg("group")).arg(Theme.palette.directColor1).arg(Style.svg("active-members"))
text: qsTr("Community")
checked: footerInput.text === footerRichText
onToggled: footerInput.text = footerRichText
}
RadioButton {
property string footerRichText: `%1 <img src="%2" width="16" height="16" style="vertical-align: top" ><font color="%2"> %3</font>`.arg(qsTr("Channel in")).arg(Style.png("tokens/SOCKS")).arg(qsTr("Doodles"))
text: qsTr("Channel")
checked: footerInput.text === footerRichText
onToggled: footerInput.text = footerRichText
}
RadioButton {
text: qsTr("Link domain")
property string footerText: "X"
checked: footerInput.text === footerText
onToggled: footerInput.text = footerText
text: "UserName"
}
}
@ -201,4 +343,12 @@ SplitView {
}
}
}
Settings {
property alias linkType: previewCard.type
}
}
// category: Controls
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=22347-219545&mode=design&t=bODv5MUGQgU9ThJF-0

View File

@ -25,15 +25,39 @@ SplitView {
LinkPreviewMiniCard {
id: previewMiniCard
anchors.centerIn: parent
titleStr: type === LinkPreviewMiniCard.Type.User ? userNameInput.text : titleInput.text
domain: domainInput.text
favIconUrl: faviconInput.text
previewState: stateInput.currentIndex
thumbnailImageUrl: externalImageInput.text
type: previewTypeInput.currentIndex
communityName: communityNameInput.text
channelName: channelNameInput.text
}
previewState: stateInput.currentIndex
linkData {
title: titleInput.text
description: ""
domain: domainInput.text
thumbnail: externalImageInput.text
image: faviconInput.text
}
userData {
name: userNameInput.text
publicKey: "zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
image: faviconInput.text
ensVerified: false
}
communityData {
name: communityNameInput.text
banner: externalImageInput.text
image: faviconInput.text
color: "orchid"
}
channelData {
name: channelNameInput.text
emoji: ""
color: "blue"
communityData {
name: communityNameInput.text
banner: externalImageInput.text
image: faviconInput.text
color: "orchid"
}
}
}
}
Pane {
@ -50,7 +74,7 @@ SplitView {
id: previewTypeInput
Layout.fillHeight: true
Layout.fillWidth: true
model: ["link", "image", "community", "channel", "user profile"]
model: ["unknown", "standard", "user profile", "community", "channel"]
}
Label {
text: "Community name"
@ -146,4 +170,4 @@ SplitView {
//Category: Controls
//"https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0"
//"https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0"

View File

@ -2,87 +2,15 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Models 1.0
import shared.views.chat 1.0
import shared.stores 1.0
SplitView {
ListModel {
LinkPreviewModel {
id: mockedLinkPreviewModel
// Create the model dynamically, because `ListElement` doesnt suppport nested elements
Component.onCompleted: {
const emptyObject = {
"unfurled": true,
"immutable": false,
"empty": false,
"url": "",
"previewType": 1,
"standardPreview": {},
"standardPreviewThumbnail": {},
"statusContactPreview": {},
"statusContactPreviewThumbnail": {},
"statusCommunityPreview": {},
"statusCommunityPreviewIcon": {},
"statusCommunityPreviewBanner": {},
"statusCommunityChannelPreview": {},
"statusCommunityChannelCommunityPreview": {},
"statusCommunityChannelCommunityPreviewIcon": {},
"statusCommunityChannelCommunityPreviewBanner": {},
}
const preview1 = Object.assign({}, emptyObject)
preview1.url = "https://www.youtube.com/watch?v=9bZkp7q19f0"
preview1.previewType = 1
preview1.standardPreview = {}
preview1.standardPreviewThumbnail = {}
preview1.standardPreview.hostname = "www.youtube.com"
preview1.standardPreview.title = "PSY - GANGNAM STYLE(강남스타일) M/V"
preview1.standardPreview.description = "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
preview1.standardPreview.linkType = 0
preview1.standardPreviewThumbnail.width = 480
preview1.standardPreviewThumbnail.height = 360
preview1.standardPreviewThumbnail.url = "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
preview1.standardPreviewThumbnail.dataUri = ""
const preview2 = Object.assign({}, emptyObject)
preview2.url = "https://status.app/u/Ow==#zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
preview2.previewType = 2
preview2.statusContactPreview = {}
preview2.statusContactPreview.publicKey = "zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
preview2.statusContactPreview.displayName = "Test contact display name"
preview2.statusContactPreview.description = "Test description"
preview2.statusContactPreviewThumbnail = {}
preview2.statusContactPreviewThumbnail.width = 64
preview2.statusContactPreviewThumbnail.height = 64
preview2.statusContactPreviewThumbnail.url = "https://placehold.co/64x64"
preview2.statusContactPreviewThumbnail.dataUri = ""
const preview3 = Object.assign({}, emptyObject)
preview3.url = "https://status.app/c/ixiACjAKDlRlc3QgQ29tbXVuaXR5Eg9PcGVuIGZvciBhbnlvbmUYdiIHI0ZGMDAwMCoCHwkD#zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview3.previewType = 3
preview3.statusCommunityPreview = {}
preview3.statusCommunityPreview.communityId = "zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview3.statusCommunityPreview.displayName = "Test community display name"
preview3.statusCommunityPreview.description = "Test community description"
preview3.statusCommunityPreview.membersCount = 10
preview3.statusCommunityPreview.color = "#123456"
preview3.statusCommunityPreviewIcon = {}
preview3.statusCommunityPreviewIcon.width = 64
preview3.statusCommunityPreviewIcon.height = 64
preview3.statusCommunityPreviewIcon.url = "https://placehold.co/64x64"
preview3.statusCommunityPreviewIcon.dataUri = ""
preview3.statusCommunityPreviewBanner = {}
preview3.statusCommunityPreviewBanner.width = 320
preview3.statusCommunityPreviewBanner.height = 180
preview3.statusCommunityPreviewBanner.url = "https://placehold.co/320x180"
preview3.statusCommunityPreviewBanner.dataUri = ""
mockedLinkPreviewModel.append(preview1)
mockedLinkPreviewModel.append(preview2)
mockedLinkPreviewModel.append(preview3)
}
}
Pane {
@ -90,38 +18,19 @@ SplitView {
SplitView.fillWidth: true
SplitView.fillHeight: true
component LinkPreviewObject: QtObject {
required property string url
required property bool unfurled
required property bool empty
required property int previewType
}
component StandardPreviewObject: QtObject {
required property string hostname
required property string title
required property string description
required property int linkType // 0 = link, 1 = image
}
component ThumbnailObject: QtObject {
required property int width
required property int height
required property string url
required property string dataUri
}
LinksMessageView {
id: linksMessageView
anchors.fill: parent
store: {}
messageStore: {}
isOnline: true
playAnimations: true
linkPreviewModel: mockedLinkPreviewModel
gifLinks: [ "https://media.tenor.com/qN_ytiwLh24AAAAC/cold.gif" ]
isCurrentUser: true
gifUnfurlingEnabled: false
canAskToUnfurlGifs: true
onImageClicked: {
console.log("image clicked")
}
@ -148,18 +57,28 @@ SplitView {
}
CheckBox {
text: qsTr("Enabled")
checked: RootStore.gifUnfurlingEnabled
onToggled: RootStore.gifUnfurlingEnabled = !RootStore.gifUnfurlingEnabled
checked: linksMessageView.gifUnfurlingEnabled
onToggled: linksMessageView.gifUnfurlingEnabled = !linksMessageView.gifUnfurlingEnabled
}
CheckBox {
text: qsTr("Never ask about GIF unfurling again")
checked: RootStore.neverAskAboutUnfurlingAgain
onClicked: RootStore.neverAskAboutUnfurlingAgain = !RootStore.neverAskAboutUnfurlingAgain
text: qsTr("Can ask about GIF unfurling")
checked: linksMessageView.canAskToUnfurlGifs
onClicked: linksMessageView.canAskToUnfurlGifs = !linksMessageView.canAskToUnfurlGifs
}
Button {
text: qsTr("Reset local `askAboutUnfurling` setting")
onClicked: linksMessageView.resetLocalAskAboutUnfurling()
}
CheckBox {
text: qsTr("Play animations")
checked: linksMessageView.playAnimations
onToggled: linksMessageView.playAnimations = !linksMessageView.playAnimations
}
CheckBox {
text: qsTr("Is online")
checked: linksMessageView.isOnline
onToggled: linksMessageView.isOnline = !linksMessageView.isOnline
}
}
}
}

View File

@ -74,7 +74,7 @@ SplitView {
id: fakeUsersModel
}
ListModel {
LinkPreviewModel {
id: fakeLinksModel
}
@ -172,19 +172,12 @@ SplitView {
fakeLinksModel.clear()
words.forEach(function(word){
if(Utils.isURL(word)) {
fakeLinksModel.append({
url: encodeURI(word),
unfurled: d.linkPreviewsEnabled,
immutable: !d.linkPreviewsEnabled,
hostname: Math.floor(Math.random() * 2) ? "youtube.com" : "",
title: "PSY - GANGNAM STYLE(강남스타일) M/V",
description: "This is the description of the link",
linkType: Math.floor(Math.random() * 3),
thumbnailWidth: 480,
thumbnailHeight: 360,
thumbnailUrl: "https://picsum.photos/480/360?random=1",
thumbnailDataUri: ""
})
const linkPreview = fakeLinksModel.getStandardLinkPreview()
linkPreview.url = encodeURI(word)
linkPreview.unfurled = Math.random() > 0.2
linkPreview.immutable = !d.linkPreviewsEnabled
linkPreview.empty = Math.random() > 0.7
fakeLinksModel.append(linkPreview)
}
})
}

View File

@ -1,86 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import shared.controls.chat 1.0
import utils 1.0
SplitView {
id: root
property bool globalUtilsReady: false
// globalUtilsInst mock
QtObject {
function getEmojiHashAsJson(publicKey) {
return JSON.stringify(["👨🏻‍🍼", "🏃🏿‍♂️", "🌇", "🤶🏿", "🏮","🤷🏻‍♂️", "🤦🏻", "📣", "🤎", "👷🏽", "😺", "🥞", "🔃", "🧝🏽‍♂️"])
}
function getColorId(publicKey) { return 4 }
function getCompressedPk(publicKey) { return "zx3sh" + publicKey }
function getColorHashAsJson(publicKey) {
return JSON.stringify([{4: 0, segmentLength: 1},
{5: 19, segmentLength: 2}])
}
function isCompressedPubKey(publicKey) { return true }
Component.onCompleted: {
Utils.globalUtilsInst = this
root.globalUtilsReady = true
}
Component.onDestruction: {
root.globalUtilsReady = false
Utils.globalUtilsInst = {}
}
}
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
Loader {
anchors.centerIn: parent
active: root.globalUtilsReady
sourceComponent: UserProfileCard {
id: userProfileCard
userName: nameInput.text
userPublicKey: "0x1234567890"
userBio: bioInput.text
userImage: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/
nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
ensVerified: false
}
}
}
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
SplitView.minimumWidth: 300
ColumnLayout {
Label {
text: "userName"
}
TextField {
id: nameInput
text: "John Doe"
}
Label {
text: "userBio"
}
TextField {
id: bioInput
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor."
}
}
}
}
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=21961-655678&mode=design&t=JiMnPfMaLPWlrFK3-0

View File

@ -0,0 +1,136 @@
import QtQuick 2.15
ListModel {
id: root
function getStandardLinkPreview() {
const preview = Object.assign({}, emptyObject)
preview.url = "https://www.youtube.com/watch?v=9bZkp7q19f0"
preview.previewType = 1
preview.standardPreview = {}
preview.standardPreviewThumbnail = {}
preview.standardPreview.hostname = "www.youtube.com"
preview.standardPreview.title = "PSY - GANGNAM STYLE(강남스타일) M/V"
preview.standardPreview.description = "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
preview.standardPreview.linkType = 0
preview.standardPreviewThumbnail.width = 480
preview.standardPreviewThumbnail.height = 360
preview.standardPreviewThumbnail.url = "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
preview.standardPreviewThumbnail.dataUri = ""
return preview
}
function getImageLinkPreview() {
const preview = Object.assign({}, emptyObject)
preview.url = "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
preview.previewType = 1
preview.standardPreview = {}
preview.standardPreviewThumbnail = {}
preview.standardPreview.hostname = "i.ytimg.com"
preview.standardPreview.title = "Image_link_preview.png"
preview.standardPreview.description = "Image link preview"
preview.standardPreview.linkType = 1
preview.standardPreviewThumbnail.width = 480
preview.standardPreviewThumbnail.height = 360
preview.standardPreviewThumbnail.url = "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
preview.standardPreviewThumbnail.dataUri = ""
return preview
}
function getCommunityLinkPreview() {
const preview = Object.assign({}, emptyObject)
preview.url = "https://status.app/c/ixiACjAKDlRlc3QgQ29tbXVuaXR5Eg9PcGVuIGZvciBhbnlvbmUYdiIHI0ZGMDAwMCoCHwkD#zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview.previewType = 3
preview.statusCommunityPreview = {}
preview.statusCommunityPreview.communityId = "zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview.statusCommunityPreview.displayName = "Test community display name"
preview.statusCommunityPreview.description = "Test community description"
preview.statusCommunityPreview.membersCount = 10
preview.statusCommunityPreview.color = "#123456"
preview.statusCommunityPreviewIcon = {}
preview.statusCommunityPreviewIcon.width = 64
preview.statusCommunityPreviewIcon.height = 64
preview.statusCommunityPreviewIcon.url = "https://picsum.photos/64/64?random=1"
preview.statusCommunityPreviewIcon.dataUri = ""
preview.statusCommunityPreviewBanner = {}
preview.statusCommunityPreviewBanner.width = 320
preview.statusCommunityPreviewBanner.height = 180
preview.statusCommunityPreviewBanner.url = "https://picsum.photos/320/180?random=1"
preview.statusCommunityPreviewBanner.dataUri = ""
return preview
}
function getChannelLinkPreview() {
const preview = Object.assign({}, emptyObject)
preview.url = "https://status.app/c/ixiACjAKDlRlc3QgQ29tbXVuaXR5Eg9PcGVuIGZvciBhbnlvbmUYdiIHI0ZGMDAwMCoCHwkD#zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview.previewType = 4
preview.statusCommunityChannelPreview = {}
preview.statusCommunityChannelPreview.channelUuid = "zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview.statusCommunityChannelPreview.emoji = "👋"
preview.statusCommunityChannelPreview.displayName = "general"
preview.statusCommunityChannelPreview.description = "Test channel description"
preview.statusCommunityChannelPreview.color = "#122456"
preview.statusCommunityChannelCommunityPreview = {}
preview.statusCommunityChannelCommunityPreview.communityId = "zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview.statusCommunityChannelCommunityPreview.displayName = "Doodles"
preview.statusCommunityChannelCommunityPreview.description = "Test community description"
preview.statusCommunityChannelCommunityPreview.membersCount = 10
preview.statusCommunityChannelCommunityPreview.color = "#123456"
preview.statusCommunityChannelCommunityPreviewIcon = {}
preview.statusCommunityChannelCommunityPreviewIcon.width = 64
preview.statusCommunityChannelCommunityPreviewIcon.height = 64
preview.statusCommunityChannelCommunityPreviewIcon.url = "https://picsum.photos/64/64?random=1"
preview.statusCommunityChannelCommunityPreviewIcon.dataUri = ""
preview.statusCommunityChannelCommunityPreviewBanner = {}
preview.statusCommunityChannelCommunityPreviewBanner.width = 320
preview.statusCommunityChannelCommunityPreviewBanner.height = 180
preview.statusCommunityChannelCommunityPreviewBanner.url = "https://picsum.photos/320/180?random=1"
preview.statusCommunityChannelCommunityPreviewBanner.dataUri = ""
return preview
}
function getContactLinkPreview() {
const preview = Object.assign({}, emptyObject)
preview.url = "https://status.app/u/Ow==#zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
preview.previewType = 2
preview.statusContactPreview = {}
preview.statusContactPreview.publicKey = "zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
preview.statusContactPreview.displayName = "Test contact display name"
preview.statusContactPreview.description = "Test description"
preview.statusContactPreviewThumbnail = {}
preview.statusContactPreviewThumbnail.width = 64
preview.statusContactPreviewThumbnail.height = 64
preview.statusContactPreviewThumbnail.url = "https://picsum.photos/64/64?random=1"
preview.statusContactPreviewThumbnail.dataUri = ""
return preview
}
readonly property var emptyObject: {
"unfurled": true,
"immutable": false,
"empty": false,
"url": "",
"previewType": 1,
"standardPreview": {},
"standardPreviewThumbnail": {},
"statusContactPreview": {},
"statusContactPreviewThumbnail": {},
"statusCommunityPreview": {},
"statusCommunityPreviewIcon": {},
"statusCommunityPreviewBanner": {},
"statusCommunityChannelPreview": {},
"statusCommunityChannelCommunityPreview": {},
"statusCommunityChannelCommunityPreviewIcon": {},
"statusCommunityChannelCommunityPreviewBanner": {},
}
// Create the model dynamically, because `ListElement` doesnt suppport nested elements
Component.onCompleted: {
append(getStandardLinkPreview())
append(getImageLinkPreview())
append(getCommunityLinkPreview())
append(getChannelLinkPreview())
append(getContactLinkPreview())
}
}

View File

@ -6,6 +6,7 @@ ChannelsModel 1.0 ChannelsModel.qml
CollectiblesModel 1.0 CollectiblesModel.qml
FeesModel 1.0 FeesModel.qml
IconModel 1.0 IconModel.qml
LinkPreviewModel 1.0 LinkPreviewModel.qml
MintedTokensModel 1.0 MintedTokensModel.qml
RecipientModel 1.0 RecipientModel.qml
TokenHoldersModel 1.0 TokenHoldersModel.qml

View File

@ -80,6 +80,30 @@ QtObject {
return num.toLocaleString(locale, 'f', precision)
}
function numberToLocaleStringInCompactForm(num, locale = null) {
locale = locale || Qt.locale()
const numberOfDigits = integralPartLength(num)
let oneArgStrFormat = "%1"
let formattedNumber = num
let multiplier = 1
if(numberOfDigits >=4 && numberOfDigits < 7) { // 1K - 999K
multiplier = 1 / 1000
oneArgStrFormat = qsTr("%1K", "Thousand")
} else if(numberOfDigits >= 7 && numberOfDigits < 10) { // 1M - 999M
multiplier = 1 / 1000000
oneArgStrFormat = qsTr("%1M", "Million")
} else if(numberOfDigits >= 10 && numberOfDigits < 13) { // 1B - 999B
multiplier = 1 / 1000000000
oneArgStrFormat = qsTr("%1B", "Billion")
} else if(numberOfDigits >= 13 && numberOfDigits < 16) { // 1T - 999T
multiplier = 1 / 1000000000000
oneArgStrFormat = qsTr("%1T", "Trillion")
}
const stringNumber = numberToLocaleString(num * multiplier, 2, locale)
return oneArgStrFormat.arg(stripTrailingZeroes(stringNumber, locale))
}
function numberFromLocaleString(num, locale = null) {
locale = locale || Qt.locale()
try {

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.46069 13.6656C6.42831 13.957 6.78989 14.1174 6.98417 13.8977L12.6016 7.54762C12.8588 7.25683 12.6524 6.79857 12.2641 6.79857H9.09442C8.91541 6.79857 8.77613 6.64297 8.7959 6.46505L9.25486 2.33445C9.28724 2.04302 8.92565 1.88265 8.73137 2.10227L3.11397 8.45238C2.85673 8.74317 3.06318 9.20143 3.45142 9.20143H6.28554C6.64358 9.20143 6.92212 9.51264 6.88258 9.86848L6.46069 13.6656Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 552 B

View File

@ -123,4 +123,168 @@ TestCase {
compare(LocaleUtils.currencyAmountToLocaleString(
data.amount, null, locale), data.amountString)
}
function test_numberToLocaleStringInCompactForm_data() {
return [
{
amount: NaN,
amountString: "nan"
},
{
amount: null,
amountString: "0"
},
{
amount: "",
amountString: "0"
},
{
amount: "string",
amountString: "nan"
},
{
amount: {},
amountString: "nan"
},
{
amount: -1,
amountString: "-1"
},
{
amount: -1.1,
amountString: "-1.1"
},
{
amount: -1.1234,
amountString: "-1.12"
},
{
amount: -1000,
amountString: "-1K"
},
{
amount: -1000.1,
amountString: "-1K"
},
{
amount: -100000,
amountString: "-100K"
},
{
amount: -1001,
amountString: "-1K"
},
{
amount: -1100,
amountString: "-1.1K"
},
{
amount: -1000000,
amountString: "-1M"
},
{
amount: -1100000.123,
amountString: "-1.1M"
},
{
amount: -1000000000,
amountString: "-1B"
},
{
amount: -1100000000,
amountString: "-1.1B"
},
{
amount: -1000000000.123,
amountString: "-1B"
},
{
amount: -1000000000000,
amountString: "-1T"
},
{
amount: -999000000000000,
amountString: "-999T"
},
{
amount: -1000000000000000,
amountString: "-1,000,000,000,000,000"
},
{
amount: 0,
amountString: "0"
},
{
amount: 1,
amountString: "1"
},
{
amount: 1.1,
amountString: "1.1"
},
{
amount: 1.1234,
amountString: "1.12"
},
{
amount: 1000,
amountString: "1K"
},
{
amount: 1000.1,
amountString: "1K"
},
{
amount: 100000,
amountString: "100K"
},
{
amount: 1001,
amountString: "1K"
},
{
amount: 1100,
amountString: "1.1K"
},
{
amount: 1000000,
amountString: "1M"
},
{
amount: 1100000.123,
amountString: "1.1M"
},
{
amount: 1000000000,
amountString: "1B"
},
{
amount: 1100000000,
amountString: "1.1B"
},
{
amount: 1000000000.123,
amountString: "1B"
},
{
amount: 1000000000000,
amountString: "1T"
},
{
amount: 999000000000000,
amountString: "999T"
},
{
amount: 1000000000000000,
amountString: "1,000,000,000,000,000"
}
]
}
function test_numberToLocaleStringInCompactForm(data) {
const locale = Qt.locale("en_US")
compare(LocaleUtils.numberToLocaleStringInCompactForm(
data.amount, locale), data.amountString)
}
}

View File

@ -277,6 +277,10 @@ Item {
popups.openConfirmExternalLinkPopup(link, domain)
}
function onActivateDeepLink(link: string) {
appMain.rootStore.mainModuleInst.activateStatusDeepLink(link)
}
function onPlaySendMessageSound() {
sendMessageSound.stop()
sendMessageSound.play()

View File

@ -6,7 +6,7 @@ import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import shared.status 1.0
import shared.controls.chat 1.0
import shared.controls.delegates 1.0
import utils 1.0
@ -99,57 +99,8 @@ Control {
Repeater {
id: linkPreviewRepeater
model: d.filteredModel
delegate: LinkPreviewMiniCard {
// Model properties
delegate: LinkPreviewMiniCardDelegate {
required property int index
required property bool unfurled
required property bool empty
required property string url
required property bool immutable
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
readonly property var thumbnail: {
switch (previewType) {
case Constants.Standard:
return standardPreviewThumbnail
case Constants.StatusContact:
return statusContactPreviewThumbnail
case Constants.StatusCommunity:
return statusCommunityPreviewIcon
case Constants.StatusCommunityChannel:
return statusCommunityChannelCommunityPreviewIcon
}
}
readonly property string thumbnailUrl: thumbnail ? thumbnail.url : ""
readonly property string thumbnailDataUri: thumbnail ? thumbnail.dataUri : ""
Layout.preferredHeight: 64
titleStr: standardPreview ? standardPreview.title : statusContactPreview ? statusContactPreview.displayName : ""
domain: standardPreview ? standardPreview.hostname : "" //TODO: use domain when available
favIconUrl: "" //TODO: use favicon when available
communityName: statusCommunityPreview ? statusCommunityPreview.displayName : ""
channelName: statusCommunityChannelPreview ? statusCommunityChannelPreview.displayName : ""
thumbnailImageUrl: thumbnailUrl.length > 0 ? thumbnailUrl : thumbnailDataUri
type: getCardType(previewType, standardPreview)
previewState: unfurled && !empty ? LinkPreviewMiniCard.State.Loaded :
unfurled && empty ? LinkPreviewMiniCard.State.LoadingFailed :
!unfurled ? LinkPreviewMiniCard.State.Loading : LinkPreviewMiniCard.State.Invalid
onClose: root.dismissLinkPreview(d.filteredModel.mapToSource(index))
onRetry: root.linkReload(url)

View File

@ -6,26 +6,23 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import shared.controls 1.0
import shared.status 1.0
import utils 1.0
import "./private"
CalloutCard {
id: root
/// The title of the callout card
required property string title
required property string description
required property string footer
readonly property LinkData linkData: LinkData { }
readonly property UserData userData: UserData { }
readonly property CommunityData communityData: CommunityData { }
readonly property ChannelData channelData: ChannelData { }
property int type: Constants.LinkPreviewType.NoPreview
property bool highlight: false
property StatusAssetSettings logoSettings: StatusAssetSettings {
width: 28
height: 28
bgRadius: bgWidth / 2
}
property string bannerImageSource: ""
signal clicked(var mouse)
@ -41,71 +38,103 @@ CalloutCard {
}
contentItem: ColumnLayout {
StatusImage {
id: bannerImage
Loader {
id: bannerImageLoader
Layout.fillWidth: true
Layout.leftMargin: d.bannerImageMargins
Layout.rightMargin: d.bannerImageMargins
Layout.topMargin: d.bannerImageMargins
Layout.preferredHeight: 170
asynchronous: true
source: root.bannerImageSource
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: root.clippingEffect
active: !!d.bannerImageSource
sourceComponent: StatusImage {
id: bannerImage
asynchronous: true
source: d.bannerImageSource
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: root.clippingEffect
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 12
Loader {
id: userImageLoader
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.topMargin: 8
Layout.bottomMargin: 8
visible: active
active: root.type === Constants.LinkPreviewType.StatusContact
sourceComponent: UserImage {
interactive: false
imageWidth: 58
imageHeight: imageWidth
ensVerified: root.userData.ensVerified
name: root.userData.name
pubkey: root.userData.publicKey
image: root.userData.image
}
}
RowLayout {
id: titleLayout
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumHeight: root.description.length ? 28 : 72
Layout.maximumHeight: description.text.length ? 28 : 72
Layout.minimumHeight: 18
StatusSmartIdenticon {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
id: logo
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 28
Layout.preferredHeight: 28
asset: root.logoSettings
name: root.logoSettings.name
visible: !!root.logoSettings.name.length || !!root.logoSettings.emoji.length
asset.width: width
asset.height: height
visible: false
}
StatusBaseText {
text: root.title
id: title
// One line centered next to the logo
// Two or more lines, or no logo, top aligned
readonly property bool centerText: lineCount == 1 && height === logo.height && logo.visible
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignTop
Layout.topMargin: verticalAlignment === Text.AlignTop && contentHeight < logo.height ? (logo.height - contentHeight) / 2 : 0
font.pixelSize: 13
font.weight: Font.Medium
wrapMode: Text.Wrap
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
verticalAlignment: centerText ? Text.AlignVCenter : Text.AlignTop
visible: text.length
}
}
EmojiHash {
Layout.topMargin: 4
Layout.bottomMargin: 6
visible: root.type === Constants.LinkPreviewType.StatusContact
publicKey: root.userData.publicKey
oneRow: true
}
StatusBaseText {
id: description
Layout.fillWidth: true
Layout.fillHeight: true
text: root.description
font.pixelSize: 12
wrapMode: Text.Wrap
elide: Text.ElideRight
color: Theme.palette.baseColor1
visible: root.description.length
visible: description.text.length
}
StatusBaseText {
id: linkSite
Loader {
id: footerLoader
Layout.fillWidth: true
text: root.footer
font.pixelSize: 12
lineHeight: 16
lineHeightMode: Text.FixedHeight
color: Theme.palette.baseColor1
elide: Text.ElideRight
verticalAlignment: Text.AlignBottom
textFormat: Text.RichText
visible: active
sourceComponent: FooterText {
}
}
}
}
MouseArea {
anchors.fill: root
hoverEnabled: true
@ -114,9 +143,151 @@ CalloutCard {
onClicked: root.clicked(mouse)
}
component FooterText: StatusBaseText {
font.pixelSize: 12
lineHeight: 16
lineHeightMode: Text.FixedHeight
color: Theme.palette.baseColor1
elide: Text.ElideRight
wrapMode: Text.Wrap
verticalAlignment: Text.AlignBottom
text: root.linkData.domain
maximumLineCount: 1
}
Component {
id: channelFooterComponent
RowLayout {
spacing: 4
FooterText {
Layout.fillHeight: true
text: qsTr("Channel in")
verticalAlignment: Text.AlignVCenter
}
StatusRoundedImage {
Layout.preferredHeight: 16
Layout.preferredWidth: height
image.source: channelData.communityData.image
}
FooterText {
Layout.fillHeight: true
Layout.fillWidth: true
text: channelData.communityData.name
verticalAlignment: Text.AlignVCenter
color: Theme.palette.directColor1
}
}
}
Component {
id: communityFooterComponent
RowLayout {
spacing: 2
StatusIcon {
icon: "group"
color: Theme.palette.directColor1
width: 16
height: width
}
FooterText {
Layout.fillHeight: true
Layout.fillWidth: communityData.activeMembersCount === -1 //TODO: remove magic number once we have activeMembersCount
color: Theme.palette.directColor1
text: LocaleUtils.numberToLocaleStringInCompactForm(communityData.membersCount)
verticalAlignment: Text.AlignVCenter
}
StatusIcon {
Layout.leftMargin: 6
icon: "active-members"
color: Theme.palette.directColor1
width: 16
height: width
visible: communityData.activeMembersCount > -1
}
FooterText {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.palette.directColor1
text: LocaleUtils.numberToLocaleStringInCompactForm(communityData.activeMembersCount)
verticalAlignment: Text.AlignVCenter
visible: communityData.activeMembersCount > -1
}
}
}
//behavior
states: [
State {
name: "noPreview"
when: root.type === Constants.LinkPreviewType.NoPreview
PropertyChanges { target: root; visible: false }
},
State {
name: "linkPreview"
when: root.type === Constants.LinkPreviewType.Standard
PropertyChanges {
target: logo
visible: !!root.linkData.image
name: root.linkData.domain
asset.name: root.linkData.image
asset.isImage: !!root.linkData.image
asset.color: Theme.palette.baseColor2
}
PropertyChanges { target: bannerImageLoader; visible: true }
PropertyChanges { target: title; text: root.linkData.title }
PropertyChanges { target: description; text: root.linkData.description }
PropertyChanges { target: d; bannerImageSource: root.linkData.thumbnail }
},
State {
name: "community"
when: root.type === Constants.LinkPreviewType.StatusCommunity
PropertyChanges {
target: logo
visible: true
name: root.communityData.name
asset.name: root.communityData.image
asset.isImage: !!root.communityData.image
asset.color: root.communityData.color
}
PropertyChanges { target: bannerImageLoader; visible: true }
PropertyChanges { target: title; text: root.communityData.name }
PropertyChanges { target: description; text: root.communityData.description }
PropertyChanges { target: d; bannerImageSource: root.communityData.banner }
PropertyChanges { target: footerLoader; active: true; visible: true; sourceComponent: communityFooterComponent }
},
State {
name: "channel"
when: root.type === Constants.LinkPreviewType.StatusCommunityChannel
PropertyChanges {
target: logo
visible: true
name: root.channelData.name
asset.name: ""
asset.isImage: false
asset.color: root.channelData.color
asset.emoji: root.channelData.emoji
}
PropertyChanges { target: bannerImageLoader; visible: true }
PropertyChanges { target: title; text: "#" + root.channelData.name }
PropertyChanges { target: description; text: root.channelData.description || root.channelData.communityData.description }
PropertyChanges { target: d; bannerImageSource: root.channelData.communityData.banner }
PropertyChanges { target: footerLoader; active: true; visible: true; sourceComponent: channelFooterComponent }
},
State {
name: "contact"
when: root.type === Constants.LinkPreviewType.StatusContact
PropertyChanges { target: root; implicitHeight: 187 }
PropertyChanges { target: bannerImageLoader; visible: false }
PropertyChanges { target: footerLoader; active: false; visible: !root.userData.bio; Layout.fillHeight: true }
PropertyChanges { target: title; text: root.userData.name }
PropertyChanges { target: description; text: root.userData.bio; Layout.minimumHeight: 32; visible: true }
}
]
QtObject {
id: d
property real bannerImageMargins: 1 / Screen.devicePixelRatio // image size isn't pixel perfect..
property bool highlight: root.highlight || root.hovered
property string bannerImageSource: ""
}
}

View File

@ -10,6 +10,8 @@ import shared 1.0
import utils 1.0
import "./private" 1.0
CalloutCard {
id: root
@ -20,45 +22,11 @@ CalloutCard {
Loaded
}
enum Type {
Unknown = 0,
Link,
Image,
Community,
Channel,
User
}
readonly property LinkData linkData: LinkData { }
readonly property UserData userData: UserData { }
readonly property CommunityData communityData: CommunityData { }
readonly property ChannelData channelData: ChannelData { }
function getCardType(previewType, standardLinkPreview) {
switch (previewType) {
case Constants.StatusContact:
return LinkPreviewMiniCard.Type.User
case Constants.StatusCommunity:
return LinkPreviewMiniCard.Type.Community
case Constants.StatusCommunityChannel:
return LinkPreviewMiniCard.Type.Channel
case Constants.Standard:
if (!standardLinkPreview)
return LinkPreviewMiniCard.Type.Unknown
switch (standardLinkPreview.linkType) {
case Constants.StandardLinkPreviewType.Link:
return LinkPreviewMiniCard.Type.Link
case Constants.StandardLinkPreviewType.Image:
return LinkPreviewMiniCard.Type.Image
default:
return LinkPreviewMiniCard.Type.Unknown
}
default:
return LinkPreviewMiniCard.Type.Unknown
}
}
required property string titleStr
required property string domain
required property string communityName
required property string channelName
required property url favIconUrl
required property url thumbnailImageUrl
required property int previewState
required property int type
@ -106,47 +74,78 @@ CalloutCard {
},
State {
name: "loaded"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Link
when: root.previewState === LinkPreviewMiniCard.State.Loaded &&
root.type === Constants.LinkPreviewType.Standard &&
root.linkData.type === Constants.StandardLinkPreviewType.Link
PropertyChanges {
target: root; visible: true; dashedBorder: false; borderWidth: 0;
backgroundColor: root.containsMouse ? Theme.palette.directColor8 : Theme.palette.indirectColor1;
borderColor: backgroundColor;
}
PropertyChanges { target: loadingAnimation; visible: false; }
PropertyChanges { target: titleText; text: root.titleStr; color: Theme.palette.directColor1 }
PropertyChanges { target: subtitleText; visible: true; }
PropertyChanges { target: titleText; text: root.linkData.title; color: Theme.palette.directColor1 }
PropertyChanges { target: subtitleText; visible: true; text: root.linkData.domain; }
PropertyChanges { target: reloadButton; visible: false; }
PropertyChanges { target: favIcon; visible: true }
},
State {
name: "loadedImage"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Image
extend: "loaded"
PropertyChanges { target: thumbnailImage; visible: root.thumbnailImageUrl != "" }
PropertyChanges { target: favIcon; visible: true; name: root.domain; asset.isLetterIdenticon: true; asset.color: Theme.palette.baseColor2; }
},
State {
name: "loadedCommunity"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Community
extend: "loaded"
PropertyChanges { target: titleText; text: root.communityName; }
},
State {
name: "loadedChannel"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Channel
extend: "loaded"
PropertyChanges { target: titleText; text: root.communityName; Layout.fillWidth: false; Layout.maximumWidth: Math.min(92, implicitWidth); }
PropertyChanges { target: secondTitleText; text: root.channelName; visible: true; }
},
State {
name: "loadedUser"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.User
extend: "loaded"
PropertyChanges {
target: favIcon
visible: true
name: root.titleStr
asset.isLetterIdenticon: true
name: root.linkData.title
asset.isLetterIdenticon: !root.linkData.image
asset.color: Theme.palette.baseColor2
}
},
State {
name: "loadedImage"
when: root.previewState === LinkPreviewMiniCard.State.Loaded &&
root.type === Constants.LinkPreviewType.Standard &&
root.linkData.type === Constants.StandardLinkPreviewType.Image
extend: "loaded"
PropertyChanges { target: thumbnailImage; visible: root.linkData.thumbnail != ""; image.source: root.linkData.thumbnail; }
PropertyChanges { target: favIcon; visible: true; name: root.linkData.domain; asset.isLetterIdenticon: true; asset.color: Theme.palette.baseColor2; }
PropertyChanges { target: subtitleText; visible: true; text: root.linkData.domain; }
},
State {
name: "loadedCommunity"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === Constants.LinkPreviewType.StatusCommunity
extend: "loaded"
PropertyChanges { target: titleText; text: root.communityData.name; }
PropertyChanges { target: subtitleText; visible: true; text: Constants.externalStatusLink; }
PropertyChanges {
target: favIcon
visible: true
name: root.communityData.name
asset.isLetterIdenticon: root.communityData.image.length === 0
asset.color: root.communityData.color
asset.name: root.communityData.image
}
},
State {
name: "loadedChannel"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === Constants.LinkPreviewType.StatusCommunityChannel
extend: "loadedCommunity"
PropertyChanges { target: titleText; text: root.channelData.communityData.name; Layout.fillWidth: false; Layout.maximumWidth: Math.min(92, implicitWidth); }
PropertyChanges { target: secondTitleText; text: "#" + root.channelData.name; visible: true; }
PropertyChanges {
target: favIcon
visible: true
name: root.channelData.communityData.name
asset.isLetterIdenticon: root.channelData.communityData.image.length === 0
asset.color: root.channelData.communityData.color
asset.name: root.channelData.communityData.image
}
},
State {
name: "loadedUser"
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === Constants.LinkPreviewType.StatusContact
extend: "loaded"
PropertyChanges { target: titleText; text: root.userData.name; Layout.fillWidth: false; Layout.maximumWidth: Math.min(92, implicitWidth); }
PropertyChanges { target: subtitleText; visible: true; text: Constants.externalStatusLink; }
PropertyChanges {
target: favIcon
visible: true
name: root.userData.name
asset.name: root.userData.image
asset.isLetterIdenticon: root.userData.image.length === 0
asset.charactersLen: 2
asset.color: Theme.palette.miscColor9
}
@ -174,8 +173,6 @@ CalloutCard {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: false
name: root.titleStr
asset.name: root.favIconUrl
asset.letterSize: asset.charactersLen == 1 ? 10 : 7
}
ColumnLayout {
@ -190,7 +187,6 @@ CalloutCard {
spacing: 0
StatusBaseText {
id: titleText
text: root.titleStr
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Style.current.additionalTextSize
@ -227,7 +223,6 @@ CalloutCard {
Layout.fillHeight: true
font.pixelSize: Style.current.tertiaryTextFontSize
color: Theme.palette.baseColor1
text: root.domain
wrapMode: Text.WordWrap
elide: Text.ElideRight
}
@ -239,7 +234,6 @@ CalloutCard {
implicitWidth: 34
implicitHeight: 34
radius: 4
image.source: root.thumbnailImageUrl
visible: false
}
StatusFlatButton {

View File

@ -1,78 +0,0 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import shared.status 1.0
import shared.controls 1.0
import shared.controls.chat 1.0
import utils 1.0
CalloutCard {
id: root
required property string userName
required property string userPublicKey
required property string userBio
required property var userImage
required property bool ensVerified
signal clicked()
implicitWidth: 305
implicitHeight: 187
padding: 12
contentItem: ColumnLayout {
spacing: 0
UserImage {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
name: root.userName
pubkey: root.userPublicKey
image: root.userImage
interactive: false
imageWidth: 58
imageHeight: imageWidth
ensVerified: root.ensVerified
}
StatusBaseText {
id: contactName
Layout.fillWidth: true
Layout.topMargin: 12
font.pixelSize: Style.current.additionalTextSize
font.weight: Font.Medium
elide: Text.ElideRight
text: root.userName
}
EmojiHash {
Layout.fillWidth: true
Layout.topMargin: 4
publicKey: root.userPublicKey
oneRow: true
}
StatusBaseText {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: 15
font.pixelSize: Style.current.tertiaryTextFontSize
color: Theme.palette.baseColor1
text: root.userBio
wrapMode: Text.WordWrap
elide: Text.ElideRight
}
}
MouseArea {
anchors.fill: root
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked()
}
}

View File

@ -0,0 +1,9 @@
import QtQuick 2.15
QtObject {
property string name
property string description
property string emoji
property string color
readonly property CommunityData communityData: CommunityData {}
}

View File

@ -0,0 +1,11 @@
import QtQuick 2.15
QtObject {
property string name
property string description
property string banner
property string image
property string color
property int membersCount
property int activeMembersCount: -1 // TODO: implement this and remove the magic number
}

View File

@ -0,0 +1,10 @@
import QtQuick 2.15
QtObject {
property string title
property string description
property string domain
property string thumbnail
property string image
property int type
}

View File

@ -0,0 +1,9 @@
import QtQuick 2.15
QtObject {
property string name
property string publicKey
property string bio
property string image
property bool ensVerified
}

View File

@ -19,6 +19,5 @@ SendTransactionButton 1.0 SendTransactionButton.qml
SentMessage 1.0 SentMessage.qml
StateBubble 1.0 StateBubble.qml
UserImage 1.0 UserImage.qml
UserProfileCard 1.0 UserProfileCard.qml
UsernameLabel 1.0 UsernameLabel.qml
VerificationLabel 1.0 VerificationLabel.qml

View File

@ -0,0 +1,91 @@
import QtQuick 2.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import shared.controls.chat 1.0
LinkPreviewCard {
id: root
/*
* Model properties
* The following properties are required to be set by the user of this component
* unfurled: Whether the link has been unfurled or not
* empty: Whether the link preview is empty or not
* url: The url of the link
* immutable: Whether the link preview can be updated
* previewType: The type of the preview. See Constants.LinkPreviewType
* standardPreview: The standard preview data (title, description, linkType, hostname)
* standardPreviewThumbnail: The standard preview thumbnail data (url, dataUri)
* statusContactPreview: The status contact preview data (displayName, publicKey, description, icon)
* statusContactPreviewThumbnail: The status contact preview thumbnail data (url, dataUri)
* statusCommunityPreview: The status community preview data (communityId, displayName, description, membersCount, color)
* statusCommunityPreviewIcon: The status community preview icon data (url, dataUri)
* statusCommunityPreviewBanner: The status community preview banner data (url, dataUri)
* statusCommunityChannelPreview: The status community channel preview data (channelId, displayName, description, emoji, color)
* statusCommunityChannelCommunityPreview: The status community channel community preview data (communityId, displayName, description, membersCount, color)
* statusCommunityChannelCommunityPreviewIcon: The status community channel community preview icon data (url, dataUri)
* statusCommunityChannelCommunityPreviewBanner: The status community channel community preview banner data (url, dataUri)
*/
required property bool unfurled
required property bool empty
required property string url
required property bool immutable
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
//View properties
property bool isCurrentUser: false
leftTail: !isCurrentUser
type: root.previewType
linkData {
title: standardPreview ? standardPreview.title : ""
description: standardPreview ? standardPreview.description : ""
domain: standardPreview ? standardPreview.hostname : "" //TODO: Use domainName when available
thumbnail: standardPreviewThumbnail ? (standardPreviewThumbnail.url || standardPreviewThumbnail.dataUri) || "" : ""
image: "" //TODO: usefavicon when available
}
userData {
name: statusContactPreview ? statusContactPreview.displayName : ""
publicKey: statusContactPreview ? statusContactPreview.publicKey : ""
bio: statusContactPreview ? statusContactPreview.description : ""
image: statusContactPreviewThumbnail ? (statusContactPreviewThumbnail.url || statusContactPreviewThumbnail.dataUri) || "" : ""
ensVerified: false // not supported yet
}
communityData {
name: statusCommunityPreview ? statusCommunityPreview.displayName : ""
description: statusCommunityPreview ? statusCommunityPreview.description : ""
banner: statusCommunityPreviewBanner ? (statusCommunityPreviewBanner.url || statusCommunityPreviewBanner.dataUri) || "" : ""
image: statusCommunityPreviewIcon ? (statusCommunityPreviewIcon.url || statusCommunityPreviewIcon.dataUri) || "" : ""
membersCount: statusCommunityPreview ? statusCommunityPreview.membersCount : 0
color: statusCommunityPreview ? statusCommunityPreview.color : ""
}
channelData {
name: statusCommunityChannelPreview ? statusCommunityChannelPreview.displayName : ""
description: statusCommunityChannelPreview ? statusCommunityChannelPreview.description : ""
emoji: statusCommunityChannelPreview ? statusCommunityChannelPreview.emoji : ""
color: statusCommunityChannelPreview ? statusCommunityChannelPreview.color : ""
communityData {
name: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.displayName : ""
description: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.description : ""
banner: statusCommunityChannelCommunityPreviewBanner ? (statusCommunityChannelCommunityPreviewBanner.url || statusCommunityChannelCommunityPreviewBanner.dataUri) || "" : ""
image: statusCommunityChannelCommunityPreviewIcon ? (statusCommunityChannelCommunityPreviewIcon.url || statusCommunityChannelCommunityPreviewIcon.dataUri) || "" : ""
membersCount: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.membersCount : 0
color: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.color : ""
}
}
}

View File

@ -0,0 +1,80 @@
import QtQuick 2.15
import utils 1.0
import shared.controls.chat 1.0
import shared.status 1.0
import StatusQ.Core 0.1
CalloutCard {
id: root
required property string link
required property bool playAnimation
required property bool isOnline
required property bool isCurrentUser
readonly property bool isPlaying: linkImage.playing
readonly property alias imageAlias: linkImage.imageAlias
signal clicked(var mouse)
implicitWidth: linkImage.width
implicitHeight: linkImage.height
leftTail: !isCurrentUser
StatusChatImageLoader {
id: linkImage
property bool localAnimationEnabled: true
objectName: "LinksMessageView_unfurledImageComponent_linkImage"
anchors.centerIn: parent
source: root.link
imageWidth: 300
isCurrentUser: root.isCurrentUser
playing: root.playAnimation && localAnimationEnabled
isOnline: root.isOnline
asynchronous: true
isAnimated: true
onClicked: {
if (isAnimated && !playing)
localAnimationEnabled = true
else
root.clicked(mouse)
}
imageAlias.cache: localAnimationEnabled // GIFs can only loop/play properly with cache enabled
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
color: "white"
}
}
}
Timer {
id: animationPlayingTimer
interval: 10000
running: linkImage.isAnimated && linkImage.playing
onTriggered: { linkImage.localAnimationEnabled = false }
}
}
}

View File

@ -0,0 +1,86 @@
import QtQuick 2.15
import utils 1.0
import shared.controls.chat 1.0
LinkPreviewMiniCard {
id: root
/*
* Model properties
* The following properties are required to be set by the user of this component
* unfurled: Whether the link has been unfurled or not
* empty: Whether the link preview is empty or not
* url: The url of the link
* immutable: Whether the link preview can be updated
* previewType: The type of the preview. See Constants.LinkPreviewType
* standardPreview: The standard preview data (title, description, linkType, hostname)
* standardPreviewThumbnail: The standard preview thumbnail data (url, dataUri)
* statusContactPreview: The status contact preview data (displayName, publicKey, description, icon)
* statusContactPreviewThumbnail: The status contact preview thumbnail data (url, dataUri)
* statusCommunityPreview: The status community preview data (communityId, displayName, description, membersCount, color)
* statusCommunityPreviewIcon: The status community preview icon data (url, dataUri)
* statusCommunityPreviewBanner: The status community preview banner data (url, dataUri)
* statusCommunityChannelPreview: The status community channel preview data (channelId, displayName, description, emoji, color)
* statusCommunityChannelCommunityPreview: The status community channel community preview data (communityId, displayName, description, membersCount, color)
* statusCommunityChannelCommunityPreviewIcon: The status community channel community preview icon data (url, dataUri)
* statusCommunityChannelCommunityPreviewBanner: The status community channel community preview banner data (url, dataUri)
*/
required property bool unfurled
required property bool empty
required property string url
required property bool immutable
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
previewState: !root.unfurled ? LinkPreviewMiniCard.State.Loading : root.unfurled && !root.empty ? LinkPreviewMiniCard.State.Loaded : LinkPreviewMiniCard.State.LoadingFailed
type: root.previewType
linkData {
title: standardPreview ? standardPreview.title : ""
description: standardPreview ? standardPreview.description : ""
domain: standardPreview ? standardPreview.hostname : "" //TODO: Use domainName when available
thumbnail: standardPreviewThumbnail ? (standardPreviewThumbnail.url || standardPreviewThumbnail.dataUri) || "" : ""
image: "" //TODO: usefavicon when available
type: standardPreview ? standardPreview.linkType : Constants.StandardLinkPreviewType.Link
}
userData {
name: statusContactPreview ? statusContactPreview.displayName : ""
publicKey: statusContactPreview ? statusContactPreview.publicKey : ""
bio: statusContactPreview ? statusContactPreview.description : ""
image: statusContactPreviewThumbnail ? (statusContactPreviewThumbnail.url || statusContactPreviewThumbnail.dataUri) || "" : ""
ensVerified: false // not supported yet
}
communityData {
name: statusCommunityPreview ? statusCommunityPreview.displayName : ""
description: statusCommunityPreview ? statusCommunityPreview.description : ""
banner: statusCommunityPreviewBanner ? (statusCommunityPreviewBanner.url || statusCommunityPreviewBanner.dataUri) || "" : ""
image: statusCommunityPreviewIcon ? (statusCommunityPreviewIcon.url || statusCommunityPreviewIcon.dataUri) || "" : ""
membersCount: statusCommunityPreview ? statusCommunityPreview.membersCount : 0
color: statusCommunityPreview ? statusCommunityPreview.color : ""
}
channelData {
name: statusCommunityChannelPreview ? statusCommunityChannelPreview.displayName : ""
description: statusCommunityChannelPreview ? statusCommunityChannelPreview.description : ""
emoji: statusCommunityChannelPreview ? statusCommunityChannelPreview.emoji : ""
color: statusCommunityChannelPreview ? statusCommunityChannelPreview.color : ""
communityData {
name: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.displayName : ""
description: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.description : ""
banner: statusCommunityChannelCommunityPreviewBanner ? (statusCommunityChannelCommunityPreviewBanner.url || statusCommunityChannelCommunityPreviewBanner.dataUri) || "" : ""
image: statusCommunityChannelCommunityPreviewIcon ? (statusCommunityChannelCommunityPreviewIcon.url || statusCommunityChannelCommunityPreviewIcon.dataUri) || "" : ""
membersCount: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.membersCount : 0
color: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.color : ""
}
}
}

View File

@ -1 +1,4 @@
ContactListItemDelegate 1.0 ContactListItemDelegate.qml
LinkPreviewCardDelegate 1.0 LinkPreviewCardDelegate.qml
LinkPreviewGifDelegate 1.0 LinkPreviewGifDelegate.qml
LinkPreviewMiniCardDelegate 1.0 LinkPreviewMiniCardDelegate.qml

View File

@ -1201,7 +1201,7 @@ Rectangle {
}
control.fileUrlsAndSources = urls
}
onImageClicked: (chatImage) => Global.openImagePopup(chatImage)
onImageClicked: (chatImage) => Global.openImagePopup(chatImage, "")
onLinkReload: (link) => control.linkPreviewReloaded(link)
onLinkClicked: (link) => Global.openLink(link)
onEnableLinkPreview: () => control.enableLinkPreview()

View File

@ -7,6 +7,8 @@ StatusMenu {
property string url
property string imageSource
property string domain
property bool requireConfirmationOnOpen: false
QtObject {
id: d
@ -43,6 +45,6 @@ StatusMenu {
text: qsTr("Open link")
icon.name: "browser"
enabled: d.isUnfurled
onTriggered: Global.openLink(root.url)
onTriggered: requireConfirmationOnOpen ? Global.openLinkWithConfirmation(root.url, root.domain) : Global.openLink(root.url)
}
}

View File

@ -10,24 +10,29 @@ import StatusQ.Components 0.1
import shared.controls 1.0
import shared.status 1.0
import shared.panels 1.0
import shared.stores 1.0
import shared.controls.chat 1.0
import shared.controls.delegates 1.0
Flow {
id: root
required property var store
required property var messageStore
required property bool isOnline
required property bool playAnimations
required property var linkPreviewModel
required property var gifLinks
required property bool isCurrentUser
required property bool gifUnfurlingEnabled
required property bool canAskToUnfurlGifs
readonly property alias hoveredLink: linksRepeater.hoveredUrl
property string highlightLink: ""
signal imageClicked(var image, var mouse, var imageSource, string url)
signal imageClicked(var image, var mouse, string imageSource, string url)
signal openContextMenu(var item, string url, string domain)
signal setNeverAskAboutUnfurlingAgain(bool neverAskAgain)
function resetLocalAskAboutUnfurling() {
d.localAskAboutUnfurling = true
@ -45,15 +50,25 @@ Flow {
Loader {
visible: active
active: root.gifLinks && root.gifLinks.length > 0
&& !RootStore.gifUnfurlingEnabled
&& d.localAskAboutUnfurling && !RootStore.neverAskAboutUnfurlingAgain
&& !root.gifUnfurlingEnabled
&& d.localAskAboutUnfurling && root.canAskToUnfurlGifs
sourceComponent: enableLinkComponent
}
Repeater {
id: tempRepeater
model: RootStore.gifUnfurlingEnabled ? gifLinks : []
delegate: gifComponent
visible: root.canAskToUnfurlGifs
model: root.gifUnfurlingEnabled ? gifLinks : []
delegate: LinkPreviewGifDelegate {
required property string modelData
link: modelData
isOnline: root.isOnline
isCurrentUser: root.isCurrentUser
playAnimation: root.playAnimations
onClicked: root.imageClicked(imageAlias, mouse, link, link)
}
}
Repeater {
@ -62,154 +77,26 @@ Flow {
property string hoveredUrl: ""
model: root.linkPreviewModel
delegate: Loader {
id: linkMessageLoader
// properties from the model
required property bool unfurled
required property bool empty
required property string url
required property bool immutable
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
readonly property string hostname: standardPreview ? standardPreview.hostname : ""
readonly property string title: standardPreview ? standardPreview.title : ""
readonly property string description: standardPreview ? standardPreview.description : ""
readonly property int standardLinkType: standardPreview ? standardPreview.linkType : ""
readonly property int thumbnailWidth: standardPreviewThumbnail ? standardPreviewThumbnail.width : ""
readonly property int thumbnailHeight: standardPreviewThumbnail ? standardPreviewThumbnail.height : ""
readonly property string thumbnailUrl: standardPreviewThumbnail ? standardPreviewThumbnail.url : ""
readonly property string thumbnailDataUri: standardPreviewThumbnail ? standardPreviewThumbnail.dataUri : ""
asynchronous: true
active: unfurled && !empty
StateGroup {
//Using StateGroup as a warkardound for https://bugreports.qt.io/browse/QTBUG-47796
states: [
State {
name: "standardLinkPreview"
when: linkMessageLoader.previewType === Constants.LinkPreviewType.Standard
PropertyChanges { target: linkMessageLoader; sourceComponent: standardLinkPreviewCard }
},
State {
name: "statusContactLinkPreview"
when: linkMessageLoader.previewType === Constants.LinkPreviewType.StatusContact
PropertyChanges { target: linkMessageLoader; sourceComponent: unfurledProfileLinkComponent }
}
]
delegate: LinkPreviewCardDelegate {
id: delegate
isCurrentUser: root.isCurrentUser
highlight: url === root.highlightLink
onHoveredChanged: {
linksRepeater.hoveredUrl = hovered ? url : ""
}
}
}
Component {
id: standardLinkPreviewCard
LinkPreviewCard {
leftTail: !root.isCurrentUser // WARNING: Is this by design?
bannerImageSource: standardPreviewThumbnail ? standardPreviewThumbnail.url : ""
title: standardPreview ? standardPreview.title : ""
description: standardPreview ? standardPreview.description : ""
footer: standardPreview ? standardPreview.hostname : ""
highlight: root.highlightLink === url
onClicked: (mouse) => {
switch (mouse.button) {
case Qt.RightButton:
root.imageClicked(unfurledLink, mouse, "", url) // request a dumb context menu with just "copy/open link" items
break
default:
Global.openLinkWithConfirmation(url, hostname)
break
if(mouse.button === Qt.RightButton) {
const domain = previewType === Constants.LinkPreviewType.Standard ? linkData.domain : Constants.externalStatusLink
root.openContextMenu(delegate, url, domain)
return
}
}
}
}
Component {
id: unfurledProfileLinkComponent
UserProfileCard {
id: unfurledProfileLink
leftTail: !root.isCurrentUser
userName: statusContactPreview && statusContactPreview.displayName ? statusContactPreview.displayName : ""
userPublicKey: statusContactPreview && statusContactPreview.publicKey ? statusContactPreview.publicKey : ""
userBio: statusContactPreview && statusContactPreview.description ? statusContactPreview.description : ""
userImage: statusContactPreviewThumbnail ? statusContactPreviewThumbnail.url : ""
ensVerified: false // not supported yet
onClicked: {
Global.openProfilePopup(userPublicKey)
}
}
}
if(previewType === Constants.LinkPreviewType.Standard) {
Global.openLinkWithConfirmation(url, linkData.domain)
return
}
//TODO: Remove this once we have gif support in new unfurling flow
Component {
id: gifComponent
CalloutCard {
implicitWidth: linkImage.width
implicitHeight: linkImage.height
leftTail: !root.isCurrentUser
StatusChatImageLoader {
id: linkImage
readonly property bool globalAnimationEnabled: root.messageStore.playAnimation
readonly property string urlLink: modelData
property bool localAnimationEnabled: true
objectName: "LinksMessageView_unfurledImageComponent_linkImage"
anchors.centerIn: parent
source: urlLink
imageWidth: 300
isCurrentUser: root.isCurrentUser
playing: globalAnimationEnabled && localAnimationEnabled
isOnline: root.store.mainModuleInst.isOnline
asynchronous: true
isAnimated: true
onClicked: {
if (!playing)
localAnimationEnabled = true
else
root.imageClicked(linkImage.imageAlias, mouse, source, urlLink)
}
imageAlias.cache: localAnimationEnabled // GIFs can only loop/play properly with cache enabled
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
color: "white"
}
}
}
Timer {
id: animationPlayingTimer
interval: 10000
running: linkImage.isAnimated && linkImage.playing
onTriggered: { linkImage.localAnimationEnabled = false }
}
Global.activateDeepLink(url)
}
}
}
@ -308,7 +195,7 @@ Flow {
text: qsTr("Don't ask me again")
}
}
onClicked: RootStore.setNeverAskAboutUnfurlingAgain(true)
onClicked: root.setNeverAskAboutUnfurlingAgain(true)
Component.onCompleted: {
background.radius = Style.current.padding;
}
@ -316,5 +203,5 @@ Flow {
}
}
}
}

View File

@ -8,6 +8,7 @@ import shared.controls 1.0
import shared.popups 1.0
import shared.views.chat 1.0
import shared.controls.chat 1.0
import shared.stores 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
@ -570,7 +571,7 @@ Loader {
rootStore.chatCommunitySectionModule.switchToChannel(link.replace("#", ""))
return
} else if (Utils.isStatusDeepLink(link)) {
rootStore.activateStatusDeepLink(link)
Global.activateDeepLink(link)
return
}
@ -755,14 +756,20 @@ Loader {
id: linksMessageView
linkPreviewModel: root.linkPreviewModel
gifLinks: root.gifLinks
messageStore: root.messageStore
store: root.rootStore
playAnimations: root.messageStore.playAnimation
isOnline: root.rootStore.mainModuleInst.isOnline
isCurrentUser: root.amISender
highlightLink: delegate.hoveredLink
onImageClicked: (image, mouse, imageSource, url) => {
d.onImageClicked(image, mouse, imageSource, url)
}
onOpenContextMenu: (item, url, domain) => {
Global.openMenu(imageContextMenuComponent, item, { url: url, domain: domain, requireConfirmationOnOpen: true })
}
onHoveredLinkChanged: delegate.highlightedLink = linksMessageView.hoveredLink
gifUnfurlingEnabled: RootStore.gifUnfurlingEnabled
canAskToUnfurlGifs: !RootStore.neverAskAboutUnfurlingAgain
onSetNeverAskAboutUnfurlingAgain: RootStore.setNeverAskAboutUnfurlingAgain(neverAskAgain)
}
}

View File

@ -56,6 +56,7 @@ QtObject {
signal openLink(string link)
signal openLinkWithConfirmation(string link, string domain)
signal activateDeepLink(string link)
signal setNthEnabledSectionActive(int nthSection)
signal appSectionBySectionTypeChanged(int sectionType, int subsection)