diff --git a/qml.qrc b/qml.qrc
index 36590f64..1d6af2c7 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -163,6 +163,7 @@
src/app/mainview/components/FilesToSendDelegate.qml
src/app/mainview/components/MessageBar.qml
src/app/mainview/components/FilesToSendContainer.qml
+ src/app/mainview/components/ReplyingContainer.qml
src/app/commoncomponents/Avatar.qml
src/app/mainview/components/ConversationAvatar.qml
src/app/mainview/components/InvitationView.qml
@@ -178,6 +179,7 @@
src/app/constant/MsgSeq.qml
src/app/commoncomponents/SBSContextMenu.qml
src/app/commoncomponents/SBSMessageBase.qml
+ src/app/commoncomponents/ReplyToRow.qml
src/app/commoncomponents/ReadStatus.qml
src/app/commoncomponents/GeneratedMessageDelegate.qml
src/app/commoncomponents/DataTransferMessageDelegate.qml
diff --git a/src/app/commoncomponents/ReplyToRow.qml b/src/app/commoncomponents/ReplyToRow.qml
new file mode 100644
index 00000000..08c6c6b7
--- /dev/null
+++ b/src/app/commoncomponents/ReplyToRow.qml
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * 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 .
+ */
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+Item {
+ id: root
+ anchors.right: isOutgoing ? parent.right : undefined
+
+ visible: ReplyTo !== ""
+ width: visible ? replyToRow.width : 0
+ height: replyToRow.height + replyToRow.anchors.topMargin
+
+ MouseArea {
+
+ z: 2
+ anchors.fill: parent
+ RowLayout {
+ id: replyToRow
+ anchors.top: parent.top
+ anchors.topMargin: JamiTheme.preferredMarginSize / 2
+
+ property bool isSelf: ReplyToAuthor === CurrentAccount.uri || ReplyToAuthor === ""
+
+ onVisibleChanged: {
+ if (visible) {
+ // Make sure we show the original post
+ // In the future, we may just want to load the previous interaction of the thread
+ // and not show it, but for now we can simplify.
+ MessagesAdapter.loadConversationUntil(ReplyTo)
+ }
+ }
+
+ Label {
+ id: replyTo
+
+ text: JamiStrings.inReplyTo
+
+ color: UtilsAdapter.luma(bubble.color) ?
+ JamiTheme.chatviewTextColorLight :
+ JamiTheme.chatviewTextColorDark
+ font.pointSize: JamiTheme.textFontSize
+ font.kerning: true
+ font.bold: true
+ Layout.leftMargin: JamiTheme.preferredMarginSize
+ }
+
+ Avatar {
+ id: avatarReply
+
+ Layout.preferredWidth: JamiTheme.avatarReadReceiptSize
+ Layout.preferredHeight: JamiTheme.avatarReadReceiptSize
+
+ showPresenceIndicator: false
+
+ imageId: {
+ if (replyToRow.isSelf)
+ return CurrentAccount.id
+ return ReplyToAuthor
+ }
+ mode: replyToRow.isSelf ? Avatar.Mode.Account : Avatar.Mode.Contact
+ }
+
+ Text {
+ id: body
+ Layout.maximumWidth: JamiTheme.preferredFieldWidth - JamiTheme.preferredMarginSize
+ Layout.rightMargin: JamiTheme.preferredMarginSize
+
+ text: ReplyToBody
+ elide: Text.ElideRight
+
+ color: UtilsAdapter.luma(bubble.color) ?
+ JamiTheme.chatviewTextColorLight :
+ JamiTheme.chatviewTextColorDark
+ font.pointSize: JamiTheme.textFontSize
+ font.kerning: true
+ font.bold: true
+ }
+ }
+
+ onClicked: function(mouse) {
+ CurrentConversation.scrollToMsg(ReplyTo)
+ }
+ }
+}
diff --git a/src/app/commoncomponents/SBSContextMenu.qml b/src/app/commoncomponents/SBSContextMenu.qml
index 4af744cd..8c6f0f09 100644
--- a/src/app/commoncomponents/SBSContextMenu.qml
+++ b/src/app/commoncomponents/SBSContextMenu.qml
@@ -29,6 +29,7 @@ ContextMenuAutoLoader {
id: root
property string location
+ property string msgId
property string transferName
property string transferId
@@ -36,6 +37,7 @@ ContextMenuAutoLoader {
GeneralMenuItem {
id: saveFile
+ canTrigger: root.transferId !== ""
itemName: JamiStrings.saveFile
onClicked: {
MessagesAdapter.copyToDownloads(root.transferId, root.transferName)
@@ -44,10 +46,19 @@ ContextMenuAutoLoader {
GeneralMenuItem {
id: openLocation
+ canTrigger: root.transferId !== ""
itemName: JamiStrings.openLocation
onClicked: {
MessagesAdapter.openDirectory(root.location)
}
+ },
+ GeneralMenuItem {
+ id: reply
+
+ itemName: JamiStrings.reply
+ onClicked: {
+ MessagesAdapter.replyToId = root.msgId
+ }
}
]
diff --git a/src/app/commoncomponents/SBSMessageBase.qml b/src/app/commoncomponents/SBSMessageBase.qml
index 825c663f..b4b07543 100644
--- a/src/app/commoncomponents/SBSMessageBase.qml
+++ b/src/app/commoncomponents/SBSMessageBase.qml
@@ -32,6 +32,7 @@ Control {
property alias avatarBlockWidth: avatarBlock.width
property alias innerContent: innerContent
property alias bubble: bubble
+ property alias selectAnimation: selectAnimation
property real extraHeight: 0
// these MUST be set but we won't use the 'required' keyword yet
@@ -44,6 +45,7 @@ Control {
property string transferName
property string formattedTime
property string location
+ property string id: Id
property string hoveredLink
property var readers: []
@@ -89,7 +91,6 @@ Control {
Layout.preferredHeight: innerContent.height + root.extraHeight
Layout.topMargin: (seq === MsgSeq.first || seq === MsgSeq.single) ? 6 : 0
-
Item {
id: avatarBlock
Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hPadding/3
@@ -100,34 +101,74 @@ Control {
anchors.bottom: parent.bottom
width: avatarSize
height: avatarSize
- imageId: author
+ imageId: root.author
showPresenceIndicator: false
mode: Avatar.Mode.Contact
}
}
- Item {
+
+
+ MouseArea {
+ id: itemMouseArea
+
Layout.fillWidth: true
Layout.fillHeight: true
+
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: function (mouse) {
+ if (mouse.button === Qt.RightButton
+ && (transferId !== "" || Type === Interaction.Type.TEXT)) {
+ // Context Menu for Transfers
+ ctxMenu.x = mouse.x
+ ctxMenu.y = mouse.y
+ ctxMenu.openMenu()
+ } else if (root.hoveredLink) {
+ MessagesAdapter.openUrl(root.hoveredLink)
+ }
+ }
+
Column {
id: innerContent
width: parent.width
// place actual content here
+ ReplyToRow {}
}
+
MessageBubble {
id: bubble
z:-1
out: isOutgoing
type: seq
- color: isOutgoing ?
- JamiTheme.messageOutBgColor :
- CurrentConversation.isCoreDialog ? JamiTheme.messageInBgColor : Qt.lighter(CurrentConversation.color, 1.5)
+ function getBaseColor() {
+ var baseColor = isOutgoing ? JamiTheme.messageOutBgColor
+ : CurrentConversation.isCoreDialog ?
+ JamiTheme.messageInBgColor : Qt.lighter(CurrentConversation.color, 1.5)
+ if (Id === MessagesAdapter.replyToId) {
+ // If we are replying to
+ return Qt.darker(baseColor, 1.5)
+ }
+ return baseColor
+ }
+ color: getBaseColor()
radius: msgRadius
anchors.right: isOutgoing ? parent.right : undefined
anchors.top: parent.top
width: innerContent.childrenRect.width
height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0)
}
+
+ SequentialAnimation {
+ id: selectAnimation
+ ColorAnimation {
+ target: bubble; property: "color"
+ to: Qt.darker(bubble.getBaseColor(), 1.5); duration: 240
+ }
+ ColorAnimation {
+ target: bubble; property: "color"
+ to: bubble.getBaseColor(); duration: 240
+ }
+ }
}
Item {
@@ -220,25 +261,9 @@ Control {
SBSContextMenu {
id: ctxMenu
+ msgId: Id
location: root.location
transferId: root.transferId
transferName: root.transferName
}
-
- MouseArea {
- id: itemMouseArea
- anchors.fill: parent
- z: -1
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- onClicked: function (mouse) {
-
- if (mouse.button === Qt.RightButton && transferId !== "") {
- // Context Menu for Transfers
- ctxMenu.x = mouse.x
- ctxMenu.y = mouse.y
- ctxMenu.openMenu()
- } else if (root.hoveredLink)
- MessagesAdapter.openUrl(root.hoveredLink)
- }
- }
}
diff --git a/src/app/commoncomponents/TextMessageDelegate.qml b/src/app/commoncomponents/TextMessageDelegate.qml
index ab98565d..35b95d2f 100644
--- a/src/app/commoncomponents/TextMessageDelegate.qml
+++ b/src/app/commoncomponents/TextMessageDelegate.qml
@@ -43,7 +43,7 @@ SBSMessageBase {
innerContent.children: [
TextEdit {
- padding: JamiTheme.chatviewPadding
+ padding: JamiTheme.preferredMarginSize
anchors.right: isOutgoing ? parent.right : undefined
text: Body
@@ -76,6 +76,7 @@ SBSMessageBase {
JamiTheme.chatviewTextColorDark
TapHandler {
+ enabled: parent.selectedText.length > 0
acceptedButtons: Qt.RightButton
onTapped: function onTapped(eventPoint) {
ctxMenu.openMenuAt(eventPoint.position)
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 8690a1e2..32af891a 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -719,6 +719,9 @@ Item {
property string leaveVideoMessage: qsTr("Leave video message")
property string send: qsTr("Send")
property string remove: qsTr("Remove")
+ property string replyTo: qsTr("Reply to")
+ property string inReplyTo: qsTr("In reply to")
+ property string reply: qsTr("Reply")
property string writeTo: qsTr("Write to %1")
// Invitation View
diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 3b745e02..5a1cb4a7 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -334,7 +334,6 @@ Item {
// MessageWebView
property real chatViewHairLineSize: 1
- property real chatviewPadding : 16
property real chatViewMaximumWidth: 900
property real chatViewHeaderPreferredHeight: 64
property real chatViewHeaderMinimumWidth: 200
diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp
index 0038bc9c..42c9a9fc 100644
--- a/src/app/currentconversation.cpp
+++ b/src/app/currentconversation.cpp
@@ -167,7 +167,7 @@ CurrentConversation::updateErrors(const QString& convId)
auto& convInfo = optConv->get();
QStringList newErrors;
QStringList newBackendErr;
- for (const auto& [code, error]: convInfo.errors) {
+ for (const auto& [code, error] : convInfo.errors) {
if (code == 1) {
newErrors.append(tr("An error occurred while fetching this repository"));
} else if (code == 2) {
@@ -175,7 +175,8 @@ CurrentConversation::updateErrors(const QString& convId)
} else if (code == 3) {
newErrors.append(tr("An invalid message was detected"));
} else if (code == 4) {
- newErrors.append(tr("Not enough authorization for updating conversation's infos"));
+ newErrors.append(
+ tr("Not enough authorization for updating conversation's infos"));
} else {
continue;
}
@@ -185,6 +186,11 @@ CurrentConversation::updateErrors(const QString& convId)
set_errors(newErrors);
}
} catch (...) {
-
}
}
+
+void
+CurrentConversation::scrollToMsg(const QString& msg)
+{
+ Q_EMIT scrollTo(msg);
+}
diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h
index 399f19dd..7ccbf511 100644
--- a/src/app/currentconversation.h
+++ b/src/app/currentconversation.h
@@ -54,10 +54,11 @@ class CurrentConversation final : public QObject
public:
explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr);
~CurrentConversation() = default;
-
+ Q_INVOKABLE void scrollToMsg(const QString& msgId);
Q_INVOKABLE void showSwarmDetails() const;
Q_SIGNALS:
+ void scrollTo(const QString& msgId);
void showDetails() const;
private Q_SLOTS:
diff --git a/src/app/mainview/components/ChatViewFooter.qml b/src/app/mainview/components/ChatViewFooter.qml
index ae97243d..25d7a71c 100644
--- a/src/app/mainview/components/ChatViewFooter.qml
+++ b/src/app/mainview/components/ChatViewFooter.qml
@@ -121,6 +121,16 @@ Rectangle {
spacing: 0
+ ReplyingContainer {
+ id: replyingContainer
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: footerColumnLayout.width
+ Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
+ Layout.preferredHeight: 36
+ visible: MessagesAdapter.replyToId !== ""
+ }
+
MessageBar {
id: messageBar
diff --git a/src/app/mainview/components/MessageListView.qml b/src/app/mainview/components/MessageListView.qml
index eba5bfd5..656b9a95 100644
--- a/src/app/mainview/components/MessageListView.qml
+++ b/src/app/mainview/components/MessageListView.qml
@@ -165,6 +165,19 @@ JamiListView {
Connections {
target: CurrentConversation
function onIdChanged() { fadeAnimation.start() }
+ function onScrollTo(id) {
+ var idx = -1
+ for (var i = 1; i < root.count; i++) {
+ var delegate = root.itemAtIndex(i)
+ if (delegate && delegate.id === id) {
+ idx = i
+ }
+ }
+ positionViewAtIndex(idx, ListView.Center)
+ var delegate = root.itemAtIndex(idx)
+ if (delegate.selectAnimation)
+ delegate.selectAnimation.start()
+ }
}
topMargin: 12
diff --git a/src/app/mainview/components/OngoingCallPage.qml b/src/app/mainview/components/OngoingCallPage.qml
index 72cc19c0..affad4e6 100644
--- a/src/app/mainview/components/OngoingCallPage.qml
+++ b/src/app/mainview/components/OngoingCallPage.qml
@@ -333,7 +333,7 @@ Rectangle {
target: MessagesAdapter
enabled: root.visible
- function onNewInteraction(interactionType) {
+ function onNewInteraction(id, interactionType) {
// Ignore call notifications, as we are in the call.
if (interactionType !== Interaction.Type.CALL && !inCallMessageWebViewStack.visible)
openInCallConversation()
diff --git a/src/app/mainview/components/ReplyingContainer.qml b/src/app/mainview/components/ReplyingContainer.qml
new file mode 100644
index 00000000..0489888b
--- /dev/null
+++ b/src/app/mainview/components/ReplyingContainer.qml
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Sébastien Blin
+ *
+ * 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 .
+ */
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+import net.jami.Models 1.1
+
+import "../../commoncomponents"
+
+Rectangle {
+ id: root
+
+ color: JamiTheme.messageOutBgColor
+
+ property var isSelf: false
+ property var author: {
+ if (MessagesAdapter.replyToId === "")
+ return ""
+
+ var author = MessagesAdapter.dataForInteraction(MessagesAdapter.replyToId, MessageList.Author)
+ isSelf = author === "" || author === undefined
+ if (isSelf) {
+ avatar.mode = Avatar.Mode.Account
+ avatar.imageId = CurrentAccount.id
+ } else {
+ avatar.mode = Avatar.Mode.Contact
+ avatar.imageId = author
+ }
+ return isSelf ? CurrentAccount.uri : author
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 12
+
+ RowLayout {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+
+ Label {
+ id: replyTo
+
+ text: JamiStrings.replyTo
+
+ color: UtilsAdapter.luma(root.color) ?
+ JamiTheme.chatviewTextColorLight :
+ JamiTheme.chatviewTextColorDark
+ font.pointSize: JamiTheme.textFontSize
+ font.kerning: true
+ font.bold: true
+ Layout.leftMargin: JamiTheme.preferredMarginSize
+ }
+
+ Avatar {
+ id: avatar
+
+ Layout.preferredWidth: JamiTheme.avatarReadReceiptSize
+ Layout.preferredHeight: JamiTheme.avatarReadReceiptSize
+
+ showPresenceIndicator: false
+
+ imageId: ""
+ mode: Avatar.Mode.Account
+ }
+
+ Label {
+ id: username
+
+ text: author === CurrentAccount.uri ?
+ CurrentAccount.bestName
+ : UtilsAdapter.getBestNameForUri(CurrentAccount.id, author)
+
+ color: UtilsAdapter.luma(root.color) ?
+ JamiTheme.chatviewTextColorLight :
+ JamiTheme.chatviewTextColorDark
+ font.pointSize: JamiTheme.textFontSize
+ font.kerning: true
+ font.bold: true
+ }
+ }
+
+
+ PushButton {
+ id: closeReply
+
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
+ Layout.rightMargin: JamiTheme.preferredMarginSize
+
+ preferredSize: 24
+
+ source: JamiResources.round_close_24dp_svg
+
+ normalColor: JamiTheme.chatviewBgColor
+ imageColor: JamiTheme.chatviewButtonColor
+
+ onClicked: MessagesAdapter.replyToId = ""
+ }
+ }
+}
diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp
index 5ea91cdd..c715b91c 100644
--- a/src/app/messagesadapter.cpp
+++ b/src/app/messagesadapter.cpp
@@ -52,6 +52,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
, filteredMsgListModel_(new FilteredMsgListModel(this))
{
connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() {
+ set_replyToId("");
const QString& convId = lrcInstance_->get_selectedConvUid();
const auto& conversation = lrcInstance_->getConversationFromConvUid(convId);
filteredMsgListModel_->setSourceModel(conversation.interactions.get());
@@ -100,6 +101,23 @@ MessagesAdapter::loadMoreMessages()
}
}
+void
+MessagesAdapter::loadConversationUntil(const QString& to)
+{
+ if (auto* model = static_cast(filteredMsgListModel_->sourceModel())) {
+ auto idx = model->indexOfMessage(to);
+ if (idx == -1) {
+ auto accountId = lrcInstance_->get_currentAccountId();
+ auto convId = lrcInstance_->get_selectedConvUid();
+ const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
+ if (convInfo.isSwarm()) {
+ auto* convModel = lrcInstance_->getCurrentConversationModel();
+ convModel->loadConversationUntil(convId, to);
+ }
+ }
+ }
+}
+
void
MessagesAdapter::connectConversationModel()
{
@@ -135,7 +153,8 @@ MessagesAdapter::sendMessage(const QString& message)
{
try {
const auto convUid = lrcInstance_->get_selectedConvUid();
- lrcInstance_->getCurrentConversationModel()->sendMessage(convUid, message);
+ lrcInstance_->getCurrentConversationModel()->sendMessage(convUid, message, replyToId_);
+ set_replyToId("");
} catch (...) {
qDebug() << "Exception during sendMessage:" << message;
}
@@ -302,6 +321,26 @@ MessagesAdapter::getTransferStats(const QString& msgId, int status)
return {{"totalSize", qint64(info.totalSize)}, {"progress", qint64(info.progress)}};
}
+QVariant
+MessagesAdapter::dataForInteraction(const QString& interactionId, int role) const
+{
+ if (auto* model = static_cast(filteredMsgListModel_->sourceModel())) {
+ auto idx = model->indexOfMessage(interactionId);
+ if (idx != -1)
+ return model->data(idx, role);
+ }
+ return {};
+}
+
+int
+MessagesAdapter::getIndexOfMessage(const QString& interactionId) const
+{
+ if (auto* model = static_cast(filteredMsgListModel_->sourceModel())) {
+ return model->indexOfMessage(interactionId);
+ }
+ return {};
+}
+
void
MessagesAdapter::userIsComposing(bool isComposing)
{
@@ -327,7 +366,7 @@ MessagesAdapter::onNewInteraction(const QString& convUid,
auto& accountInfo = lrcInstance_->getAccountInfo(accountId);
auto& convModel = accountInfo.conversationModel;
convModel->clearUnreadInteractions(convUid);
- Q_EMIT newInteraction(static_cast(interaction.type));
+ Q_EMIT newInteraction(interactionId, static_cast(interaction.type));
} catch (...) {
}
}
@@ -486,8 +525,10 @@ MessagesAdapter::isLocalImage(const QString& mimename)
QImageReader reader;
QList supportedFormats = reader.supportedImageFormats();
auto iterator = std::find_if(supportedFormats.begin(),
- supportedFormats.end(),
- [fileFormat](QByteArray format) { return format == fileFormat; });
+ supportedFormats.end(),
+ [fileFormat](QByteArray format) {
+ return format == fileFormat;
+ });
return {{"isImage", iterator != supportedFormats.end()}};
}
return {{"isImage", false}};
diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h
index 2473fcbb..26afb48a 100644
--- a/src/app/messagesadapter.h
+++ b/src/app/messagesadapter.h
@@ -58,6 +58,7 @@ class MessagesAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_RO_PROPERTY(QVariant, messageListModel)
+ QML_PROPERTY(QString, replyToId)
QML_RO_PROPERTY(QList, currentConvComposingList)
public:
@@ -68,7 +69,7 @@ public:
~MessagesAdapter() = default;
Q_SIGNALS:
- void newInteraction(int type);
+ void newInteraction(const QString& id, int type);
void newMessageBarPlaceholderText(QString placeholderText);
void newFilePasted(QString filePath);
void newTextPasted();
@@ -80,6 +81,7 @@ protected:
Q_INVOKABLE void setupChatView(const QVariantMap& convInfo);
Q_INVOKABLE void loadMoreMessages();
+ Q_INVOKABLE void loadConversationUntil(const QString& to);
Q_INVOKABLE void connectConversationModel();
Q_INVOKABLE void sendConversationRequest();
Q_INVOKABLE void removeConversation(const QString& convUid);
@@ -109,8 +111,11 @@ protected:
const QString& msg,
bool showPreview);
Q_INVOKABLE void onPaste();
+ Q_INVOKABLE int getIndexOfMessage(const QString& messageId) const;
Q_INVOKABLE QString getStatusString(int status);
Q_INVOKABLE QVariantMap getTransferStats(const QString& messageId, int);
+ Q_INVOKABLE QVariant dataForInteraction(const QString& interactionId,
+ int role = Qt::DisplayRole) const;
// Run corrsponding js functions, c++ to qml.
void setMessagesImageContent(const QString& path, bool isBased64 = false);
diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h
index 7da8d5aa..f0307d96 100644
--- a/src/libclient/api/conversationmodel.h
+++ b/src/libclient/api/conversationmodel.h
@@ -313,6 +313,7 @@ public:
* @return id for loading request. -1 if not loaded
*/
int loadConversationMessages(const QString& conversationId, const int size = 1);
+ int loadConversationUntil(const QString& conversationId, const QString& to);
/**
* accept request for conversation
* @param conversationId conversation's id
diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp
index 278ff26e..9c7430ef 100644
--- a/src/libclient/conversationmodel.cpp
+++ b/src/libclient/conversationmodel.cpp
@@ -1630,6 +1630,25 @@ ConversationModel::loadConversationMessages(const QString& conversationId, const
size);
}
+int
+ConversationModel::loadConversationUntil(const QString& conversationId, const QString& to)
+{
+ auto conversationOpt = getConversationForUid(conversationId);
+ if (!conversationOpt.has_value()) {
+ return -1;
+ }
+ auto& conversation = conversationOpt->get();
+ if (conversation.allMessagesLoaded) {
+ return -1;
+ }
+ auto lastMsgId = conversation.interactions->empty() ? ""
+ : conversation.interactions->front().first;
+ return ConfigurationManager::instance().loadConversationUntil(owner.id,
+ conversationId,
+ lastMsgId,
+ to);
+}
+
void
ConversationModel::acceptConversationRequest(const QString& conversationId)
{
diff --git a/src/libclient/messagelistmodel.cpp b/src/libclient/messagelistmodel.cpp
index ac79134b..b81c3e61 100644
--- a/src/libclient/messagelistmodel.cpp
+++ b/src/libclient/messagelistmodel.cpp
@@ -171,6 +171,7 @@ MessageListModel::clear()
{
Q_EMIT beginResetModel();
interactions_.clear();
+ replyTo_.clear();
Q_EMIT endResetModel();
}
@@ -289,6 +290,18 @@ MessageListModel::insertMessage(int index, item_t& message)
Q_EMIT beginInsertRows(QModelIndex(), index, index);
interactions_.insert(index, message);
Q_EMIT endInsertRows();
+ auto replyId = message.second.commit["reply-to"];
+ auto commitId = message.second.commit["id"];
+ if (!replyId.isEmpty())
+ replyTo_[replyId].append(commitId);
+ for (const auto& msgId : replyTo_[commitId]) {
+ int index = getIndexOfMessage(msgId);
+ if (index == -1)
+ continue;
+ QModelIndex modelIndex = QAbstractListModel::index(index, 0);
+ Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ReplyToAuthor});
+ Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ReplyToBody});
+ }
}
iterator
@@ -343,6 +356,8 @@ MessageListModel::roleNames() const
QVariant
MessageListModel::dataForItem(item_t item, int, int role) const
{
+ auto replyId = item.second.commit["reply-to"];
+ auto repliedMsg = getIndexOfMessage(replyId);
switch (role) {
case Role::Id:
return QVariant(item.first);
@@ -368,6 +383,12 @@ MessageListModel::dataForItem(item_t item, int, int role) const
return QVariant(item.second.commit["uri"]);
case Role::ContactAction:
return QVariant(item.second.commit["action"]);
+ case Role::ReplyTo:
+ return QVariant(replyId);
+ case Role::ReplyToAuthor:
+ return repliedMsg == -1 ? QVariant("") : QVariant(data(repliedMsg, Role::Author));
+ case Role::ReplyToBody:
+ return repliedMsg == -1 ? QVariant("") : QVariant(data(repliedMsg, Role::Body));
case Role::TransferName:
return QVariant(item.second.commit["displayName"]);
case Role::Readers:
@@ -377,6 +398,16 @@ MessageListModel::dataForItem(item_t item, int, int role) const
}
}
+QVariant
+MessageListModel::data(int idx, int role) const
+{
+ QModelIndex index = QAbstractListModel::index(idx, 0);
+ if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
+ return {};
+ }
+ return dataForItem(interactions_.at(index.row()), index.row(), role);
+}
+
QVariant
MessageListModel::data(const QModelIndex& index, int role) const
{
diff --git a/src/libclient/messagelistmodel.h b/src/libclient/messagelistmodel.h
index 3d36e577..d4137bee 100644
--- a/src/libclient/messagelistmodel.h
+++ b/src/libclient/messagelistmodel.h
@@ -44,6 +44,9 @@ struct Info;
X(ActionUri) \
X(LinkPreviewInfo) \
X(Linkified) \
+ X(ReplyTo) \
+ X(ReplyToBody) \
+ X(ReplyToAuthor) \
X(TransferName) \
X(Readers)
@@ -89,7 +92,7 @@ public:
iterator begin();
constIterator begin() const;
reverseIterator rbegin();
- int size() const;
+ Q_INVOKABLE int size() const;
void clear();
bool empty() const;
interaction::Info at(const QString& intId) const;
@@ -102,7 +105,8 @@ public:
void moveMessages(QList msgIds, const QString& parentId);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+ Q_INVOKABLE virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+ Q_INVOKABLE virtual QVariant data(int idx, int role = Qt::DisplayRole) const;
QHash roleNames() const override;
QVariant dataForItem(item_t item, int indexRow, int role = Qt::DisplayRole) const;
bool contains(const QString& msgId);
@@ -132,6 +136,7 @@ private:
// to allow quick access.
QMap lastDisplayedMessageUid_;
QMap messageToReaders_;
+ QMap replyTo_;
void moveMessage(const QString& msgId, const QString& parentId);
void insertMessage(int index, item_t& message);
diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h
index ad287fc9..ae1093b2 100644
--- a/src/libclient/qtwrapper/configurationmanager_wrap.h
+++ b/src/libclient/qtwrapper/configurationmanager_wrap.h
@@ -1056,6 +1056,16 @@ public Q_SLOTS: // METHODS
fromId.toStdString(),
size);
}
+ uint32_t loadConversationUntil(const QString& accountId,
+ const QString& conversationId,
+ const QString& fromId,
+ const QString& toId)
+ {
+ return DRing::loadConversationUntil(accountId.toStdString(),
+ conversationId.toStdString(),
+ fromId.toStdString(),
+ toId.toStdString());
+ }
void setDefaultModerator(const QString& accountID, const QString& peerURI, const bool& state)
{