1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-07-23 08:55:26 +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/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>

View file

@ -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
width: ListView.view ? ListView.view.width : 0
// message interaction
property string hoveredLink
MouseArea {
id: itemMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
if (root.hoveredLink)
Qt.openUrlExternally(root.hoveredLink)
sourceComponent: {
if (Status === Interaction.Status.TRANSFER_FINISHED) {
mediaInfo = MessagesAdapter.getMediaInfo(Body)
if (Object.keys(mediaInfo).length !== 0)
return localMediaMsgComp
}
return dataTransferMsgComp
}
Loader {
id: loader
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
}
}
}
}
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
}

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.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
readonly property real hPadding: 12
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: hMargin
anchors.rightMargin: hMargin
spacing: 2
width: ListView.view ? ListView.view.width : 0
height: mainColumnLayout.implicitHeight
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
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)
}
}
}

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.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
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)
}
} 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
role: "Type"
DelegateChoice {
roleValue: Interaction.Type.TEXT
TextMessageDelegate {
Component.onCompleted: {
if (index) {
computeTimestampVisibility(this, index)
computeSequencing(this, index)
} 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)
} else {
seq = MsgSeq.last
rAdjustSeq(nItem)
Qt.callLater(computeTimestampVisibility, this, index)
Qt.callLater(computeSequencing, this, index)
}
}
}
if (seq === MsgSeq.last) {
showTime = true
}
DelegateChoice {
roleValue: Interaction.Type.CALL
GeneratedMessageDelegate {
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
Qt.callLater(computeTimestampVisibility, this, index)
}
}
}
Component.onCompleted: {
if (index) {
computeTimestampVisibility()
computeSequencing()
} else {
Qt.callLater(computeTimestampVisibility)
Qt.callLater(computeSequencing)
DelegateChoice {
roleValue: Interaction.Type.CONTACT
GeneratedMessageDelegate {
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
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()
function loadMoreMsgsIfNeeded() {
if (atYBeginning && !CurrentConversation.allMessagesLoaded)
MessagesAdapter.loadMoreMessages()
}
Connections {
target: MessagesAdapter