1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-07-03 23:25:28 +02:00

chatview: timestamp improvements

New timestamp computation and sequencing ( by day and hour)

GitLab: #827
Change-Id: Ie170f31c075dc37f00d393272410329dc045f2d3
This commit is contained in:
Nicolas 2022-09-12 09:36:50 -04:00 committed by Nicolas Vengeon
parent c3e8e38e99
commit d6ed9adf32
14 changed files with 366 additions and 255 deletions

View file

@ -203,5 +203,6 @@
<file>src/app/mainview/components/CustomizeTipBox.qml</file>
<file>src/app/mainview/components/BackupTipBox.qml</file>
<file>src/app/mainview/components/InformativeTipBox.qml</file>
<file>src/app/commoncomponents/TimestampInfo.qml</file>
</qresource>
</RCC>

View file

@ -27,24 +27,36 @@ Column {
id: root
property bool showTime: false
property int seq: MsgSeq.single
property alias font: textLabel.font
property bool showDay: false
property int timestamp: Timestamp
property string formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
property string formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
property int seq: MsgSeq.single//a changer par textlabel
width: ListView.view ? ListView.view.width : 0
spacing: 2
topPadding: 12
bottomPadding: 12
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
TimestampInfo {
id:timestampItem
showDay: root.showDay
showTime: root.showTime
formattedTime: root.formattedTime
formattedDay: root.formattedDay
Layout.alignment: Qt.AlignHCenter
}
Rectangle {
id: msg
anchors.horizontalCenter: parent.horizontalCenter
width: childrenRect.width
height: JamiTheme.contactMessageAvatarSize + 12
radius: JamiTheme.contactMessageAvatarSize / 2 + 6
Layout.alignment: Qt.AlignVCenter
color: "transparent"
border.width: 1
border.color: CurrentConversation.isCoreDialog ? JamiTheme.messageInBgColor : CurrentConversation.color
@ -54,20 +66,18 @@ Column {
Avatar {
Layout.leftMargin: 6
width: JamiTheme.contactMessageAvatarSize
height: JamiTheme.contactMessageAvatarSize
visible: ActionUri !== ""
imageId: ActionUri !== CurrentAccount.uri ? ActionUri : CurrentAccount.id
showPresenceIndicator: false
mode: ActionUri !== CurrentAccount.uri ? Avatar.Mode.Contact : Avatar.Mode.Account
}
Label {
Layout.rightMargin: 6
id: textLabel
Layout.rightMargin: 6
width: parent.width
text: Body
horizontalAlignment: Qt.AlignHCenter
@ -77,24 +87,7 @@ Column {
}
}
}
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

@ -31,7 +31,12 @@ Loader {
id: root
property var mediaInfo
property bool showTime: false
property bool showTime
property bool showDay
property int timestamp: Timestamp
property string formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
property string formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
property int seq: MsgSeq.single
property string author: Author
@ -70,7 +75,9 @@ Loader {
transferName: TransferName
transferId: Id
readers: Readers
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
timestamp: root.timestamp
formattedTime: root.formattedTime
formattedDay: root.formattedTime
extraHeight: progressBar.visible ? 18 : 0
innerContent.children: [
RowLayout {
@ -247,6 +254,7 @@ Loader {
transferId: Id
readers: Readers
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
bubble.visible: false
innerContent.children: [
Loader {
@ -332,7 +340,7 @@ Loader {
sourceSize.width: width
sourceSize.height: height
source: "file:///" + Body
property real aspectRatio: implicitWidth / implicitHeight
property real aspectRatio: width / implicitHeight
property real adjustedWidth: Math.min(maxSize,
Math.max(minSize,
innerContent.width - senderMargin))

View file

@ -29,39 +29,38 @@ Column {
id: root
property bool showTime: false
property bool showDay: false
property int seq: MsgSeq.single
property alias font: textLabel.font
property int timestamp: Timestamp
property string formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
property string formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
width: ListView.view ? ListView.view.width : 0
spacing: 2
topPadding: 12
bottomPadding: 12
ColumnLayout {
width: parent.width
TimestampInfo {
id:timestampItem
showDay: root.showDay
showTime: root.showTime
formattedTime: root.formattedTime
formattedDay: root.formattedDay
Layout.alignment: Qt.AlignHCenter
}
Label {
id: textLabel
width: parent.width
text: Body
horizontalAlignment: Qt.AlignHCenter
Layout.alignment: 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

View file

@ -37,26 +37,26 @@ Control {
// these MUST be set but we won't use the 'required' keyword yet
property bool isOutgoing
property bool showTime
property bool showTime: false
property bool showDay: false
property int seq
property string author
property string transferId
property string registeredNameText
property string transferName
property string formattedTime
property string formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
property string formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
property string location
property string id: Id
property string hoveredLink
property var readers: []
property int timestamp: Timestamp
readonly property real senderMargin: 64
readonly property real avatarSize: 20
readonly property real msgRadius: 20
readonly property real hPadding: JamiTheme.sbsMessageBasePreferredPadding
width: ListView.view ? ListView.view.width : 0
height: mainColumnLayout.implicitHeight
rightPadding: hPadding
leftPadding: hPadding
@ -64,11 +64,21 @@ Control {
id: mainColumnLayout
anchors.centerIn: parent
width: parent.width - hPadding * 2
spacing: 0
TimestampInfo {
id: timestampItem
showDay: root.showDay
showTime: root.showTime
formattedTime: root.formattedTime
formattedDay: root.formattedDay
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.fillHeight: true
}
Item {
id: usernameblock
@ -107,13 +117,11 @@ Control {
}
}
MouseArea {
id: itemMouseArea
Layout.fillWidth: true
Layout.fillHeight: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function (mouse) {
if (mouse.button === Qt.RightButton
@ -194,6 +202,7 @@ Control {
id: readsOne
visible: root.readers.length === 1 && CurrentAccount.sendReadReceipt
width: {
if (root.readers.length === 0)
return 0
@ -216,28 +225,12 @@ Control {
orientation: ListView.Horizontal
Layout.preferredHeight: {
if (showTime || seq === MsgSeq.last)
return contentHeight + formattedTimeLabel.contentHeight
return contentHeight + timestampItem.contentHeight
else if (readsMultiple.visible)
return JamiTheme.avatarReadReceiptSize
return 0
}
Label {
id: formattedTimeLabel
text: formattedTime
color: JamiTheme.timestampColor
visible: showTime || seq === MsgSeq.last
height: visible * implicitHeight
font.pointSize: 9
topPadding : 4
anchors.rightMargin: status.width
anchors.right: !isOutgoing ? undefined : readsMultiple.left
anchors.left: isOutgoing ? undefined : parent.left
anchors.leftMargin: avatarBlockWidth + 6
}
ReadStatus {
id: readsMultiple
visible: root.readers.length > 1 && CurrentAccount.sendReadReceipt
@ -254,7 +247,6 @@ Control {
anchors.topMargin: 1
readers: root.readers
}
}
}

View file

@ -21,7 +21,6 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
@ -31,25 +30,21 @@ SBSMessageBase {
id : root
property bool isRemoteImage
property real maxMsgWidth: root.width - senderMargin - 2 * hPadding - avatarBlockWidth
isOutgoing: Author === ""
author: Author
readers: Readers
timestamp: Timestamp
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
innerContent.children: [
TextEdit {
padding: JamiTheme.preferredMarginSize
anchors.right: isOutgoing ? parent.right : undefined
text: Body
horizontalAlignment: Text.AlignLeft
width: {
if (extraContent.active)
Math.max(extraContent.width,
@ -62,9 +57,7 @@ SBSMessageBase {
height: implicitHeight
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
selectByMouse: true
font.pixelSize: JamiTheme.chatviewFontSize
font.hintingPreference: Font.PreferNoHinting
renderType: Text.NativeRendering
textFormat: Text.MarkdownText

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
* Author: Nicolas Vengeon <nicolas.vengeon@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 "../mainview/components/"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
ColumnLayout{
id: root
property bool showTime
property bool showDay
property string formattedTime
property string formattedDay
property real detailsOpacity: 0.6
Connections {
target: MessagesAdapter.messageListModel
function onTimestampUpdate() {
if (showTime || showDay) {
formattedTime = MessagesAdapter.getFormattedTime(Timestamp)
}
}
}
Item {
visible: showDay
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: JamiTheme.dayTimestampTopMargin
Rectangle {
id: line
height: 1
opacity: detailsOpacity
color:JamiTheme.timestampColor
width: chatView.width - JamiTheme.timestampLinePadding
anchors.centerIn: parent
}
Rectangle {
id: dayRectangle
width: borderRectangle.width
height: borderRectangle.height
radius: 5
color: JamiTheme.chatviewBgColor
Layout.fillHeight: true
anchors.centerIn: parent
Rectangle {
id: borderRectangle
border { color: JamiTheme.timestampColor; width: 1}
opacity: detailsOpacity
width: formattedDayLabel.width + JamiTheme.dayTimestampVPadding
height: formattedDayLabel.height + JamiTheme.dayTimestampHPadding
radius: dayRectangle.radius
color: JamiTheme.transparentColor
}
Text {
id: formattedDayLabel
color: JamiTheme.chatviewTextColor
anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter}
text: formattedDay
font.pointSize: JamiTheme.timestampFont
}
}
}
Label {
id: formattedTimeLabel
text: formattedTime
Layout.bottomMargin: JamiTheme.timestampBottomMargin
Layout.topMargin: JamiTheme.timestampTopMargin
Layout.alignment: Qt.AlignHCenter
color: JamiTheme.timestampColor
visible: showTime
height: visible * implicitHeight
font.pointSize: JamiTheme.timestampFont
}
}

View file

@ -321,6 +321,16 @@ Item {
property real lineEditContextMenuItemsWidth: 100
property real lineEditContextMenuSeparatorsHeight: 2
//TimestampInfo
property int timestampLinePadding: 40
property int dayTimestampTopMargin: 30
property int timestampBottomMargin: 42
property int timestampTopMargin: 20
property int dayTimestampHPadding: 16
property real dayTimestampVPadding: 32
property real timestampFont: calcSize(12)
property int timestampIntervalTime: 120
// Jami switch
property real switchIndicatorRadius: 30

View file

@ -44,105 +44,95 @@ JamiListView {
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
function computeTimestampVisibility(item1, item1Index, item2, item2Index) {
if (item1 && item2) {
if (item1Index < item2Index) {
item1.showTime = item1.timestamp - item2.timestamp > JamiTheme.timestampIntervalTime
item1.showDay = item1.formattedDay !== item2.formattedDay
}else {
item.showTime = true
var pItem = root.itemAtIndex(itemIndex + 1)
if (pItem) {
pItem.showTime = (item.timestamp - pItem.timestamp) > 60 &&
pItem.formattedTime !== item.formattedTime
item2.showTime = item2.timestamp - item1.timestamp > JamiTheme.timestampIntervalTime
item2.showDay = item2.formattedDay !== item1.formattedDay
}
return true
}
return false
}
function computeSequencing(computeItem, computeItemIndex) {
if (root === undefined)
return
var cItem = {
'author': computeItem.author,
'showTime': computeItem.showTime
function computeChatview(item,itemIndex) {
if (!root ) return
var rootItem = root.itemAtIndex(0)
var pItem = root.itemAtIndex(itemIndex - 1)
var pItemIndex = itemIndex - 1
var nItem = root.itemAtIndex(itemIndex + 1)
var nItemIndex = itemIndex + 1
//Middle insertion
if (pItem && nItem) {
computeTimestampVisibility(item, itemIndex, nItem, nItemIndex)
computeSequencing(nItemIndex, nItem, root.itemAtIndex(itemIndex + 2), item)
}
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)
}
//top buffer insertion = scroll up
if (pItem && !nItem) {
if (!isSeq(pItem, cItem)) {
computeItem.seq = MsgSeq.single
} else {
computeItem.seq = MsgSeq.last
rAdjustSeq(pItem)
computeTimestampVisibility(item, itemIndex, pItem, pItemIndex)
computeSequencing(pItemIndex, pItem, item, root.itemAtIndex(itemIndex - 2))
}
} else if (nItem && !pItem) {
if (!isSeq(cItem, nItem)) {
computeItem.seq = MsgSeq.single
} else {
setSeq(MsgSeq.first)
adjustSeq(nItem)
//bottom buffer insertion = scroll down
if (!pItem && nItem) {
computeTimestampVisibility(item, itemIndex, nItem, nItemIndex)
computeSequencing(nItemIndex, nItem, root.itemAtIndex(itemIndex + 2), item)
}
} 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
//index 0 insertion = new message
if (itemIndex === 0) {
Qt.callLater(computeSequencing, itemIndex, item, root.itemAtIndex(itemIndex + 1), null)
if (! computeTimestampVisibility(item, itemIndex, nItem, nItemIndex)) {
Qt.callLater(computeChatview, item, itemIndex)
}
} else {
if (!isSeq(pItem, cItem)) {
computeItem.seq = MsgSeq.first
adjustSeq(pItem)
} else {
computeItem.seq = MsgSeq.last
rAdjustSeq(nItem)
}
//top element
if(itemIndex === root.count - 1 && CurrentConversation.allMessagesLoaded) {
item.showTime = true
item.showDay = true
}
}
if (computeItem.seq === MsgSeq.last) {
computeItem.showTime = true
function computeSequencing(index, item, nItem, pItem) {
if (root === undefined || !item)
return
function isFirst() {
if (!nItem) return true
else{
if (item.showTime) {
return true
}
if (nItem.author !== item.author) {
return true
}
}
return false
}
function isLast() {
if (!pItem) return true
else{
if (pItem.showTime) {
return true
}
if (pItem.author !== item.author) {
return true
}
}
return false
}
if (isLast() && isFirst())
item.seq = MsgSeq.single
if (!isLast() && isFirst())
item.seq = MsgSeq.first
if (isLast() && !isFirst())
item.seq = MsgSeq.last
if (!isLast() && !isFirst())
item.seq = MsgSeq.middle
}
// fade-in mechanism
@ -189,6 +179,7 @@ JamiListView {
width: parent.width
// this offscreen caching is pretty huge
// displayMarginEnd may be removed
displayMarginBeginning: 2048
displayMarginEnd: 2048
maximumFlickVelocity: 2048
@ -216,65 +207,55 @@ JamiListView {
id: delegateChooser
role: "Type"
DelegateChoice {
id: 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)
}
computeChatview(this,index)
}
}
}
DelegateChoice {
roleValue: Interaction.Type.CALL
GeneratedMessageDelegate {
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
Qt.callLater(computeTimestampVisibility, this, index)
computeChatview(this,index)
}
}
}
DelegateChoice {
roleValue: Interaction.Type.CONTACT
ContactMessageDelegate {
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
Qt.callLater(computeTimestampVisibility, this, index)
computeChatview(this,index)
}
}
}
DelegateChoice {
roleValue: Interaction.Type.INITIAL
GeneratedMessageDelegate {
font.bold: true
Component.onCompleted: {
if (index)
computeTimestampVisibility(this, index)
else
Qt.callLater(computeTimestampVisibility, this, index)
computeChatview(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)
}
computeChatview(this,index)
}
}
}

View file

@ -581,21 +581,41 @@ MessagesAdapter::getFormattedTime(const quint64 timestamp)
{
const auto now = QDateTime::currentDateTime();
const auto seconds = now.toSecsSinceEpoch() - timestamp;
auto interval = qFloor(seconds / (3600 * 24));
if (interval > 5)
return QLocale::system().toString(QDateTime::fromSecsSinceEpoch(timestamp),
QLocale::ShortFormat);
if (interval > 1)
return QObject::tr("%1 days ago").arg(interval);
if (interval == 1)
return QObject::tr("one day ago");
interval = qFloor(seconds / 3600);
if (interval > 1)
return QObject::tr("%1 hours ago").arg(interval);
if (interval == 1)
return QObject::tr("one hour ago");
interval = qFloor(seconds / 60);
if (interval > 1)
return QObject::tr("%1 minutes ago").arg(interval);
auto interval = qFloor(seconds / 60);
if (interval > 1) {
auto curLang = settingsManager_->getValue(Settings::Key::LANG);
auto curLocal(QLocale(curLang.toString()));
auto curTime = QDateTime::fromSecsSinceEpoch(timestamp).time();
QString timeLocale;
if (curLang == "SYSTEM")
timeLocale = QLocale::system().toString(curTime, QLocale::system().ShortFormat);
else
timeLocale = curLocal.toString(curTime, curLocal.ShortFormat);
return timeLocale;
}
return QObject::tr("just now");
}
QString
MessagesAdapter::getFormattedDay(const quint64 timestamp)
{
auto now = QDate::currentDate();
auto before = QDateTime::fromSecsSinceEpoch(timestamp).date();
if (before == now)
return QObject::tr("Today");
if (before.daysTo(now) == 1)
return QObject::tr("Yesterday");
auto curLang = settingsManager_->getValue(Settings::Key::LANG);
auto curLocal(QLocale(curLang.toString()));
auto curDate = QDateTime::fromSecsSinceEpoch(timestamp).date();
QString dateLocale;
if (curLang == "SYSTEM")
dateLocale = QLocale::system().toString(curDate, QLocale::system().ShortFormat);
else
dateLocale = curLocal.toString(curDate, curLocal.ShortFormat);
return dateLocale;
}

View file

@ -79,6 +79,7 @@ protected:
Q_INVOKABLE QVariantMap isLocalImage(const QString& mimeName);
Q_INVOKABLE QVariantMap getMediaInfo(const QString& msg);
Q_INVOKABLE bool isRemoteImage(const QString& msg);
Q_INVOKABLE QString getFormattedDay(const quint64 timestamp);
Q_INVOKABLE QString getFormattedTime(const quint64 timestamp);
Q_INVOKABLE void parseMessageUrls(const QString& messageId,
const QString& msg,

View file

@ -62,7 +62,6 @@ RowLayout {
Layout.preferredWidth: root.itemWidth
Layout.preferredHeight: JamiTheme.preferredFieldHeight
Layout.alignment: Qt.AlignCenter
font.pointSize: JamiTheme.buttonFontSize
font.kerning: true
@ -91,7 +90,6 @@ RowLayout {
MaterialToolTip {
id: toolTip
parent: textField
visible: textField.hovered && (root.tooltipText.length > 0)
delay: Qt.styleHints.mousePressAndHoldInterval

View file

@ -36,7 +36,11 @@ using reverseIterator = MessageListModel::reverseIterator;
MessageListModel::MessageListModel(QObject* parent)
: QAbstractListModel(parent)
{}
, timestampTimer_(new QTimer(this))
{
connect(timestampTimer_, &QTimer::timeout, this, &MessageListModel::timestampUpdate);
timestampTimer_->start(1000);
}
QPair<iterator, bool>
MessageListModel::emplace(const QString& msgId, interaction::Info message, bool beginning)

View file

@ -22,6 +22,7 @@
#include "api/interaction.h"
#include <QAbstractListModel>
#include <QTimer>
namespace lrc {
namespace api {
@ -125,6 +126,8 @@ public:
void emitDataChanged(iterator it, VectorInt roles = {});
void emitDataChanged(const QString& msgId, VectorInt roles = {});
Q_SIGNAL void timestampUpdate();
protected:
using Role = MessageList::Role;
@ -144,6 +147,8 @@ private:
iterator insertMessage(iterator it, item_t& message);
void removeMessage(int index, iterator it);
void moveMessage(int from, int to);
QTimer* timestampTimer_ {nullptr};
};
} // namespace api
} // namespace lrc