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
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
sourceComponent: { 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) { if (Status === Interaction.Status.TRANSFER_FINISHED) {
mediaInfo = MessagesAdapter.getMediaInfo(Body) mediaInfo = MessagesAdapter.getMediaInfo(Body)
if (Object.keys(mediaInfo).length !== 0) if (Object.keys(mediaInfo).length !== 0)
return localMediaMsgComp return localMediaMsgComp
} }
return dataTransferMsgComp return dataTransferMsgComp
default:
// if this happens, adjust FilteredMsgListModel
console.warn("Invalid message type has not been filtered.")
return null
}
}
} }
Component { opacity: 0
id: textMsgComp Behavior on opacity { NumberAnimation { duration: 100 } }
onLoaded: opacity = 1
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,16 +39,26 @@ 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
width: ListView.view ? ListView.view.width : 0
height: mainColumnLayout.implicitHeight
rightPadding: hPadding
leftPadding: hPadding
contentItem: ColumnLayout {
id: mainColumnLayout
anchors.centerIn: parent
width: parent.width
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: hMargin
anchors.rightMargin: hMargin
spacing: 2 spacing: 2
RowLayout { RowLayout {
@ -57,7 +67,7 @@ ColumnLayout {
spacing: 0 spacing: 0
Item { Item {
id: avatarBlock id: avatarBlock
Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hMargin Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hPadding
Layout.preferredHeight: isOutgoing ? 0 : bubble.height Layout.preferredHeight: isOutgoing ? 0 : bubble.height
Avatar { Avatar {
id: avatar id: avatar
@ -96,7 +106,7 @@ ColumnLayout {
Item { Item {
id: infoCell id: infoCell
Layout.preferredWidth: parent.width Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
Label { Label {
@ -112,4 +122,16 @@ ColumnLayout {
anchors.leftMargin: avatarBlockWidth + 6 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 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() {
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
} 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)
}
}
}
if (seq === MsgSeq.last) {
showTime = true
}
}
role: "Type"
DelegateChoice {
roleValue: Interaction.Type.TEXT
TextMessageDelegate {
Component.onCompleted: { Component.onCompleted: {
if (index) { if (index) {
computeTimestampVisibility() computeTimestampVisibility(this, index)
computeSequencing() computeSequencing(this, index)
} else { } else {
Qt.callLater(computeTimestampVisibility) Qt.callLater(computeTimestampVisibility, this, index)
Qt.callLater(computeSequencing) Qt.callLater(computeSequencing, this, index)
}
}
}
}
DelegateChoice {
roleValue: Interaction.Type.CALL
GeneratedMessageDelegate {
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
Qt.callLater(computeTimestampVisibility, this, index)
}
}
}
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() onAtYBeginningChanged: loadMoreMsgsIfNeeded()
function loadMoreMsgsIfNeeded() {
if (atYBeginning && !CurrentConversation.allMessagesLoaded)
MessagesAdapter.loadMoreMessages()
}
Connections { Connections {
target: MessagesAdapter target: MessagesAdapter