diff --git a/qml.qrc b/qml.qrc index 67fb8da2c23a35c58b3e00eb0f5b225c268c0189..71bf3277b79781cfa71869403fbf08717092adfc 100644 --- a/qml.qrc +++ b/qml.qrc @@ -161,10 +161,12 @@ <file>src/commoncomponents/BackButton.qml</file> <file>src/commoncomponents/JamiSwitch.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/commoncomponents/MessageBubble.qml</file> <file>src/constant/MsgSeq.qml</file> <file>src/commoncomponents/SBSMessageBase.qml</file> + <file>src/commoncomponents/GeneratedMessageDelegate.qml</file> + <file>src/commoncomponents/DataTransferMessageDelegate.qml</file> </qresource> </RCC> diff --git a/src/commoncomponents/MessageDelegate.qml b/src/commoncomponents/DataTransferMessageDelegate.qml similarity index 57% rename from src/commoncomponents/MessageDelegate.qml rename to src/commoncomponents/DataTransferMessageDelegate.qml index 210530e9531397ff9ed3a25a77e9934477a8750c..287aab3634d1c847a8aa25a3b193f12f1d9ba898 100644 --- a/src/commoncomponents/MessageDelegate.qml +++ b/src/commoncomponents/DataTransferMessageDelegate.qml @@ -1,7 +1,8 @@ -/* +/* * 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 @@ -24,272 +25,48 @@ import QtGraphicalEffects 1.15 import QtWebEngine 1.10 import net.jami.Models 1.1 -import net.jami.Adapters 1.1 import net.jami.Constants 1.1 +import net.jami.Adapters 1.1 -Control { +Loader { 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 - - 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 int seq: MsgSeq.single - width: parent ? parent.width : 0 - height: loader.height - - // message interaction - property string hoveredLink - MouseArea { - id: itemMouseArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - if (root.hoveredLink) - Qt.openUrlExternally(root.hoveredLink) - } - } - - Loader { - id: loader - - width: root.width - height: sourceComponent.height + width: ListView.view ? ListView.view.width : 0 - 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 - } + sourceComponent: { + if (Status === Interaction.Status.TRANSFER_FINISHED) { + mediaInfo = MessagesAdapter.getMediaInfo(Body) + if (Object.keys(mediaInfo).length !== 0) + return localMediaMsgComp } + return dataTransferMsgComp } - 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 - } - } - } - } + opacity: 0 + Behavior on opacity { NumberAnimation { duration: 100 } } + onLoaded: opacity = 1 Component { id: dataTransferMsgComp SBSMessageBase { id: dataTransferItem + property var transferStats: MessagesAdapter.getTransferStats(Id, Status) property bool canOpen: Status === Interaction.Status.TRANSFER_FINISHED || isOutgoing property real maxMsgWidth: root.width - senderMargin - - 2 * hMargin - avatarBlockWidth + 2 * hPadding - avatarBlockWidth - buttonsLoader.width - 24 - 6 - 24 - isOutgoing: root.isOutgoing + + isOutgoing: Author === "" showTime: root.showTime seq: root.seq - author: root.author - formattedTime: root.formattedTime + author: Author + formattedTime: MessagesAdapter.getFormattedTime(Timestamp) extraHeight: progressBar.visible ? 18 : 0 innerContent.children: [ RowLayout { @@ -300,9 +77,8 @@ Control { target: parent enabled: canOpen onHoveredChanged: { - root.hoveredLink = enabled && hovered ? - ("file:///" + body) : - "" + dataTransferItem.hoveredLink = enabled && hovered ? + ("file:///" + Body) : "" } cursorShape: enabled ? Qt.PointingHandCursor : @@ -393,7 +169,7 @@ Control { topPadding: 10 text: CurrentConversation.isSwarm ? TransferName : - body + Body wrapMode: Label.WrapAtWordBoundaryOrAnywhere font.weight: Font.DemiBold font.pointSize: 11 @@ -404,10 +180,10 @@ Control { JamiTheme.messageInTxtColor MouseArea { anchors.fill: parent + propagateComposedEvents: true cursorShape: canOpen ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: if(canOpen) itemMouseArea.clicked(mouse) } } Label { @@ -450,11 +226,13 @@ Control { id: localMediaMsgComp SBSMessageBase { - isOutgoing: root.isOutgoing + id: localMediaMsgItem + + isOutgoing: Author === "" showTime: root.showTime seq: root.seq - author: root.author - formattedTime: root.formattedTime + author: Author + formattedTime: MessagesAdapter.getFormattedTime(Timestamp) bubble.visible: false innerContent.children: [ Loader { @@ -522,7 +300,7 @@ Control { antialiasing: true autoTransform: false asynchronous: true - source: "file:///" + body + source: "file:///" + Body property real aspectRatio: implicitWidth / implicitHeight property real adjustedWidth: Math.min(maxSize, Math.max(minSize, @@ -547,7 +325,7 @@ Control { HoverHandler { target : parent onHoveredChanged: { - root.hoveredLink = hovered ? img.source : "" + localMediaMsgItem.hoveredLink = hovered ? img.source : "" } cursorShape: Qt.PointingHandCursor } @@ -557,8 +335,4 @@ Control { ] } } - - opacity: 0 - Behavior on opacity { NumberAnimation { duration: 40 } } - Component.onCompleted: opacity = 1 } diff --git a/src/commoncomponents/GeneratedMessageDelegate.qml b/src/commoncomponents/GeneratedMessageDelegate.qml new file mode 100644 index 0000000000000000000000000000000000000000..b69335c2e5cde759cc4af50d00886b78f3cf1ef1 --- /dev/null +++ b/src/commoncomponents/GeneratedMessageDelegate.qml @@ -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 +} diff --git a/src/commoncomponents/SBSMessageBase.qml b/src/commoncomponents/SBSMessageBase.qml index 0c9cafa72304023618817fcdb14371198c124c3c..ac2114086e0afdfc66337d81296e241f9ae99592 100644 --- a/src/commoncomponents/SBSMessageBase.qml +++ b/src/commoncomponents/SBSMessageBase.qml @@ -25,7 +25,7 @@ import net.jami.Models 1.1 import net.jami.Adapters 1.1 import net.jami.Constants 1.1 -ColumnLayout { +Control { id: root property alias avatarBlockWidth: avatarBlock.width @@ -39,77 +39,99 @@ ColumnLayout { property int seq property string author property string formattedTime + property string hoveredLink readonly property real senderMargin: 64 readonly property real avatarSize: 32 readonly property real msgRadius: 18 - readonly property real hMargin: 12 - - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: hMargin - anchors.rightMargin: hMargin - spacing: 2 - - RowLayout { - Layout.preferredHeight: innerContent.height + root.extraHeight - Layout.topMargin: (seq === MsgSeq.first || seq === MsgSeq.single) ? 6 : 0 - spacing: 0 - Item { - id: avatarBlock - Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hMargin - 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 + readonly property real hPadding: 12 + + width: ListView.view ? ListView.view.width : 0 + height: mainColumnLayout.implicitHeight + + rightPadding: hPadding + leftPadding: hPadding + + contentItem: ColumnLayout { + id: mainColumnLayout + + anchors.centerIn: parent + + width: parent.width + + spacing: 2 + + RowLayout { + Layout.preferredHeight: innerContent.height + root.extraHeight + Layout.topMargin: (seq === MsgSeq.first || seq === MsgSeq.single) ? 6 : 0 + spacing: 0 + 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 { + id: infoCell + 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) + Layout.preferredHeight: childrenRect.height + + Label { + text: formattedTime + color: JamiTheme.timestampColor + visible: showTime || seq === MsgSeq.last + height: visible * implicitHeight + font.pointSize: 9 + + anchors.right: !isOutgoing ? undefined : parent.right + anchors.rightMargin: 8 + anchors.left: isOutgoing ? undefined : parent.left + anchors.leftMargin: avatarBlockWidth + 6 } } } - Item { - id: infoCell - - Layout.preferredWidth: parent.width - Layout.preferredHeight: childrenRect.height - - Label { - text: formattedTime - color: JamiTheme.timestampColor - visible: showTime || seq === MsgSeq.last - height: visible * implicitHeight - font.pointSize: 9 - - anchors.right: !isOutgoing ? undefined : parent.right - anchors.rightMargin: 8 - anchors.left: isOutgoing ? undefined : parent.left - anchors.leftMargin: avatarBlockWidth + 6 + + MouseArea { + id: itemMouseArea + anchors.fill: parent + z: -1 + acceptedButtons: Qt.LeftButton + onClicked: { + if (root.hoveredLink) + Qt.openUrlExternally(root.hoveredLink) } } } diff --git a/src/commoncomponents/TextMessageDelegate.qml b/src/commoncomponents/TextMessageDelegate.qml new file mode 100644 index 0000000000000000000000000000000000000000..61b717023b7aaa611256185bab50a5755a215120 --- /dev/null +++ b/src/commoncomponents/TextMessageDelegate.qml @@ -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 + } +} diff --git a/src/mainview/components/MessageListView.qml b/src/mainview/components/MessageListView.qml index 2224edc14948209d1024e2e220930aabd2c25f4c..a320174b71cef32b9f94d61b03942d08ffd5b67b 100644 --- a/src/mainview/components/MessageListView.qml +++ b/src/mainview/components/MessageListView.qml @@ -20,6 +20,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import Qt.labs.qmlmodels 1.0 import net.jami.Models 1.1 import net.jami.Adapters 1.1 @@ -30,6 +31,118 @@ import "../../commoncomponents" ListView { 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 Component.onCompleted: fadeAnimation.start() Rectangle { @@ -74,135 +187,64 @@ ListView { model: MessagesAdapter.messageListModel - delegate: MessageDelegate { - // sequencing/timestamps (2-sided style) - 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 + delegate: DelegateChooser { + id: delegateChooser + + role: "Type" + DelegateChoice { + roleValue: Interaction.Type.TEXT + TextMessageDelegate { + Component.onCompleted: { + if (index) { + computeTimestampVisibility(this, index) + computeSequencing(this, index) + } else { + Qt.callLater(computeTimestampVisibility, this, index) + Qt.callLater(computeSequencing, this, index) + } } } } - - function computeSequencing() { - if (listView === undefined) - return - var cItem = { - 'author': author, - 'isGenerated': isGenerated, - 'showTime': showTime - } - 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) + DelegateChoice { + roleValue: Interaction.Type.CALL + GeneratedMessageDelegate { + Component.onCompleted: { + if (index) + computeTimestampVisibility(this, index) + else + Qt.callLater(computeTimestampVisibility, this, index) } - } else if (nItem && !pItem) { - if (!isSeq(cItem, nItem)) { - seq = MsgSeq.single - } else { - setSeq(MsgSeq.first) - adjustSeq(nItem) + } + } + DelegateChoice { + roleValue: Interaction.Type.CONTACT + GeneratedMessageDelegate { + Component.onCompleted: { + if (index) + computeTimestampVisibility(this, index) + else + Qt.callLater(computeTimestampVisibility, this, index) } - } else if (!nItem && !pItem) { - seq = MsgSeq.single - } else { - if (isSeq(pItem, nItem)) { - if (isSeq(pItem, cItem)) { - seq = MsgSeq.middle - } else { - 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)) { - seq = MsgSeq.first - adjustSeq(pItem) + } + } + DelegateChoice { + roleValue: Interaction.Type.DATA_TRANSFER + DataTransferMessageDelegate { + Component.onCompleted: { + if (index) { + computeTimestampVisibility(this, index) + computeSequencing(this, index) } else { - seq = MsgSeq.last - rAdjustSeq(nItem) + Qt.callLater(computeTimestampVisibility, this, index) + Qt.callLater(computeSequencing, this, index) } } } - - if (seq === MsgSeq.last) { - showTime = true - } - } - - Component.onCompleted: { - if (index) { - computeTimestampVisibility() - computeSequencing() - } else { - Qt.callLater(computeTimestampVisibility) - Qt.callLater(computeSequencing) - } } } - function getDistanceToBottom() { - const scrollDiff = ScrollBar.vertical.position - - (1.0 - ScrollBar.vertical.size) - return Math.abs(scrollDiff) * contentHeight - } - onAtYBeginningChanged: loadMoreMsgsIfNeeded() - function loadMoreMsgsIfNeeded() { - if (atYBeginning && !CurrentConversation.allMessagesLoaded) - MessagesAdapter.loadMoreMessages() - } - Connections { target: MessagesAdapter