From 4a30d13bdcc61c6a172771ea21ac345528eadaa2 Mon Sep 17 00:00:00 2001
From: Alex Jbanca <47811206+alexjba@users.noreply.github.com>
Date: Wed, 25 Oct 2023 18:20:02 +0300
Subject: [PATCH] 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
---
.../pages/ChatInputLinksPreviewAreaPage.qml | 122 +------
storybook/pages/LinkPreviewCardPage.qml | 324 +++++++++++++-----
storybook/pages/LinkPreviewMiniCardPage.qml | 44 ++-
storybook/pages/LinksMessageViewPage.qml | 125 ++-----
storybook/pages/StatusChatInputPage.qml | 21 +-
storybook/pages/UserProfileCardPage.qml | 86 -----
storybook/src/Models/LinkPreviewModel.qml | 136 ++++++++
storybook/src/Models/qmldir | 1 +
ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml | 24 ++
.../src/assets/img/icons/active-members.svg | 3 +
.../tests/TestUtils/tst_test-LocaleUtils.qml | 164 +++++++++
ui/app/mainui/AppMain.qml | 4 +
.../chat/ChatInputLinksPreviewArea.qml | 53 +--
.../shared/controls/chat/LinkPreviewCard.qml | 251 +++++++++++---
.../controls/chat/LinkPreviewMiniCard.qml | 142 ++++----
.../shared/controls/chat/UserProfileCard.qml | 78 -----
.../controls/chat/private/ChannelData.qml | 9 +
.../controls/chat/private/CommunityData.qml | 11 +
.../shared/controls/chat/private/LinkData.qml | 10 +
.../shared/controls/chat/private/UserData.qml | 9 +
ui/imports/shared/controls/chat/qmldir | 1 -
.../delegates/LinkPreviewCardDelegate.qml | 91 +++++
.../delegates/LinkPreviewGifDelegate.qml | 80 +++++
.../delegates/LinkPreviewMiniCardDelegate.qml | 86 +++++
ui/imports/shared/controls/delegates/qmldir | 3 +
ui/imports/shared/status/StatusChatInput.qml | 2 +-
.../shared/views/chat/ImageContextMenu.qml | 4 +-
.../shared/views/chat/LinksMessageView.qml | 195 +++--------
ui/imports/shared/views/chat/MessageView.qml | 13 +-
ui/imports/utils/Global.qml | 1 +
30 files changed, 1272 insertions(+), 821 deletions(-)
delete mode 100644 storybook/pages/UserProfileCardPage.qml
create mode 100644 storybook/src/Models/LinkPreviewModel.qml
create mode 100644 ui/StatusQ/src/assets/img/icons/active-members.svg
delete mode 100644 ui/imports/shared/controls/chat/UserProfileCard.qml
create mode 100644 ui/imports/shared/controls/chat/private/ChannelData.qml
create mode 100644 ui/imports/shared/controls/chat/private/CommunityData.qml
create mode 100644 ui/imports/shared/controls/chat/private/LinkData.qml
create mode 100644 ui/imports/shared/controls/chat/private/UserData.qml
create mode 100644 ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml
create mode 100644 ui/imports/shared/controls/delegates/LinkPreviewGifDelegate.qml
create mode 100644 ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml
diff --git a/storybook/pages/ChatInputLinksPreviewAreaPage.qml b/storybook/pages/ChatInputLinksPreviewAreaPage.qml
index cb5074f812..adde059d23 100644
--- a/storybook/pages/ChatInputLinksPreviewAreaPage.qml
+++ b/storybook/pages/ChatInputLinksPreviewAreaPage.qml
@@ -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
}
}
diff --git a/storybook/pages/LinkPreviewCardPage.qml b/storybook/pages/LinkPreviewCardPage.qml
index 2c89b65fa8..3bb6f88975 100644
--- a/storybook/pages/LinkPreviewCardPage.qml
+++ b/storybook/pages/LinkPreviewCardPage.qml
@@ -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: ` 629.2K 112.1K`.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 %3`.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/💬-Chat⎜Desktop?type=design&node-id=22347-219545&mode=design&t=bODv5MUGQgU9ThJF-0
diff --git a/storybook/pages/LinkPreviewMiniCardPage.qml b/storybook/pages/LinkPreviewMiniCardPage.qml
index bf08923785..efb35722e9 100644
--- a/storybook/pages/LinkPreviewMiniCardPage.qml
+++ b/storybook/pages/LinkPreviewMiniCardPage.qml
@@ -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/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0"
\ No newline at end of file
+//"https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0"
diff --git a/storybook/pages/LinksMessageViewPage.qml b/storybook/pages/LinksMessageViewPage.qml
index e947d47f39..708594e87d 100644
--- a/storybook/pages/LinksMessageViewPage.qml
+++ b/storybook/pages/LinksMessageViewPage.qml
@@ -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
+ }
}
}
}
diff --git a/storybook/pages/StatusChatInputPage.qml b/storybook/pages/StatusChatInputPage.qml
index 15f0b14896..36af98ea30 100644
--- a/storybook/pages/StatusChatInputPage.qml
+++ b/storybook/pages/StatusChatInputPage.qml
@@ -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)
}
})
}
diff --git a/storybook/pages/UserProfileCardPage.qml b/storybook/pages/UserProfileCardPage.qml
deleted file mode 100644
index 3c04ceeb11..0000000000
--- a/storybook/pages/UserProfileCardPage.qml
+++ /dev/null
@@ -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/💬-Chat⎜Desktop?type=design&node-id=21961-655678&mode=design&t=JiMnPfMaLPWlrFK3-0
diff --git a/storybook/src/Models/LinkPreviewModel.qml b/storybook/src/Models/LinkPreviewModel.qml
new file mode 100644
index 0000000000..5ca01dfe1d
--- /dev/null
+++ b/storybook/src/Models/LinkPreviewModel.qml
@@ -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())
+ }
+}
diff --git a/storybook/src/Models/qmldir b/storybook/src/Models/qmldir
index 8d4f18cd30..7e2c826713 100644
--- a/storybook/src/Models/qmldir
+++ b/storybook/src/Models/qmldir
@@ -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
diff --git a/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml b/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml
index 8737ffa1fc..c19ec2b1f5 100644
--- a/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml
+++ b/ui/StatusQ/src/StatusQ/Core/LocaleUtils.qml
@@ -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 {
diff --git a/ui/StatusQ/src/assets/img/icons/active-members.svg b/ui/StatusQ/src/assets/img/icons/active-members.svg
new file mode 100644
index 0000000000..3870e6a58e
--- /dev/null
+++ b/ui/StatusQ/src/assets/img/icons/active-members.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/StatusQ/tests/TestUtils/tst_test-LocaleUtils.qml b/ui/StatusQ/tests/TestUtils/tst_test-LocaleUtils.qml
index 86bd88b786..52c3d39181 100644
--- a/ui/StatusQ/tests/TestUtils/tst_test-LocaleUtils.qml
+++ b/ui/StatusQ/tests/TestUtils/tst_test-LocaleUtils.qml
@@ -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)
+ }
}
diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml
index 8279044fee..a6a6795f8e 100644
--- a/ui/app/mainui/AppMain.qml
+++ b/ui/app/mainui/AppMain.qml
@@ -277,6 +277,10 @@ Item {
popups.openConfirmExternalLinkPopup(link, domain)
}
+ function onActivateDeepLink(link: string) {
+ appMain.rootStore.mainModuleInst.activateStatusDeepLink(link)
+ }
+
function onPlaySendMessageSound() {
sendMessageSound.stop()
sendMessageSound.play()
diff --git a/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml b/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
index a380b5b621..b897b8b196 100644
--- a/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
+++ b/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
@@ -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)
diff --git a/ui/imports/shared/controls/chat/LinkPreviewCard.qml b/ui/imports/shared/controls/chat/LinkPreviewCard.qml
index 6f6ef6722b..0e11ea4b8c 100644
--- a/ui/imports/shared/controls/chat/LinkPreviewCard.qml
+++ b/ui/imports/shared/controls/chat/LinkPreviewCard.qml
@@ -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: ""
}
}
diff --git a/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml b/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml
index f8a8bcbc58..e9da236658 100644
--- a/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml
+++ b/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml
@@ -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 {
diff --git a/ui/imports/shared/controls/chat/UserProfileCard.qml b/ui/imports/shared/controls/chat/UserProfileCard.qml
deleted file mode 100644
index 037e0df933..0000000000
--- a/ui/imports/shared/controls/chat/UserProfileCard.qml
+++ /dev/null
@@ -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()
- }
-}
\ No newline at end of file
diff --git a/ui/imports/shared/controls/chat/private/ChannelData.qml b/ui/imports/shared/controls/chat/private/ChannelData.qml
new file mode 100644
index 0000000000..f54e82b042
--- /dev/null
+++ b/ui/imports/shared/controls/chat/private/ChannelData.qml
@@ -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 {}
+}
diff --git a/ui/imports/shared/controls/chat/private/CommunityData.qml b/ui/imports/shared/controls/chat/private/CommunityData.qml
new file mode 100644
index 0000000000..b70d7c3db5
--- /dev/null
+++ b/ui/imports/shared/controls/chat/private/CommunityData.qml
@@ -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
+}
diff --git a/ui/imports/shared/controls/chat/private/LinkData.qml b/ui/imports/shared/controls/chat/private/LinkData.qml
new file mode 100644
index 0000000000..d75f35c5ac
--- /dev/null
+++ b/ui/imports/shared/controls/chat/private/LinkData.qml
@@ -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
+}
diff --git a/ui/imports/shared/controls/chat/private/UserData.qml b/ui/imports/shared/controls/chat/private/UserData.qml
new file mode 100644
index 0000000000..437215277d
--- /dev/null
+++ b/ui/imports/shared/controls/chat/private/UserData.qml
@@ -0,0 +1,9 @@
+import QtQuick 2.15
+
+QtObject {
+ property string name
+ property string publicKey
+ property string bio
+ property string image
+ property bool ensVerified
+}
diff --git a/ui/imports/shared/controls/chat/qmldir b/ui/imports/shared/controls/chat/qmldir
index 5d683d5261..4068bec001 100644
--- a/ui/imports/shared/controls/chat/qmldir
+++ b/ui/imports/shared/controls/chat/qmldir
@@ -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
diff --git a/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml b/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml
new file mode 100644
index 0000000000..b0af202721
--- /dev/null
+++ b/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml
@@ -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 : ""
+ }
+ }
+}
diff --git a/ui/imports/shared/controls/delegates/LinkPreviewGifDelegate.qml b/ui/imports/shared/controls/delegates/LinkPreviewGifDelegate.qml
new file mode 100644
index 0000000000..c6326f8f0d
--- /dev/null
+++ b/ui/imports/shared/controls/delegates/LinkPreviewGifDelegate.qml
@@ -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 }
+ }
+ }
+}
diff --git a/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml b/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml
new file mode 100644
index 0000000000..4520508736
--- /dev/null
+++ b/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml
@@ -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 : ""
+ }
+ }
+}
diff --git a/ui/imports/shared/controls/delegates/qmldir b/ui/imports/shared/controls/delegates/qmldir
index 76d9aa335d..81e214886b 100644
--- a/ui/imports/shared/controls/delegates/qmldir
+++ b/ui/imports/shared/controls/delegates/qmldir
@@ -1 +1,4 @@
ContactListItemDelegate 1.0 ContactListItemDelegate.qml
+LinkPreviewCardDelegate 1.0 LinkPreviewCardDelegate.qml
+LinkPreviewGifDelegate 1.0 LinkPreviewGifDelegate.qml
+LinkPreviewMiniCardDelegate 1.0 LinkPreviewMiniCardDelegate.qml
diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml
index 880809f80e..e2220d31d7 100644
--- a/ui/imports/shared/status/StatusChatInput.qml
+++ b/ui/imports/shared/status/StatusChatInput.qml
@@ -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()
diff --git a/ui/imports/shared/views/chat/ImageContextMenu.qml b/ui/imports/shared/views/chat/ImageContextMenu.qml
index 8301b40042..4eae7f8118 100644
--- a/ui/imports/shared/views/chat/ImageContextMenu.qml
+++ b/ui/imports/shared/views/chat/ImageContextMenu.qml
@@ -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)
}
}
diff --git a/ui/imports/shared/views/chat/LinksMessageView.qml b/ui/imports/shared/views/chat/LinksMessageView.qml
index b87f37596a..2e74edbdd4 100644
--- a/ui/imports/shared/views/chat/LinksMessageView.qml
+++ b/ui/imports/shared/views/chat/LinksMessageView.qml
@@ -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 {
}
}
}
-
}
+
diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml
index 4f270857f5..870a34a0ad 100644
--- a/ui/imports/shared/views/chat/MessageView.qml
+++ b/ui/imports/shared/views/chat/MessageView.qml
@@ -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)
}
}
diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml
index 07b799accb..f75814587c 100644
--- a/ui/imports/utils/Global.qml
+++ b/ui/imports/utils/Global.qml
@@ -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)