status-desktop/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml
Alex Jbanca 4a30d13bdc
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
2023-10-25 18:20:02 +03:00

273 lines
11 KiB
QML

import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import shared 1.0
import utils 1.0
import "./private" 1.0
CalloutCard {
id: root
enum State {
Invalid,
Loading,
LoadingFailed,
Loaded
}
readonly property LinkData linkData: LinkData { }
readonly property UserData userData: UserData { }
readonly property CommunityData communityData: CommunityData { }
readonly property ChannelData channelData: ChannelData { }
required property int previewState
required property int type
readonly property bool containsMouse: mouseArea.hovered || reloadButton.hovered || closeButton.hovered
signal close()
signal retry()
signal clicked(var eventPoint)
signal rightClicked(var eventPoint)
implicitWidth: 260
implicitHeight: 64
verticalPadding: 15
horizontalPadding: 12
borderColor: Theme.palette.directColor7
backgroundColor: root.containsMouse ? Theme.palette.directColor7 : Theme.palette.baseColor4
// behavior
states: [
State {
name: "invalid"
when: root.previewState === LinkPreviewMiniCard.State.Invalid
PropertyChanges {
target: root
visible: false
}
},
State {
name: "loading"
when: root.previewState === LinkPreviewMiniCard.State.Loading
PropertyChanges { target: root; visible: true; dashedBorder: true; }
PropertyChanges { target: loadingAnimation; visible: true; }
PropertyChanges { target: titleText; text: qsTr("Generating preview..."); color: Theme.palette.baseColor1 }
PropertyChanges { target: subtitleText; visible: false; }
PropertyChanges { target: reloadButton; visible: false; }
},
State {
name: "loadingFailed"
when: root.previewState === LinkPreviewMiniCard.State.LoadingFailed
PropertyChanges { target: root; visible: true; dashedBorder: true; }
PropertyChanges { target: loadingAnimation; visible: false; }
PropertyChanges { target: titleText; text: qsTr("Failed to generate preview"); color: Theme.palette.directColor1 }
PropertyChanges { target: subtitleText; visible: false; }
PropertyChanges { target: reloadButton; visible: true; }
},
State {
name: "loaded"
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.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
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
}
}
]
contentItem: Item {
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
RowLayout {
id: layout
anchors.fill: parent
spacing: 0
LoadingAnimation {
id: loadingAnimation
Layout.alignment: Qt.AlignVCenter
Layout.margins: 4
visible: false
}
StatusSmartIdenticon {
id: favIcon
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 1
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: false
asset.letterSize: asset.charactersLen == 1 ? 10 : 7
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 8
spacing: 0
RowLayout {
id: titleRow
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
StatusBaseText {
id: titleText
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Style.current.additionalTextSize
font.weight: Font.Medium
wrapMode: Text.Wrap
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
maximumLineCount: 1
}
StatusIcon {
id: secondTitleIcon
width: 16
height: 16
icon: "tiny/chevron-right"
color: Theme.palette.baseColor1
visible: secondTitleText.visible
}
StatusBaseText {
id: secondTitleText
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Style.current.additionalTextSize
font.weight: Font.Medium
wrapMode: Text.Wrap
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
maximumLineCount: 1
visible: false
}
}
StatusBaseText {
id: subtitleText
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Style.current.tertiaryTextFontSize
color: Theme.palette.baseColor1
wrapMode: Text.WordWrap
elide: Text.ElideRight
}
}
StatusRoundedImage {
id: thumbnailImage
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 4
implicitWidth: 34
implicitHeight: 34
radius: 4
visible: false
}
StatusFlatButton {
id: reloadButton
icon.name: "refresh"
size: StatusBaseButton.Size.Small
hoverColor: Theme.palette.directColor8
textColor: Theme.palette.directColor1
onClicked: root.retry()
}
StatusFlatButton {
id: closeButton
icon.name: "close"
size: StatusBaseButton.Size.Small
hoverColor: Theme.palette.directColor8
textColor: Theme.palette.directColor1
onClicked: root.close()
}
}
}
HoverHandler {
id: mouseArea
target: background
cursorShape: Qt.PointingHandCursor
}
TapHandler {
id: tapHandler
target: background
onTapped: root.clicked(eventPoint)
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: root.rightClicked(eventPoint)
}
}