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:
parent
c3e8e38e99
commit
d6ed9adf32
14 changed files with 366 additions and 255 deletions
1
qml.qrc
1
qml.qrc
|
@ -203,5 +203,6 @@
|
||||||
<file>src/app/mainview/components/CustomizeTipBox.qml</file>
|
<file>src/app/mainview/components/CustomizeTipBox.qml</file>
|
||||||
<file>src/app/mainview/components/BackupTipBox.qml</file>
|
<file>src/app/mainview/components/BackupTipBox.qml</file>
|
||||||
<file>src/app/mainview/components/InformativeTipBox.qml</file>
|
<file>src/app/mainview/components/InformativeTipBox.qml</file>
|
||||||
|
<file>src/app/commoncomponents/TimestampInfo.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -27,24 +27,36 @@ Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showTime: false
|
property bool showTime: false
|
||||||
property int seq: MsgSeq.single
|
property bool showDay: false
|
||||||
property alias font: textLabel.font
|
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
|
width: ListView.view ? ListView.view.width : 0
|
||||||
|
|
||||||
spacing: 2
|
spacing: 2
|
||||||
topPadding: 12
|
topPadding: 12
|
||||||
bottomPadding: 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 {
|
Rectangle {
|
||||||
id: msg
|
id: msg
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
width: childrenRect.width
|
width: childrenRect.width
|
||||||
height: JamiTheme.contactMessageAvatarSize + 12
|
height: JamiTheme.contactMessageAvatarSize + 12
|
||||||
radius: JamiTheme.contactMessageAvatarSize / 2 + 6
|
radius: JamiTheme.contactMessageAvatarSize / 2 + 6
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: CurrentConversation.isCoreDialog ? JamiTheme.messageInBgColor : CurrentConversation.color
|
border.color: CurrentConversation.isCoreDialog ? JamiTheme.messageInBgColor : CurrentConversation.color
|
||||||
|
@ -54,20 +66,18 @@ Column {
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
Layout.leftMargin: 6
|
Layout.leftMargin: 6
|
||||||
|
|
||||||
width: JamiTheme.contactMessageAvatarSize
|
width: JamiTheme.contactMessageAvatarSize
|
||||||
height: JamiTheme.contactMessageAvatarSize
|
height: JamiTheme.contactMessageAvatarSize
|
||||||
visible: ActionUri !== ""
|
visible: ActionUri !== ""
|
||||||
|
|
||||||
imageId: ActionUri !== CurrentAccount.uri ? ActionUri : CurrentAccount.id
|
imageId: ActionUri !== CurrentAccount.uri ? ActionUri : CurrentAccount.id
|
||||||
showPresenceIndicator: false
|
showPresenceIndicator: false
|
||||||
mode: ActionUri !== CurrentAccount.uri ? Avatar.Mode.Contact : Avatar.Mode.Account
|
mode: ActionUri !== CurrentAccount.uri ? Avatar.Mode.Contact : Avatar.Mode.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.rightMargin: 6
|
|
||||||
|
|
||||||
id: textLabel
|
id: textLabel
|
||||||
|
|
||||||
|
Layout.rightMargin: 6
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: Body
|
text: Body
|
||||||
horizontalAlignment: Qt.AlignHCenter
|
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
|
opacity: 0
|
||||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||||
Component.onCompleted: opacity = 1
|
Component.onCompleted: opacity = 1
|
||||||
|
|
|
@ -31,7 +31,12 @@ Loader {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var mediaInfo
|
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 int seq: MsgSeq.single
|
||||||
property string author: Author
|
property string author: Author
|
||||||
|
|
||||||
|
@ -70,7 +75,9 @@ Loader {
|
||||||
transferName: TransferName
|
transferName: TransferName
|
||||||
transferId: Id
|
transferId: Id
|
||||||
readers: Readers
|
readers: Readers
|
||||||
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
timestamp: root.timestamp
|
||||||
|
formattedTime: root.formattedTime
|
||||||
|
formattedDay: root.formattedTime
|
||||||
extraHeight: progressBar.visible ? 18 : 0
|
extraHeight: progressBar.visible ? 18 : 0
|
||||||
innerContent.children: [
|
innerContent.children: [
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -247,6 +254,7 @@ Loader {
|
||||||
transferId: Id
|
transferId: Id
|
||||||
readers: Readers
|
readers: Readers
|
||||||
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
||||||
|
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
|
||||||
bubble.visible: false
|
bubble.visible: false
|
||||||
innerContent.children: [
|
innerContent.children: [
|
||||||
Loader {
|
Loader {
|
||||||
|
@ -332,7 +340,7 @@ Loader {
|
||||||
sourceSize.width: width
|
sourceSize.width: width
|
||||||
sourceSize.height: height
|
sourceSize.height: height
|
||||||
source: "file:///" + Body
|
source: "file:///" + Body
|
||||||
property real aspectRatio: implicitWidth / implicitHeight
|
property real aspectRatio: width / implicitHeight
|
||||||
property real adjustedWidth: Math.min(maxSize,
|
property real adjustedWidth: Math.min(maxSize,
|
||||||
Math.max(minSize,
|
Math.max(minSize,
|
||||||
innerContent.width - senderMargin))
|
innerContent.width - senderMargin))
|
||||||
|
|
|
@ -29,39 +29,38 @@ Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool showTime: false
|
property bool showTime: false
|
||||||
|
property bool showDay: false
|
||||||
property int seq: MsgSeq.single
|
property int seq: MsgSeq.single
|
||||||
property alias font: textLabel.font
|
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
|
width: ListView.view ? ListView.view.width : 0
|
||||||
|
|
||||||
spacing: 2
|
spacing: 2
|
||||||
topPadding: 12
|
topPadding: 12
|
||||||
bottomPadding: 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 {
|
Label {
|
||||||
id: textLabel
|
id: textLabel
|
||||||
width: parent.width
|
|
||||||
text: Body
|
text: Body
|
||||||
horizontalAlignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
color: JamiTheme.chatviewTextColor
|
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
|
opacity: 0
|
||||||
|
|
|
@ -37,26 +37,26 @@ Control {
|
||||||
|
|
||||||
// these MUST be set but we won't use the 'required' keyword yet
|
// these MUST be set but we won't use the 'required' keyword yet
|
||||||
property bool isOutgoing
|
property bool isOutgoing
|
||||||
property bool showTime
|
property bool showTime: false
|
||||||
|
property bool showDay: false
|
||||||
property int seq
|
property int seq
|
||||||
property string author
|
property string author
|
||||||
property string transferId
|
property string transferId
|
||||||
property string registeredNameText
|
property string registeredNameText
|
||||||
property string transferName
|
property string transferName
|
||||||
property string formattedTime
|
property string formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
||||||
|
property string formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
|
||||||
property string location
|
property string location
|
||||||
property string id: Id
|
property string id: Id
|
||||||
property string hoveredLink
|
property string hoveredLink
|
||||||
property var readers: []
|
property var readers: []
|
||||||
|
property int timestamp: Timestamp
|
||||||
readonly property real senderMargin: 64
|
readonly property real senderMargin: 64
|
||||||
readonly property real avatarSize: 20
|
readonly property real avatarSize: 20
|
||||||
readonly property real msgRadius: 20
|
readonly property real msgRadius: 20
|
||||||
readonly property real hPadding: JamiTheme.sbsMessageBasePreferredPadding
|
readonly property real hPadding: JamiTheme.sbsMessageBasePreferredPadding
|
||||||
|
|
||||||
width: ListView.view ? ListView.view.width : 0
|
width: ListView.view ? ListView.view.width : 0
|
||||||
height: mainColumnLayout.implicitHeight
|
height: mainColumnLayout.implicitHeight
|
||||||
|
|
||||||
rightPadding: hPadding
|
rightPadding: hPadding
|
||||||
leftPadding: hPadding
|
leftPadding: hPadding
|
||||||
|
|
||||||
|
@ -64,11 +64,21 @@ Control {
|
||||||
id: mainColumnLayout
|
id: mainColumnLayout
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
width: parent.width - hPadding * 2
|
width: parent.width - hPadding * 2
|
||||||
|
|
||||||
spacing: 0
|
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 {
|
Item {
|
||||||
|
|
||||||
id: usernameblock
|
id: usernameblock
|
||||||
|
@ -78,7 +88,7 @@ Control {
|
||||||
id: username
|
id: username
|
||||||
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author)
|
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author)
|
||||||
font.bold: true
|
font.bold: true
|
||||||
visible: (seq === MsgSeq.first || seq === MsgSeq.single) && !isOutgoing
|
visible:(seq === MsgSeq.first || seq === MsgSeq.single) && !isOutgoing
|
||||||
font.pixelSize: JamiTheme.usernameBlockFontSize
|
font.pixelSize: JamiTheme.usernameBlockFontSize
|
||||||
color: JamiTheme.chatviewUsernameColor
|
color: JamiTheme.chatviewUsernameColor
|
||||||
lineHeight: JamiTheme.usernameBlockLineHeight
|
lineHeight: JamiTheme.usernameBlockLineHeight
|
||||||
|
@ -107,13 +117,11 @@ Control {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: itemMouseArea
|
id: itemMouseArea
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
onClicked: function (mouse) {
|
onClicked: function (mouse) {
|
||||||
if (mouse.button === Qt.RightButton
|
if (mouse.button === Qt.RightButton
|
||||||
|
@ -194,6 +202,7 @@ Control {
|
||||||
id: readsOne
|
id: readsOne
|
||||||
|
|
||||||
visible: root.readers.length === 1 && CurrentAccount.sendReadReceipt
|
visible: root.readers.length === 1 && CurrentAccount.sendReadReceipt
|
||||||
|
|
||||||
width: {
|
width: {
|
||||||
if (root.readers.length === 0)
|
if (root.readers.length === 0)
|
||||||
return 0
|
return 0
|
||||||
|
@ -216,28 +225,12 @@ Control {
|
||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
Layout.preferredHeight: {
|
Layout.preferredHeight: {
|
||||||
if (showTime || seq === MsgSeq.last)
|
if (showTime || seq === MsgSeq.last)
|
||||||
return contentHeight + formattedTimeLabel.contentHeight
|
return contentHeight + timestampItem.contentHeight
|
||||||
else if (readsMultiple.visible)
|
else if (readsMultiple.visible)
|
||||||
return JamiTheme.avatarReadReceiptSize
|
return JamiTheme.avatarReadReceiptSize
|
||||||
return 0
|
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 {
|
ReadStatus {
|
||||||
id: readsMultiple
|
id: readsMultiple
|
||||||
visible: root.readers.length > 1 && CurrentAccount.sendReadReceipt
|
visible: root.readers.length > 1 && CurrentAccount.sendReadReceipt
|
||||||
|
@ -254,7 +247,6 @@ Control {
|
||||||
anchors.topMargin: 1
|
anchors.topMargin: 1
|
||||||
readers: root.readers
|
readers: root.readers
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Qt5Compat.GraphicalEffects
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
import net.jami.Models 1.1
|
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
|
||||||
|
@ -31,25 +30,21 @@ SBSMessageBase {
|
||||||
id : root
|
id : root
|
||||||
|
|
||||||
property bool isRemoteImage
|
property bool isRemoteImage
|
||||||
|
|
||||||
property real maxMsgWidth: root.width - senderMargin - 2 * hPadding - avatarBlockWidth
|
property real maxMsgWidth: root.width - senderMargin - 2 * hPadding - avatarBlockWidth
|
||||||
|
|
||||||
isOutgoing: Author === ""
|
isOutgoing: Author === ""
|
||||||
author: Author
|
author: Author
|
||||||
readers: Readers
|
readers: Readers
|
||||||
|
timestamp: Timestamp
|
||||||
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
||||||
|
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
|
||||||
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
|
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
|
||||||
|
|
||||||
innerContent.children: [
|
innerContent.children: [
|
||||||
TextEdit {
|
TextEdit {
|
||||||
|
|
||||||
padding: JamiTheme.preferredMarginSize
|
padding: JamiTheme.preferredMarginSize
|
||||||
anchors.right: isOutgoing ? parent.right : undefined
|
anchors.right: isOutgoing ? parent.right : undefined
|
||||||
|
|
||||||
text: Body
|
text: Body
|
||||||
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
|
||||||
width: {
|
width: {
|
||||||
if (extraContent.active)
|
if (extraContent.active)
|
||||||
Math.max(extraContent.width,
|
Math.max(extraContent.width,
|
||||||
|
@ -62,9 +57,7 @@ SBSMessageBase {
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
|
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
|
|
||||||
font.pixelSize: JamiTheme.chatviewFontSize
|
font.pixelSize: JamiTheme.chatviewFontSize
|
||||||
|
|
||||||
font.hintingPreference: Font.PreferNoHinting
|
font.hintingPreference: Font.PreferNoHinting
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
textFormat: Text.MarkdownText
|
textFormat: Text.MarkdownText
|
||||||
|
|
106
src/app/commoncomponents/TimestampInfo.qml
Normal file
106
src/app/commoncomponents/TimestampInfo.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -321,6 +321,16 @@ Item {
|
||||||
property real lineEditContextMenuItemsWidth: 100
|
property real lineEditContextMenuItemsWidth: 100
|
||||||
property real lineEditContextMenuSeparatorsHeight: 2
|
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
|
// Jami switch
|
||||||
property real switchIndicatorRadius: 30
|
property real switchIndicatorRadius: 30
|
||||||
|
|
|
@ -44,105 +44,95 @@ JamiListView {
|
||||||
MessagesAdapter.loadMoreMessages()
|
MessagesAdapter.loadMoreMessages()
|
||||||
}
|
}
|
||||||
|
|
||||||
// sequencing/timestamps (2-sided style)
|
|
||||||
function computeTimestampVisibility(item, itemIndex) {
|
function computeTimestampVisibility(item1, item1Index, item2, item2Index) {
|
||||||
if (root === undefined)
|
if (item1 && item2) {
|
||||||
return
|
if (item1Index < item2Index) {
|
||||||
var nItem = root.itemAtIndex(itemIndex - 1)
|
item1.showTime = item1.timestamp - item2.timestamp > JamiTheme.timestampIntervalTime
|
||||||
if (nItem && itemIndex !== root.count - 1) {
|
item1.showDay = item1.formattedDay !== item2.formattedDay
|
||||||
item.showTime = (nItem.timestamp - item.timestamp) > 60 &&
|
}else {
|
||||||
nItem.formattedTime !== item.formattedTime
|
item2.showTime = item2.timestamp - item1.timestamp > JamiTheme.timestampIntervalTime
|
||||||
} else {
|
item2.showDay = item2.formattedDay !== item1.formattedDay
|
||||||
item.showTime = true
|
|
||||||
var pItem = root.itemAtIndex(itemIndex + 1)
|
|
||||||
if (pItem) {
|
|
||||||
pItem.showTime = (item.timestamp - pItem.timestamp) > 60 &&
|
|
||||||
pItem.formattedTime !== item.formattedTime
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeSequencing(computeItem, computeItemIndex) {
|
function computeChatview(item,itemIndex) {
|
||||||
if (root === undefined)
|
if (!root ) return
|
||||||
return
|
var rootItem = root.itemAtIndex(0)
|
||||||
var cItem = {
|
var pItem = root.itemAtIndex(itemIndex - 1)
|
||||||
'author': computeItem.author,
|
var pItemIndex = itemIndex - 1
|
||||||
'showTime': computeItem.showTime
|
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)
|
//top buffer insertion = scroll up
|
||||||
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 (pItem && !nItem) {
|
||||||
if (!isSeq(pItem, cItem)) {
|
computeTimestampVisibility(item, itemIndex, pItem, pItemIndex)
|
||||||
computeItem.seq = MsgSeq.single
|
computeSequencing(pItemIndex, pItem, item, root.itemAtIndex(itemIndex - 2))
|
||||||
} else {
|
|
||||||
computeItem.seq = MsgSeq.last
|
|
||||||
rAdjustSeq(pItem)
|
|
||||||
}
|
}
|
||||||
} else if (nItem && !pItem) {
|
//bottom buffer insertion = scroll down
|
||||||
if (!isSeq(cItem, nItem)) {
|
if (!pItem && nItem) {
|
||||||
computeItem.seq = MsgSeq.single
|
computeTimestampVisibility(item, itemIndex, nItem, nItemIndex)
|
||||||
} else {
|
computeSequencing(nItemIndex, nItem, root.itemAtIndex(itemIndex + 2), item)
|
||||||
setSeq(MsgSeq.first)
|
|
||||||
adjustSeq(nItem)
|
|
||||||
}
|
}
|
||||||
} else if (!nItem && !pItem) {
|
//index 0 insertion = new message
|
||||||
computeItem.seq = MsgSeq.single
|
if (itemIndex === 0) {
|
||||||
} else {
|
Qt.callLater(computeSequencing, itemIndex, item, root.itemAtIndex(itemIndex + 1), null)
|
||||||
if (isSeq(pItem, nItem)) {
|
if (! computeTimestampVisibility(item, itemIndex, nItem, nItemIndex)) {
|
||||||
if (isSeq(pItem, cItem)) {
|
Qt.callLater(computeChatview, item, itemIndex)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
//top element
|
||||||
|
if(itemIndex === root.count - 1 && CurrentConversation.allMessagesLoaded) {
|
||||||
|
item.showTime = true
|
||||||
|
item.showDay = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (computeItem.seq === MsgSeq.last) {
|
function computeSequencing(index, item, nItem, pItem) {
|
||||||
computeItem.showTime = true
|
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
|
// fade-in mechanism
|
||||||
|
@ -189,6 +179,7 @@ JamiListView {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
// this offscreen caching is pretty huge
|
// this offscreen caching is pretty huge
|
||||||
// displayMarginEnd may be removed
|
// displayMarginEnd may be removed
|
||||||
|
|
||||||
displayMarginBeginning: 2048
|
displayMarginBeginning: 2048
|
||||||
displayMarginEnd: 2048
|
displayMarginEnd: 2048
|
||||||
maximumFlickVelocity: 2048
|
maximumFlickVelocity: 2048
|
||||||
|
@ -216,65 +207,55 @@ JamiListView {
|
||||||
id: delegateChooser
|
id: delegateChooser
|
||||||
|
|
||||||
role: "Type"
|
role: "Type"
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
|
id: delegateChoice
|
||||||
|
|
||||||
roleValue: Interaction.Type.TEXT
|
roleValue: Interaction.Type.TEXT
|
||||||
|
|
||||||
TextMessageDelegate {
|
TextMessageDelegate {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (index) {
|
computeChatview(this,index)
|
||||||
computeTimestampVisibility(this, index)
|
|
||||||
computeSequencing(this, index)
|
|
||||||
} else {
|
|
||||||
Qt.callLater(computeTimestampVisibility, this, index)
|
|
||||||
Qt.callLater(computeSequencing, this, index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: Interaction.Type.CALL
|
roleValue: Interaction.Type.CALL
|
||||||
|
|
||||||
GeneratedMessageDelegate {
|
GeneratedMessageDelegate {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (index)
|
computeChatview(this,index)
|
||||||
computeTimestampVisibility(this, index)
|
|
||||||
else
|
|
||||||
Qt.callLater(computeTimestampVisibility, this, index)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: Interaction.Type.CONTACT
|
roleValue: Interaction.Type.CONTACT
|
||||||
|
|
||||||
ContactMessageDelegate {
|
ContactMessageDelegate {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (index)
|
computeChatview(this,index)
|
||||||
computeTimestampVisibility(this, index)
|
|
||||||
else
|
|
||||||
Qt.callLater(computeTimestampVisibility, this, index)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: Interaction.Type.INITIAL
|
roleValue: Interaction.Type.INITIAL
|
||||||
|
|
||||||
GeneratedMessageDelegate {
|
GeneratedMessageDelegate {
|
||||||
font.bold: true
|
font.bold: true
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (index)
|
computeChatview(this,index)
|
||||||
computeTimestampVisibility(this, index)
|
|
||||||
else
|
|
||||||
Qt.callLater(computeTimestampVisibility, this, index)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: Interaction.Type.DATA_TRANSFER
|
roleValue: Interaction.Type.DATA_TRANSFER
|
||||||
|
|
||||||
DataTransferMessageDelegate {
|
DataTransferMessageDelegate {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (index) {
|
computeChatview(this,index)
|
||||||
computeTimestampVisibility(this, index)
|
|
||||||
computeSequencing(this, index)
|
|
||||||
} else {
|
|
||||||
Qt.callLater(computeTimestampVisibility, this, index)
|
|
||||||
Qt.callLater(computeSequencing, this, index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -581,21 +581,41 @@ MessagesAdapter::getFormattedTime(const quint64 timestamp)
|
||||||
{
|
{
|
||||||
const auto now = QDateTime::currentDateTime();
|
const auto now = QDateTime::currentDateTime();
|
||||||
const auto seconds = now.toSecsSinceEpoch() - timestamp;
|
const auto seconds = now.toSecsSinceEpoch() - timestamp;
|
||||||
auto interval = qFloor(seconds / (3600 * 24));
|
auto interval = qFloor(seconds / 60);
|
||||||
if (interval > 5)
|
|
||||||
return QLocale::system().toString(QDateTime::fromSecsSinceEpoch(timestamp),
|
if (interval > 1) {
|
||||||
QLocale::ShortFormat);
|
auto curLang = settingsManager_->getValue(Settings::Key::LANG);
|
||||||
if (interval > 1)
|
auto curLocal(QLocale(curLang.toString()));
|
||||||
return QObject::tr("%1 days ago").arg(interval);
|
auto curTime = QDateTime::fromSecsSinceEpoch(timestamp).time();
|
||||||
if (interval == 1)
|
QString timeLocale;
|
||||||
return QObject::tr("one day ago");
|
if (curLang == "SYSTEM")
|
||||||
interval = qFloor(seconds / 3600);
|
timeLocale = QLocale::system().toString(curTime, QLocale::system().ShortFormat);
|
||||||
if (interval > 1)
|
else
|
||||||
return QObject::tr("%1 hours ago").arg(interval);
|
timeLocale = curLocal.toString(curTime, curLocal.ShortFormat);
|
||||||
if (interval == 1)
|
|
||||||
return QObject::tr("one hour ago");
|
return timeLocale;
|
||||||
interval = qFloor(seconds / 60);
|
}
|
||||||
if (interval > 1)
|
|
||||||
return QObject::tr("%1 minutes ago").arg(interval);
|
|
||||||
return QObject::tr("just now");
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ protected:
|
||||||
Q_INVOKABLE QVariantMap isLocalImage(const QString& mimeName);
|
Q_INVOKABLE QVariantMap isLocalImage(const QString& mimeName);
|
||||||
Q_INVOKABLE QVariantMap getMediaInfo(const QString& msg);
|
Q_INVOKABLE QVariantMap getMediaInfo(const QString& msg);
|
||||||
Q_INVOKABLE bool isRemoteImage(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 QString getFormattedTime(const quint64 timestamp);
|
||||||
Q_INVOKABLE void parseMessageUrls(const QString& messageId,
|
Q_INVOKABLE void parseMessageUrls(const QString& messageId,
|
||||||
const QString& msg,
|
const QString& msg,
|
||||||
|
|
|
@ -62,7 +62,6 @@ RowLayout {
|
||||||
Layout.preferredWidth: root.itemWidth
|
Layout.preferredWidth: root.itemWidth
|
||||||
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
|
||||||
font.pointSize: JamiTheme.buttonFontSize
|
font.pointSize: JamiTheme.buttonFontSize
|
||||||
font.kerning: true
|
font.kerning: true
|
||||||
|
|
||||||
|
@ -91,7 +90,6 @@ RowLayout {
|
||||||
|
|
||||||
MaterialToolTip {
|
MaterialToolTip {
|
||||||
id: toolTip
|
id: toolTip
|
||||||
|
|
||||||
parent: textField
|
parent: textField
|
||||||
visible: textField.hovered && (root.tooltipText.length > 0)
|
visible: textField.hovered && (root.tooltipText.length > 0)
|
||||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||||
|
|
|
@ -36,7 +36,11 @@ using reverseIterator = MessageListModel::reverseIterator;
|
||||||
|
|
||||||
MessageListModel::MessageListModel(QObject* parent)
|
MessageListModel::MessageListModel(QObject* parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{}
|
, timestampTimer_(new QTimer(this))
|
||||||
|
{
|
||||||
|
connect(timestampTimer_, &QTimer::timeout, this, &MessageListModel::timestampUpdate);
|
||||||
|
timestampTimer_->start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
QPair<iterator, bool>
|
QPair<iterator, bool>
|
||||||
MessageListModel::emplace(const QString& msgId, interaction::Info message, bool beginning)
|
MessageListModel::emplace(const QString& msgId, interaction::Info message, bool beginning)
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "api/interaction.h"
|
#include "api/interaction.h"
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
namespace lrc {
|
namespace lrc {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
@ -125,6 +126,8 @@ public:
|
||||||
void emitDataChanged(iterator it, VectorInt roles = {});
|
void emitDataChanged(iterator it, VectorInt roles = {});
|
||||||
void emitDataChanged(const QString& msgId, VectorInt roles = {});
|
void emitDataChanged(const QString& msgId, VectorInt roles = {});
|
||||||
|
|
||||||
|
Q_SIGNAL void timestampUpdate();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using Role = MessageList::Role;
|
using Role = MessageList::Role;
|
||||||
|
|
||||||
|
@ -144,6 +147,8 @@ private:
|
||||||
iterator insertMessage(iterator it, item_t& message);
|
iterator insertMessage(iterator it, item_t& message);
|
||||||
void removeMessage(int index, iterator it);
|
void removeMessage(int index, iterator it);
|
||||||
void moveMessage(int from, int to);
|
void moveMessage(int from, int to);
|
||||||
|
|
||||||
|
QTimer* timestampTimer_ {nullptr};
|
||||||
};
|
};
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace lrc
|
} // namespace lrc
|
||||||
|
|
Loading…
Add table
Reference in a new issue