1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-08-04 14:55:43 +02:00

messages: divide up MessageDelegate and use DelegateChooser

1. GeneratedMessageDelegate, TextMessageDelegate and
   DataTransferMessageDelegate
2. DelegateChooser in MessageListView

Change-Id: I5a3718f59e74b3499afc4abfa2826bab6cf442c8
This commit is contained in:
Ming Rui Zhang 2021-10-01 14:23:14 -04:00
parent e85d4506de
commit 44fdbb8378
6 changed files with 517 additions and 432 deletions

View file

@ -161,10 +161,12 @@
<file>src/commoncomponents/BackButton.qml</file> <file>src/commoncomponents/BackButton.qml</file>
<file>src/commoncomponents/JamiSwitch.qml</file> <file>src/commoncomponents/JamiSwitch.qml</file>
<file>src/mainview/components/ReadOnlyFooter.qml</file> <file>src/mainview/components/ReadOnlyFooter.qml</file>
<file>src/commoncomponents/MessageDelegate.qml</file> <file>src/commoncomponents/TextMessageDelegate.qml</file>
<file>src/mainview/components/MessageListView.qml</file> <file>src/mainview/components/MessageListView.qml</file>
<file>src/commoncomponents/MessageBubble.qml</file> <file>src/commoncomponents/MessageBubble.qml</file>
<file>src/constant/MsgSeq.qml</file> <file>src/constant/MsgSeq.qml</file>
<file>src/commoncomponents/SBSMessageBase.qml</file> <file>src/commoncomponents/SBSMessageBase.qml</file>
<file>src/commoncomponents/GeneratedMessageDelegate.qml</file>
<file>src/commoncomponents/DataTransferMessageDelegate.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -1,7 +1,8 @@
/* /*
* Copyright (C) 2021 by Savoir-faire Linux * Copyright (C) 2021 by Savoir-faire Linux
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com> * Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -24,272 +25,48 @@ import QtGraphicalEffects 1.15
import QtWebEngine 1.10 import QtWebEngine 1.10
import net.jami.Models 1.1 import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import net.jami.Adapters 1.1
Control { Loader {
id: root id: root
readonly property ListView listView: ListView.view
readonly property bool isGenerated: Type === Interaction.Type.CALL ||
Type === Interaction.Type.CONTACT
readonly property string author: Author
readonly property var body: Body
readonly property var timestamp: Timestamp
readonly property bool isOutgoing: model.Author === ""
readonly property var formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
readonly property var linkInfo: LinkPreviewInfo
property var mediaInfo property var mediaInfo
readonly property real senderMargin: 64
readonly property real avatarSize: 32
readonly property real msgRadius: 18
readonly property real hMargin: 12
property bool showTime: false property bool showTime: false
property int seq: MsgSeq.single property int seq: MsgSeq.single
width: parent ? parent.width : 0 width: ListView.view ? ListView.view.width : 0
height: loader.height
// message interaction sourceComponent: {
property string hoveredLink if (Status === Interaction.Status.TRANSFER_FINISHED) {
MouseArea { mediaInfo = MessagesAdapter.getMediaInfo(Body)
id: itemMouseArea if (Object.keys(mediaInfo).length !== 0)
anchors.fill: parent return localMediaMsgComp
acceptedButtons: Qt.LeftButton
onClicked: {
if (root.hoveredLink)
Qt.openUrlExternally(root.hoveredLink)
} }
return dataTransferMsgComp
} }
Loader { opacity: 0
id: loader Behavior on opacity { NumberAnimation { duration: 100 } }
onLoaded: opacity = 1
width: root.width
height: sourceComponent.height
sourceComponent: {
switch (Type) {
case Interaction.Type.TEXT: return textMsgComp
case Interaction.Type.CALL:
case Interaction.Type.CONTACT: return generatedMsgComp
case Interaction.Type.DATA_TRANSFER:
if (Status === Interaction.Status.TRANSFER_FINISHED) {
mediaInfo = MessagesAdapter.getMediaInfo(Body)
if (Object.keys(mediaInfo).length !== 0)
return localMediaMsgComp
}
return dataTransferMsgComp
default:
// if this happens, adjust FilteredMsgListModel
console.warn("Invalid message type has not been filtered.")
return null
}
}
}
Component {
id: textMsgComp
SBSMessageBase {
property real maxMsgWidth: root.width - senderMargin - 2 * hMargin - avatarBlockWidth
property bool isRemoteImage
isOutgoing: root.isOutgoing
showTime: root.showTime
seq: root.seq
author: root.author
formattedTime: root.formattedTime
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
innerContent.children: [
TextEdit {
padding: 10
anchors.right: isOutgoing ? parent.right : undefined
text: '<span style="white-space: pre-wrap">' + body + '</span>'
width: {
if (extraContent.active)
Math.max(extraContent.width,
Math.min(implicitWidth - avatarBlockWidth,
extraContent.minSize) - senderMargin)
else
Math.min(implicitWidth, innerContent.width - senderMargin)
}
height: implicitHeight
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
selectByMouse: true
font.pointSize: 11
font.hintingPreference: Font.PreferNoHinting
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
onLinkHovered: root.hoveredLink = hoveredLink
onLinkActivated: Qt.openUrlExternally(hoveredLink)
readOnly: true
color: isOutgoing ?
JamiTheme.messageOutTxtColor :
JamiTheme.messageInTxtColor
},
Loader {
id: extraContent
width: sourceComponent.width
height: sourceComponent.height
anchors.right: isOutgoing ? parent.right : undefined
property real minSize: 192
property real maxSize: 320
active: linkInfo.url !== undefined
sourceComponent: ColumnLayout {
id: previewContent
spacing: 12
Component.onCompleted: {
isRemoteImage = MessagesAdapter.isRemoteImage(linkInfo.url)
}
HoverHandler {
target: previewContent
onHoveredChanged: {
root.hoveredLink = hovered ? linkInfo.url : ""
}
cursorShape: Qt.PointingHandCursor
}
AnimatedImage {
id: img
cache: true
source: isRemoteImage ?
linkInfo.url :
(hasImage ? linkInfo.image : "")
fillMode: Image.PreserveAspectCrop
mipmap: true
antialiasing: true
autoTransform: true
asynchronous: true
readonly property bool hasImage: linkInfo.image !== null
property real aspectRatio: implicitWidth / implicitHeight
property real adjustedWidth: Math.min(extraContent.maxSize,
Math.max(extraContent.minSize,
maxMsgWidth))
Layout.preferredWidth: adjustedWidth
Layout.preferredHeight: Math.ceil(adjustedWidth / aspectRatio)
Rectangle {
color: JamiTheme.previewImageBackgroundColor
z: -1
anchors.fill: parent
}
layer.enabled: isRemoteImage
layer.effect: OpacityMask {
maskSource: MessageBubble {
Rectangle { height: msgRadius; width: parent.width }
out: isOutgoing
type: seq
width: img.width
height: img.height
radius: msgRadius
}
}
}
Column {
opacity: img.status !== Image.Loading
visible: !isRemoteImage
Layout.preferredWidth: img.width - 2 * hMargin
Layout.leftMargin: hMargin
Layout.rightMargin: hMargin
spacing: 6
Label {
width: parent.width
font.pointSize: 10
font.hintingPreference: Font.PreferNoHinting
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
color: JamiTheme.previewTitleColor
visible: linkInfo.title !== null
text: linkInfo.title
}
Label {
width: parent.width
font.pointSize: 11
font.hintingPreference: Font.PreferNoHinting
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
color: JamiTheme.previewSubtitleColor
visible: linkInfo.description !== null
text: '<a href=" " style="text-decoration: ' +
( hoveredLink ? 'underline' : 'none') + ';"' +
'>' + linkInfo.description + '</a>'
}
Label {
width: parent.width
font.pointSize: 10
font.hintingPreference: Font.PreferNoHinting
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
color: JamiTheme.previewSubtitleColor
text: linkInfo.domain
}
}
}
}
]
Component.onCompleted: {
if (!Linkified) {
MessagesAdapter.parseMessageUrls(Id, Body)
}
}
}
}
Component {
id: generatedMsgComp
Column {
width: root.width
spacing: 2
topPadding: 12
bottomPadding: 12
Label {
width: parent.width
text: body
horizontalAlignment: Qt.AlignHCenter
font.pointSize: 12
color: JamiTheme.chatviewTextColor
}
Item {
id: infoCell
width: parent.width
height: childrenRect.height
Label {
text: formattedTime
color: JamiTheme.timestampColor
visible: showTime || seq === MsgSeq.last
height: visible * implicitHeight
font.pointSize: 9
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
Component { Component {
id: dataTransferMsgComp id: dataTransferMsgComp
SBSMessageBase { SBSMessageBase {
id: dataTransferItem id: dataTransferItem
property var transferStats: MessagesAdapter.getTransferStats(Id, Status) property var transferStats: MessagesAdapter.getTransferStats(Id, Status)
property bool canOpen: Status === Interaction.Status.TRANSFER_FINISHED || isOutgoing property bool canOpen: Status === Interaction.Status.TRANSFER_FINISHED || isOutgoing
property real maxMsgWidth: root.width - senderMargin - property real maxMsgWidth: root.width - senderMargin -
2 * hMargin - avatarBlockWidth 2 * hPadding - avatarBlockWidth
- buttonsLoader.width - 24 - 6 - 24 - buttonsLoader.width - 24 - 6 - 24
isOutgoing: root.isOutgoing
isOutgoing: Author === ""
showTime: root.showTime showTime: root.showTime
seq: root.seq seq: root.seq
author: root.author author: Author
formattedTime: root.formattedTime formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
extraHeight: progressBar.visible ? 18 : 0 extraHeight: progressBar.visible ? 18 : 0
innerContent.children: [ innerContent.children: [
RowLayout { RowLayout {
@ -300,9 +77,8 @@ Control {
target: parent target: parent
enabled: canOpen enabled: canOpen
onHoveredChanged: { onHoveredChanged: {
root.hoveredLink = enabled && hovered ? dataTransferItem.hoveredLink = enabled && hovered ?
("file:///" + body) : ("file:///" + Body) : ""
""
} }
cursorShape: enabled ? cursorShape: enabled ?
Qt.PointingHandCursor : Qt.PointingHandCursor :
@ -393,7 +169,7 @@ Control {
topPadding: 10 topPadding: 10
text: CurrentConversation.isSwarm ? text: CurrentConversation.isSwarm ?
TransferName : TransferName :
body Body
wrapMode: Label.WrapAtWordBoundaryOrAnywhere wrapMode: Label.WrapAtWordBoundaryOrAnywhere
font.weight: Font.DemiBold font.weight: Font.DemiBold
font.pointSize: 11 font.pointSize: 11
@ -404,10 +180,10 @@ Control {
JamiTheme.messageInTxtColor JamiTheme.messageInTxtColor
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
propagateComposedEvents: true
cursorShape: canOpen ? cursorShape: canOpen ?
Qt.PointingHandCursor : Qt.PointingHandCursor :
Qt.ArrowCursor Qt.ArrowCursor
onClicked: if(canOpen) itemMouseArea.clicked(mouse)
} }
} }
Label { Label {
@ -450,11 +226,13 @@ Control {
id: localMediaMsgComp id: localMediaMsgComp
SBSMessageBase { SBSMessageBase {
isOutgoing: root.isOutgoing id: localMediaMsgItem
isOutgoing: Author === ""
showTime: root.showTime showTime: root.showTime
seq: root.seq seq: root.seq
author: root.author author: Author
formattedTime: root.formattedTime formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
bubble.visible: false bubble.visible: false
innerContent.children: [ innerContent.children: [
Loader { Loader {
@ -522,7 +300,7 @@ Control {
antialiasing: true antialiasing: true
autoTransform: false autoTransform: false
asynchronous: true asynchronous: true
source: "file:///" + body source: "file:///" + Body
property real aspectRatio: implicitWidth / implicitHeight property real aspectRatio: implicitWidth / implicitHeight
property real adjustedWidth: Math.min(maxSize, property real adjustedWidth: Math.min(maxSize,
Math.max(minSize, Math.max(minSize,
@ -547,7 +325,7 @@ Control {
HoverHandler { HoverHandler {
target : parent target : parent
onHoveredChanged: { onHoveredChanged: {
root.hoveredLink = hovered ? img.source : "" localMediaMsgItem.hoveredLink = hovered ? img.source : ""
} }
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
@ -557,8 +335,4 @@ Control {
] ]
} }
} }
opacity: 0
Behavior on opacity { NumberAnimation { duration: 40 } }
Component.onCompleted: opacity = 1
} }

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
Column {
id: root
property bool showTime: false
property int seq: MsgSeq.single
width: ListView.view ? ListView.view.width : 0
spacing: 2
topPadding: 12
bottomPadding: 12
Label {
width: parent.width
text: Body
horizontalAlignment: Qt.AlignHCenter
font.pointSize: 12
color: JamiTheme.chatviewTextColor
}
Item {
id: infoCell
width: parent.width
height: childrenRect.height
Label {
text: MessagesAdapter.getFormattedTime(Timestamp)
color: JamiTheme.timestampColor
visible: showTime || seq === MsgSeq.last
height: visible * implicitHeight
font.pointSize: 9
anchors.horizontalCenter: parent.horizontalCenter
}
}
opacity: 0
Behavior on opacity { NumberAnimation { duration: 100 } }
Component.onCompleted: opacity = 1
}

View file

@ -25,7 +25,7 @@ import net.jami.Models 1.1
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
ColumnLayout { Control {
id: root id: root
property alias avatarBlockWidth: avatarBlock.width property alias avatarBlockWidth: avatarBlock.width
@ -39,77 +39,99 @@ ColumnLayout {
property int seq property int seq
property string author property string author
property string formattedTime property string formattedTime
property string hoveredLink
readonly property real senderMargin: 64 readonly property real senderMargin: 64
readonly property real avatarSize: 32 readonly property real avatarSize: 32
readonly property real msgRadius: 18 readonly property real msgRadius: 18
readonly property real hMargin: 12 readonly property real hPadding: 12
anchors.left: parent.left width: ListView.view ? ListView.view.width : 0
anchors.right: parent.right height: mainColumnLayout.implicitHeight
anchors.leftMargin: hMargin
anchors.rightMargin: hMargin
spacing: 2
RowLayout { rightPadding: hPadding
Layout.preferredHeight: innerContent.height + root.extraHeight leftPadding: hPadding
Layout.topMargin: (seq === MsgSeq.first || seq === MsgSeq.single) ? 6 : 0
spacing: 0 contentItem: ColumnLayout {
Item { id: mainColumnLayout
id: avatarBlock
Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hMargin anchors.centerIn: parent
Layout.preferredHeight: isOutgoing ? 0 : bubble.height
Avatar { width: parent.width
id: avatar
visible: !isOutgoing && (seq === MsgSeq.last || seq === MsgSeq.single) spacing: 2
anchors.bottom: parent.bottom
width: avatarSize RowLayout {
height: avatarSize Layout.preferredHeight: innerContent.height + root.extraHeight
imageId: author Layout.topMargin: (seq === MsgSeq.first || seq === MsgSeq.single) ? 6 : 0
showPresenceIndicator: false spacing: 0
mode: Avatar.Mode.Contact Item {
id: avatarBlock
Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hPadding
Layout.preferredHeight: isOutgoing ? 0 : bubble.height
Avatar {
id: avatar
visible: !isOutgoing && (seq === MsgSeq.last || seq === MsgSeq.single)
anchors.bottom: parent.bottom
width: avatarSize
height: avatarSize
imageId: author
showPresenceIndicator: false
mode: Avatar.Mode.Contact
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Column {
id: innerContent
width: parent.width
// place actual content here
}
MessageBubble {
id: bubble
z:-1
out: isOutgoing
type: seq
color: isOutgoing ?
JamiTheme.messageOutBgColor :
JamiTheme.messageInBgColor
radius: msgRadius
anchors.right: isOutgoing ? parent.right : undefined
width: innerContent.childrenRect.width
height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0)
}
} }
} }
Item { Item {
id: infoCell
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.preferredHeight: childrenRect.height
Column {
id: innerContent Label {
width: parent.width text: formattedTime
// place actual content here color: JamiTheme.timestampColor
} visible: showTime || seq === MsgSeq.last
MessageBubble { height: visible * implicitHeight
id: bubble font.pointSize: 9
z:-1
out: isOutgoing anchors.right: !isOutgoing ? undefined : parent.right
type: seq anchors.rightMargin: 8
color: isOutgoing ? anchors.left: isOutgoing ? undefined : parent.left
JamiTheme.messageOutBgColor : anchors.leftMargin: avatarBlockWidth + 6
JamiTheme.messageInBgColor
radius: msgRadius
anchors.right: isOutgoing ? parent.right : undefined
width: innerContent.childrenRect.width
height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0)
} }
} }
} }
Item {
id: infoCell
Layout.preferredWidth: parent.width MouseArea {
Layout.preferredHeight: childrenRect.height id: itemMouseArea
anchors.fill: parent
Label { z: -1
text: formattedTime acceptedButtons: Qt.LeftButton
color: JamiTheme.timestampColor onClicked: {
visible: showTime || seq === MsgSeq.last if (root.hoveredLink)
height: visible * implicitHeight Qt.openUrlExternally(root.hoveredLink)
font.pointSize: 9
anchors.right: !isOutgoing ? undefined : parent.right
anchors.rightMargin: 8
anchors.left: isOutgoing ? undefined : parent.left
anchors.leftMargin: avatarBlockWidth + 6
} }
} }
} }

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
SBSMessageBase {
id : root
property bool isRemoteImage
property real maxMsgWidth: root.width - senderMargin - 2 * hPadding - avatarBlockWidth
isOutgoing: Author === ""
author: Author
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
innerContent.children: [
TextEdit {
padding: 10
anchors.right: isOutgoing ? parent.right : undefined
text: '<span style="white-space: pre-wrap">' + Body + '</span>'
width: {
if (extraContent.active)
Math.max(extraContent.width,
Math.min(implicitWidth - avatarBlockWidth,
extraContent.minSize) - senderMargin)
else
Math.min(implicitWidth, innerContent.width - senderMargin)
}
height: implicitHeight
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
selectByMouse: true
font.pointSize: 11
font.hintingPreference: Font.PreferNoHinting
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
onLinkHovered: root.hoveredLink = hoveredLink
onLinkActivated: Qt.openUrlExternally(hoveredLink)
readOnly: true
color: isOutgoing ?
JamiTheme.messageOutTxtColor :
JamiTheme.messageInTxtColor
},
Loader {
id: extraContent
width: sourceComponent.width
height: sourceComponent.height
anchors.right: isOutgoing ? parent.right : undefined
property real minSize: 192
property real maxSize: 320
active: LinkPreviewInfo.url !== undefined
sourceComponent: ColumnLayout {
id: previewContent
spacing: 12
Component.onCompleted: {
isRemoteImage = MessagesAdapter.isRemoteImage(LinkPreviewInfo.url)
}
HoverHandler {
target: previewContent
onHoveredChanged: {
root.hoveredLink = hovered ? LinkPreviewInfo.url : ""
}
cursorShape: Qt.PointingHandCursor
}
AnimatedImage {
id: img
cache: true
source: isRemoteImage ?
LinkPreviewInfo.url :
(hasImage ? LinkPreviewInfo.image : "")
fillMode: Image.PreserveAspectCrop
mipmap: true
antialiasing: true
autoTransform: true
asynchronous: true
readonly property bool hasImage: LinkPreviewInfo.image !== null
property real aspectRatio: implicitWidth / implicitHeight
property real adjustedWidth: Math.min(extraContent.maxSize,
Math.max(extraContent.minSize,
maxMsgWidth))
Layout.preferredWidth: adjustedWidth
Layout.preferredHeight: Math.ceil(adjustedWidth / aspectRatio)
Rectangle {
color: JamiTheme.previewImageBackgroundColor
z: -1
anchors.fill: parent
}
layer.enabled: isRemoteImage
layer.effect: OpacityMask {
maskSource: MessageBubble {
Rectangle { height: msgRadius; width: parent.width }
out: isOutgoing
type: seq
width: img.width
height: img.height
radius: msgRadius
}
}
}
Column {
opacity: img.status !== Image.Loading
visible: !isRemoteImage
Layout.preferredWidth: img.width - 2 * hPadding
Layout.leftMargin: hPadding
Layout.rightMargin: hPadding
spacing: 6
Label {
width: parent.width
font.pointSize: 10
font.hintingPreference: Font.PreferNoHinting
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
color: JamiTheme.previewTitleColor
visible: LinkPreviewInfo.title !== null
text: LinkPreviewInfo.title
}
Label {
width: parent.width
font.pointSize: 11
font.hintingPreference: Font.PreferNoHinting
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
color: JamiTheme.previewSubtitleColor
visible: LinkPreviewInfo.description !== null
text: '<a href=" " style="text-decoration: ' +
( hoveredLink ? 'underline' : 'none') + ';"' +
'>' + LinkPreviewInfo.description + '</a>'
}
Label {
width: parent.width
font.pointSize: 10
font.hintingPreference: Font.PreferNoHinting
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
color: JamiTheme.previewSubtitleColor
text: LinkPreviewInfo.domain
}
}
}
}
]
opacity: 0
Behavior on opacity { NumberAnimation { duration: 100 } }
Component.onCompleted: {
if (!Linkified) {
MessagesAdapter.parseMessageUrls(Id, Body)
}
opacity = 1
}
}

View file

@ -20,6 +20,7 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import Qt.labs.qmlmodels 1.0
import net.jami.Models 1.1 import net.jami.Models 1.1
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
@ -30,6 +31,118 @@ import "../../commoncomponents"
ListView { ListView {
id: root id: root
function getDistanceToBottom() {
const scrollDiff = ScrollBar.vertical.position -
(1.0 - ScrollBar.vertical.size)
return Math.abs(scrollDiff) * contentHeight
}
function loadMoreMsgsIfNeeded() {
if (atYBeginning && !CurrentConversation.allMessagesLoaded)
MessagesAdapter.loadMoreMessages()
}
// sequencing/timestamps (2-sided style)
function computeTimestampVisibility(item, itemIndex) {
if (root === undefined)
return
var nItem = root.itemAtIndex(itemIndex - 1)
if (nItem && itemIndex !== root.count - 1) {
item.showTime = (nItem.timestamp - item.timestamp) > 60 &&
nItem.formattedTime !== item.formattedTime
} else {
item.showTime = true
var pItem = root.itemAtIndex(itemIndex + 1)
if (pItem) {
pItem.showTime = (item.timestamp - pItem.timestamp) > 60 &&
pItem.formattedTime !== item.formattedTime
}
}
}
function computeSequencing(computeItem, computeItemIndex) {
if (root === undefined)
return
var cItem = {
'author': computeItem.author,
'showTime': computeItem.showTime
}
var pItem = root.itemAtIndex(computeItemIndex + 1)
var nItem = root.itemAtIndex(computeItemIndex - 1)
let isSeq = (item0, item1) =>
item0.author === item1.author && !item0.showTime
let setSeq = function (newSeq, item) {
if (item === undefined)
computeItem.seq = newSeq
else
item.seq = newSeq
}
let rAdjustSeq = function (item) {
if (item.seq === MsgSeq.last)
item.seq = MsgSeq.middle
else if (item.seq === MsgSeq.single)
setSeq(MsgSeq.first, item)
}
let adjustSeq = function (item) {
if (item.seq === MsgSeq.first)
item.seq = MsgSeq.middle
else if (item.seq === MsgSeq.single)
setSeq(MsgSeq.last, item)
}
if (pItem && !nItem) {
if (!isSeq(pItem, cItem)) {
computeItem.seq = MsgSeq.single
} else {
computeItem.seq = MsgSeq.last
rAdjustSeq(pItem)
}
} else if (nItem && !pItem) {
if (!isSeq(cItem, nItem)) {
computeItem.seq = MsgSeq.single
} else {
setSeq(MsgSeq.first)
adjustSeq(nItem)
}
} else if (!nItem && !pItem) {
computeItem.seq = MsgSeq.single
} else {
if (isSeq(pItem, nItem)) {
if (isSeq(pItem, cItem)) {
computeItem.seq = MsgSeq.middle
} else {
computeItem.seq = MsgSeq.single
if (pItem.seq === MsgSeq.first)
pItem.seq = MsgSeq.single
else if (item.seq === MsgSeq.middle)
pItem.seq = MsgSeq.last
if (nItem.seq === MsgSeq.last)
nItem.seq = MsgSeq.single
else if (nItem.seq === MsgSeq.middle)
nItem.seq = MsgSeq.first
}
} else {
if (!isSeq(pItem, cItem)) {
computeItem.seq = MsgSeq.first
adjustSeq(pItem)
} else {
computeItem.seq = MsgSeq.last
rAdjustSeq(nItem)
}
}
}
if (computeItem.seq === MsgSeq.last) {
computeItem.showTime = true
}
}
// fade-in mechanism // fade-in mechanism
Component.onCompleted: fadeAnimation.start() Component.onCompleted: fadeAnimation.start()
Rectangle { Rectangle {
@ -74,135 +187,64 @@ ListView {
model: MessagesAdapter.messageListModel model: MessagesAdapter.messageListModel
delegate: MessageDelegate { delegate: DelegateChooser {
// sequencing/timestamps (2-sided style) id: delegateChooser
function computeTimestampVisibility() {
if (listView === undefined)
return
var nItem = listView.itemAtIndex(index - 1)
if (nItem && index !== listView.count - 1) {
showTime = (nItem.timestamp - timestamp) > 60 &&
nItem.formattedTime !== formattedTime
} else {
showTime = true
var pItem = listView.itemAtIndex(index + 1)
if (pItem) {
pItem.showTime = (timestamp - pItem.timestamp) > 60 &&
pItem.formattedTime !== formattedTime
}
}
}
function computeSequencing() { role: "Type"
if (listView === undefined) DelegateChoice {
return roleValue: Interaction.Type.TEXT
var cItem = { TextMessageDelegate {
'author': author, Component.onCompleted: {
'isGenerated': isGenerated, if (index) {
'showTime': showTime computeTimestampVisibility(this, index)
} computeSequencing(this, index)
var pItem = listView.itemAtIndex(index + 1)
var nItem = listView.itemAtIndex(index - 1)
let isSeq = (item0, item1) =>
item0.author === item1.author &&
!(item0.isGenerated || item1.isGenerated) &&
!item0.showTime
let setSeq = function (newSeq, item) {
if (item === undefined)
seq = isGenerated ? MsgSeq.single : newSeq
else
item.seq = item.isGenerated ? MsgSeq.single : newSeq
}
let rAdjustSeq = function (item) {
if (item.seq === MsgSeq.last)
item.seq = MsgSeq.middle
else if (item.seq === MsgSeq.single)
setSeq(MsgSeq.first, item)
}
let adjustSeq = function (item) {
if (item.seq === MsgSeq.first)
item.seq = MsgSeq.middle
else if (item.seq === MsgSeq.single)
setSeq(MsgSeq.last, item)
}
if (pItem && !nItem) {
if (!isSeq(pItem, cItem)) {
seq = MsgSeq.single
} else {
seq = MsgSeq.last
rAdjustSeq(pItem)
}
} else if (nItem && !pItem) {
if (!isSeq(cItem, nItem)) {
seq = MsgSeq.single
} else {
setSeq(MsgSeq.first)
adjustSeq(nItem)
}
} else if (!nItem && !pItem) {
seq = MsgSeq.single
} else {
if (isSeq(pItem, nItem)) {
if (isSeq(pItem, cItem)) {
seq = MsgSeq.middle
} else { } else {
seq = MsgSeq.single Qt.callLater(computeTimestampVisibility, this, index)
Qt.callLater(computeSequencing, this, index)
if (pItem.seq === MsgSeq.first)
pItem.seq = MsgSeq.single
else if (item.seq === MsgSeq.middle)
pItem.seq = MsgSeq.last
if (nItem.seq === MsgSeq.last)
nItem.seq = MsgSeq.single
else if (nItem.seq === MsgSeq.middle)
nItem.seq = MsgSeq.first
}
} else {
if (!isSeq(pItem, cItem)) {
seq = MsgSeq.first
adjustSeq(pItem)
} else {
seq = MsgSeq.last
rAdjustSeq(nItem)
} }
} }
} }
}
if (seq === MsgSeq.last) { DelegateChoice {
showTime = true roleValue: Interaction.Type.CALL
GeneratedMessageDelegate {
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
Qt.callLater(computeTimestampVisibility, this, index)
}
} }
} }
DelegateChoice {
Component.onCompleted: { roleValue: Interaction.Type.CONTACT
if (index) { GeneratedMessageDelegate {
computeTimestampVisibility() Component.onCompleted: {
computeSequencing() if (index)
} else { computeTimestampVisibility(this, index)
Qt.callLater(computeTimestampVisibility) else
Qt.callLater(computeSequencing) Qt.callLater(computeTimestampVisibility, this, index)
}
}
}
DelegateChoice {
roleValue: Interaction.Type.DATA_TRANSFER
DataTransferMessageDelegate {
Component.onCompleted: {
if (index) {
computeTimestampVisibility(this, index)
computeSequencing(this, index)
} else {
Qt.callLater(computeTimestampVisibility, this, index)
Qt.callLater(computeSequencing, this, index)
}
}
} }
} }
}
function getDistanceToBottom() {
const scrollDiff = ScrollBar.vertical.position -
(1.0 - ScrollBar.vertical.size)
return Math.abs(scrollDiff) * contentHeight
} }
onAtYBeginningChanged: loadMoreMsgsIfNeeded() onAtYBeginningChanged: loadMoreMsgsIfNeeded()
function loadMoreMsgsIfNeeded() {
if (atYBeginning && !CurrentConversation.allMessagesLoaded)
MessagesAdapter.loadMoreMessages()
}
Connections { Connections {
target: MessagesAdapter target: MessagesAdapter