diff --git a/src/app/commoncomponents/EditedPopup.qml b/src/app/commoncomponents/EditedPopup.qml
new file mode 100644
index 00000000..2df12aef
--- /dev/null
+++ b/src/app/commoncomponents/EditedPopup.qml
@@ -0,0 +1,97 @@
+/*
+ * 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 Qt5Compat.GraphicalEffects
+
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+BaseModalDialog {
+ id: root
+
+ width: 488
+ height: 256
+
+ property var previousBodies: undefined
+
+ popupContent: Item {
+ id: rect
+
+ width: root.width
+
+ JamiListView {
+ anchors.fill: parent
+ anchors.margins: JamiTheme.preferredMarginSize
+
+ model: root.previousBodies
+
+ delegate: Rectangle {
+ width: root.width - 2 * JamiTheme.preferredMarginSize
+ height: Math.max(JamiTheme.menuItemsPreferredHeight, rowBody.implicitHeight)
+ color: index % 2 === 0 ? JamiTheme.backgroundColor : JamiTheme.secondaryBackgroundColor
+
+ RowLayout {
+ id: rowBody
+ spacing: JamiTheme.preferredMarginSize
+ width: parent.width
+ anchors.centerIn: parent
+
+ Text {
+ Layout.preferredWidth: root.width / 4
+ Layout.leftMargin: JamiTheme.settingsMarginSize
+
+ text: MessagesAdapter.getFormattedDay(modelData.timestamp.toString())
+ + " - " + MessagesAdapter.getFormattedTime(modelData.timestamp.toString())
+ color: JamiTheme.textColor
+ opacity: 0.5
+ }
+
+ Text {
+ Layout.alignment: Qt.AlignLeft
+ Layout.fillWidth: true
+
+ TextMetrics {
+ id: metrics
+ elide: Text.ElideRight
+ elideWidth: 3 * rowBody.width / 4 - 2 * JamiTheme.preferredMarginSize
+ text: modelData.body
+ }
+
+ text: metrics.elidedText
+ color: JamiTheme.textColor
+ }
+ }
+ }
+ }
+
+ PushButton {
+ id: btnCancel
+ imageColor: "grey"
+ normalColor: "transparent"
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.rightMargin: 10
+ source: JamiResources.round_close_24dp_svg
+ onClicked: close()
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/commoncomponents/SBSContextMenu.qml b/src/app/commoncomponents/SBSContextMenu.qml
index 8c6f0f09..261b0048 100644
--- a/src/app/commoncomponents/SBSContextMenu.qml
+++ b/src/app/commoncomponents/SBSContextMenu.qml
@@ -29,6 +29,7 @@ ContextMenuAutoLoader {
id: root
property string location
+ property bool isOutgoing
property string msgId
property string transferName
property string transferId
@@ -57,8 +58,29 @@ ContextMenuAutoLoader {
itemName: JamiStrings.reply
onClicked: {
+ MessagesAdapter.editId = ""
MessagesAdapter.replyToId = root.msgId
}
+ },
+ GeneralMenuItem {
+ id: edit
+
+ canTrigger: transferId === "" && isOutgoing
+ itemName: JamiStrings.edit
+ onClicked: {
+ MessagesAdapter.replyToId = ""
+ MessagesAdapter.editId = root.msgId
+ }
+ },
+ GeneralMenuItem {
+ id: deleteMsg
+ dangerous: true
+
+ canTrigger: transferId === "" && isOutgoing
+ itemName: JamiStrings.optionDelete
+ onClicked: {
+ MessagesAdapter.editMessage(CurrentConversation.id, "", root.msgId)
+ }
}
]
diff --git a/src/app/commoncomponents/SBSMessageBase.qml b/src/app/commoncomponents/SBSMessageBase.qml
index 343b4da4..13ccfd49 100644
--- a/src/app/commoncomponents/SBSMessageBase.qml
+++ b/src/app/commoncomponents/SBSMessageBase.qml
@@ -153,8 +153,8 @@ Control {
var baseColor = isOutgoing ? JamiTheme.messageOutBgColor
: CurrentConversation.isCoreDialog ?
JamiTheme.messageInBgColor : Qt.lighter(CurrentConversation.color, 1.5)
- if (Id === MessagesAdapter.replyToId) {
- // If we are replying to
+ if (Id === MessagesAdapter.replyToId || Id === MessagesAdapter.editId) {
+ // If we are replying to or editing the message
return Qt.darker(baseColor, 1.5)
}
return baseColor
@@ -289,6 +289,7 @@ Control {
msgId: Id
location: root.location
+ isOutgoing: root.isOutgoing
transferId: root.transferId
transferName: root.transferName
}
diff --git a/src/app/commoncomponents/TextMessageDelegate.qml b/src/app/commoncomponents/TextMessageDelegate.qml
index fdd352f1..6fb61fd4 100644
--- a/src/app/commoncomponents/TextMessageDelegate.qml
+++ b/src/app/commoncomponents/TextMessageDelegate.qml
@@ -42,6 +42,12 @@ SBSMessageBase {
formattedDay: MessagesAdapter.getFormattedDay(Timestamp)
extraHeight: extraContent.active && !isRemoteImage ? msgRadius : -isRemoteImage
+ EditedPopup {
+ id: editedPopup
+
+ previousBodies: PreviousBodies
+ }
+
innerContent.children: [
TextEdit {
id: textEditId
@@ -106,6 +112,48 @@ SBSMessageBase {
selectOnly: parent.readOnly
}
},
+ RowLayout {
+ id: editedRow
+ anchors.right: isOutgoing ? parent.right : undefined
+ visible: PreviousBodies.length !== 0
+
+ ResponsiveImage {
+ id: editedImage
+
+ Layout.leftMargin: JamiTheme.preferredMarginSize
+ Layout.bottomMargin: JamiTheme.preferredMarginSize
+
+ source: JamiResources.round_edit_24dp_svg
+ width: JamiTheme.editedFontSize
+ height: JamiTheme.editedFontSize
+ layer {
+ enabled: true
+ effect: ColorOverlay {
+ color: editedLabel.color
+ }
+ }
+ }
+
+ Text {
+ id: editedLabel
+
+ Layout.rightMargin: JamiTheme.preferredMarginSize
+ Layout.bottomMargin: JamiTheme.preferredMarginSize
+
+ text: JamiStrings.edited
+ color: UtilsAdapter.luma(bubble.color) ?
+ JamiTheme.chatviewTextColorLight :
+ JamiTheme.chatviewTextColorDark
+ font.pointSize: JamiTheme.editedFontSize
+
+ TapHandler {
+ acceptedButtons: Qt.LeftButton
+ onTapped: {
+ editedPopup.open()
+ }
+ }
+ }
+ },
Loader {
id: extraContent
anchors.right: isOutgoing ? parent.right : undefined
diff --git a/src/app/commoncomponents/contextmenu/GeneralMenuItem.qml b/src/app/commoncomponents/contextmenu/GeneralMenuItem.qml
index 82a0b9fb..d42b5cd5 100644
--- a/src/app/commoncomponents/contextmenu/GeneralMenuItem.qml
+++ b/src/app/commoncomponents/contextmenu/GeneralMenuItem.qml
@@ -37,6 +37,7 @@ MenuItem {
property bool canTrigger: true
property bool addMenuSeparatorAfter: false
property bool autoTextSizeAdjustment: true
+ property bool dangerous: false
property BaseContextMenu parentMenu
property int itemPreferredWidth: JamiTheme.menuItemsPreferredWidth
@@ -94,7 +95,7 @@ MenuItem {
Layout.fillWidth: true
text: itemName
- color: JamiTheme.textColor
+ color: dangerous ? JamiTheme.redColor : JamiTheme.textColor
font.pointSize: JamiTheme.textFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 3e83a6c4..9624c0df 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -726,6 +726,8 @@ Item {
property string inReplyTo: qsTr("In reply to")
property string reply: qsTr("Reply")
property string writeTo: qsTr("Write to %1")
+ property string edit: qsTr("Edit")
+ property string edited: qsTr("Edited")
// Invitation View
property string invitationViewSentRequest: qsTr("%1 has sent you a request for a conversation.")
diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 67447c78..7712b000 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -267,6 +267,7 @@ Item {
property real smartlistItemInfoFontSize: calcSize(9 + fontSizeOffsetSmall)
property real filterItemFontSize: calcSize(smartlistItemFontSize)
property real filterBadgeFontSize: calcSize(8.25)
+ property real editedFontSize: calcSize(8)
property real accountListItemHeight: 64
property real accountListAvatarSize: 40
property real smartListItemHeight: 64
diff --git a/src/app/mainview/components/ChatViewFooter.qml b/src/app/mainview/components/ChatViewFooter.qml
index c5107076..9ab6cd28 100644
--- a/src/app/mainview/components/ChatViewFooter.qml
+++ b/src/app/mainview/components/ChatViewFooter.qml
@@ -81,6 +81,16 @@ Rectangle {
function onNewTextPasted() {
messageBar.textAreaObj.pasteText()
}
+
+ function onEditIdChanged() {
+ if (MessagesAdapter.editId.length > 0)
+ messageBar.textAreaObj.forceActiveFocus()
+ }
+
+ function onReplyToIdChanged() {
+ if (MessagesAdapter.replyToId.length > 0)
+ messageBar.forceActiveFocus()
+ }
}
RecordBox {
@@ -131,6 +141,16 @@ Rectangle {
visible: MessagesAdapter.replyToId !== ""
}
+ EditContainer {
+ id: editContainer
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: footerColumnLayout.width
+ Layout.maximumWidth: JamiTheme.chatViewMaximumWidth
+ Layout.preferredHeight: 36
+ visible: MessagesAdapter.editId !== ""
+ }
+
MessageBar {
id: messageBar
@@ -162,8 +182,13 @@ Rectangle {
onSendFileButtonClicked: jamiFileDialog.open()
onSendMessageButtonClicked: {
// Send text message
- if (messageBar.text)
- MessagesAdapter.sendMessage(messageBar.text)
+ if (messageBar.text) {
+ if (MessagesAdapter.editId !== "") {
+ MessagesAdapter.editMessage(CurrentConversation.id, messageBar.text)
+ } else {
+ MessagesAdapter.sendMessage(messageBar.text)
+ }
+ }
messageBar.textAreaObj.clearText()
// Send file messages
diff --git a/src/app/mainview/components/EditContainer.qml b/src/app/mainview/components/EditContainer.qml
new file mode 100644
index 00000000..5f136098
--- /dev/null
+++ b/src/app/mainview/components/EditContainer.qml
@@ -0,0 +1,99 @@
+/*
+ * 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 body: {
+ if (MessagesAdapter.editId === "")
+ return ""
+
+ return MessagesAdapter.dataForInteraction(MessagesAdapter.editId, MessageList.Body)
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 12
+
+ RowLayout {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+
+ Label {
+ id: editLbl
+
+ text: JamiStrings.edit
+
+ color: UtilsAdapter.luma(root.color) ?
+ JamiTheme.chatviewTextColorLight :
+ JamiTheme.chatviewTextColorDark
+ font.pointSize: JamiTheme.textFontSize
+ font.kerning: true
+ font.bold: true
+ Layout.leftMargin: JamiTheme.preferredMarginSize
+ }
+
+ Label {
+ id: bodyLbl
+
+ TextMetrics {
+ id: metrics
+ elide: Text.ElideRight
+ elideWidth: root.width - 100
+ text: root.body
+ }
+
+ text: metrics.elidedText
+ 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.editId = ""
+ }
+ }
+}
diff --git a/src/app/mainview/components/MessageListView.qml b/src/app/mainview/components/MessageListView.qml
index ffc2c012..01cff87d 100644
--- a/src/app/mainview/components/MessageListView.qml
+++ b/src/app/mainview/components/MessageListView.qml
@@ -193,7 +193,8 @@ JamiListView {
onMessageListModelChanged: sourceModel = messageListModel
filters: ExpressionFilter {
readonly property int mergeType: Interaction.Type.MERGE
- expression: Body !== "" && Type !== mergeType
+ readonly property int editedType: Interaction.Type.EDITED
+ expression: Body !== "" && Type !== mergeType && Type !== editedType
}
sorters: ExpressionSorter {
expression: modelLeft.index > modelRight.index
diff --git a/src/app/mainview/components/ParticipantOverlay.qml b/src/app/mainview/components/ParticipantOverlay.qml
index e66bdb54..3de0ad19 100644
--- a/src/app/mainview/components/ParticipantOverlay.qml
+++ b/src/app/mainview/components/ParticipantOverlay.qml
@@ -291,7 +291,7 @@ Item {
visible: (!root.isMe && !root.meModerator) ? root.participantIsMuted : root.isLocalMuted
source: JamiResources.micro_off_black_24dp_svg
- color: "red"
+ color: JamiTheme.redColor
HoverHandler { id: hoverMicrophone }
MaterialToolTip {
diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp
index b3ae25b3..3d9f70ba 100644
--- a/src/app/messagesadapter.cpp
+++ b/src/app/messagesadapter.cpp
@@ -52,6 +52,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
{
connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() {
set_replyToId("");
+ set_editId("");
const QString& convId = lrcInstance_->get_selectedConvUid();
const auto& conversation = lrcInstance_->getConversationFromConvUid(convId);
set_messageListModel(QVariant::fromValue(conversation.interactions.get()));
@@ -155,6 +156,23 @@ MessagesAdapter::sendMessage(const QString& message)
}
}
+void
+MessagesAdapter::editMessage(const QString& convId, const QString& newBody, const QString& messageId)
+{
+ try {
+ const auto convUid = lrcInstance_->get_selectedConvUid();
+ auto editId = !messageId.isEmpty() ? messageId : editId_;
+ if (editId.isEmpty()) {
+ qWarning("No message to edit");
+ return;
+ }
+ lrcInstance_->getCurrentConversationModel()->editMessage(convId, newBody, editId);
+ set_editId("");
+ } catch (...) {
+ qDebug() << "Exception during message edition:" << messageId;
+ }
+}
+
void
MessagesAdapter::sendFile(const QString& message)
{
diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h
index 21097934..b059fc8e 100644
--- a/src/app/messagesadapter.h
+++ b/src/app/messagesadapter.h
@@ -32,6 +32,7 @@ class MessagesAdapter final : public QmlAdapterBase
Q_OBJECT
QML_RO_PROPERTY(QVariant, messageListModel)
QML_PROPERTY(QString, replyToId)
+ QML_PROPERTY(QString, editId)
QML_RO_PROPERTY(QList, currentConvComposingList)
public:
@@ -67,6 +68,9 @@ protected:
Q_INVOKABLE void unbanContact(int index);
Q_INVOKABLE void unbanConversation(const QString& convUid);
Q_INVOKABLE void sendMessage(const QString& message);
+ Q_INVOKABLE void editMessage(const QString& convId,
+ const QString& newBody,
+ const QString& messageId = "");
Q_INVOKABLE void sendFile(const QString& message);
Q_INVOKABLE void acceptFile(const QString& arg);
Q_INVOKABLE void cancelFile(const QString& arg);
diff --git a/src/app/qml.qrc b/src/app/qml.qrc
index 854594f8..3560ee0b 100644
--- a/src/app/qml.qrc
+++ b/src/app/qml.qrc
@@ -142,6 +142,7 @@
mainview/components/CallButtonDelegate.qml
mainview/components/CallActionBar.qml
commoncomponents/HalfPill.qml
+ commoncomponents/EditedPopup.qml
commoncomponents/MaterialToolTip.qml
mainview/components/ParticipantCallInStatusDelegate.qml
mainview/components/ParticipantCallInStatusView.qml
@@ -163,6 +164,7 @@
mainview/components/MessageBar.qml
mainview/components/FilesToSendContainer.qml
mainview/components/ReplyingContainer.qml
+ mainview/components/EditContainer.qml
commoncomponents/Avatar.qml
mainview/components/ConversationAvatar.qml
mainview/components/InvitationView.qml
diff --git a/src/libclient/api/conversation.h b/src/libclient/api/conversation.h
index 51911788..5cb87ecc 100644
--- a/src/libclient/api/conversation.h
+++ b/src/libclient/api/conversation.h
@@ -74,7 +74,7 @@ struct Info
QString callId;
QString confId;
std::unique_ptr interactions;
- QString lastMessageUid = 0;
+ QString lastMessageUid;
QHash parentsId; // pair messageid/parentid for messages without parent loaded
unsigned int unreadMessages = 0;
QVector> errors;
diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h
index 29907bb8..d0871caa 100644
--- a/src/libclient/api/conversationmodel.h
+++ b/src/libclient/api/conversationmodel.h
@@ -217,6 +217,13 @@ public:
* @param parentId id of parent message. Default is "" - last message in conversation.
*/
void sendMessage(const QString& uid, const QString& body, const QString& parentId = "");
+ /**
+ * Edit a message (empty body = delete message)
+ * @param convId The conversation with the message to edit
+ * @param newBody The new body
+ * @param messageId The id of the message (MUST be by the same author & plain/text)
+ */
+ void editMessage(const QString& convId, const QString& newBody, const QString& messageId);
/**
* Modify the current filter (will change the result of getFilteredConversations)
* @param filter the new filter
diff --git a/src/libclient/api/interaction.h b/src/libclient/api/interaction.h
index 3b32df01..5993fc79 100644
--- a/src/libclient/api/interaction.h
+++ b/src/libclient/api/interaction.h
@@ -32,7 +32,7 @@ namespace interaction {
Q_NAMESPACE
Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
-enum class Type { INVALID, INITIAL, TEXT, CALL, CONTACT, DATA_TRANSFER, MERGE, COUNT__ };
+enum class Type { INVALID, INITIAL, TEXT, CALL, CONTACT, DATA_TRANSFER, MERGE, EDITED, COUNT__ };
Q_ENUM_NS(Type)
static inline const QString
@@ -51,6 +51,8 @@ to_string(const Type& type)
return "DATA_TRANSFER";
case Type::MERGE:
return "MERGE";
+ case Type::EDITED:
+ return "EDITED";
case Type::INVALID:
case Type::COUNT__:
default:
@@ -73,6 +75,8 @@ to_type(const QString& type)
return interaction::Type::DATA_TRANSFER;
else if (type == "merge")
return interaction::Type::MERGE;
+ else if (type == "application/edited-message")
+ return interaction::Type::EDITED;
else
return interaction::Type::INVALID;
}
@@ -238,6 +242,19 @@ getContactInteractionString(const QString& authorUri, const ContactAction& actio
return {};
}
+struct Body
+{
+ Q_GADGET
+
+ Q_PROPERTY(QString commitId MEMBER commitId)
+ Q_PROPERTY(QString body MEMBER body)
+ Q_PROPERTY(int timestamp MEMBER timestamp)
+public:
+ QString commitId;
+ QString body;
+ std::time_t timestamp;
+};
+
/**
* @var authorUri
* @var body
@@ -263,6 +280,7 @@ struct Info
MapStringString commit;
QVariantMap linkPreviewInfo = {};
bool linkified = false;
+ QVector previousBodies;
Info() {}
@@ -286,7 +304,7 @@ struct Info
Info(const MapStringString& message, const QString& accountURI)
{
type = to_type(message["type"]);
- if (type == Type::TEXT) {
+ if (type == Type::TEXT || type == Type::EDITED) {
body = message["body"];
}
authorUri = accountURI == message["author"] ? "" : message["author"];
diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp
index 0a4184ff..c904ad1d 100644
--- a/src/libclient/conversationmodel.cpp
+++ b/src/libclient/conversationmodel.cpp
@@ -220,7 +220,7 @@ public:
const VectorString peersForConversation(const conversation::Info& conversation) const;
// insert swarm interactions. Return false if interaction already exists.
bool insertSwarmInteraction(const QString& interactionId,
- const interaction::Info& interaction,
+ interaction::Info& interaction,
conversation::Info& conversation,
bool insertAtBegin);
void invalidateModel();
@@ -1154,7 +1154,7 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS
try {
auto& conversation = pimpl_->getConversationForUid(uid, true).get();
if (!conversation.isLegacy()) {
- ConfigurationManager::instance().sendMessage(owner.id, uid, body, parentId);
+ ConfigurationManager::instance().sendMessage(owner.id, uid, body, parentId, 0);
return;
}
@@ -1181,7 +1181,8 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS
ConfigurationManager::instance().sendMessage(owner.id,
conversationId,
body,
- parentId);
+ parentId,
+ 0);
return;
}
auto& peers = pimpl_->peersForConversation(newConv);
@@ -1271,6 +1272,18 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS
}
}
+void
+ConversationModel::editMessage(const QString& convId,
+ const QString& newBody,
+ const QString& messageId)
+{
+ auto conversationOpt = getConversationForUid(convId);
+ if (!conversationOpt.has_value()) {
+ return;
+ }
+ ConfigurationManager::instance().sendMessage(owner.id, convId, newBody, messageId, 1);
+}
+
void
ConversationModel::refreshFilter()
{
@@ -2323,6 +2336,7 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
}
auto msgId = message["id"];
auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
+ conversation.interactions->editMessage(msgId, msg);
auto downloadFile = false;
if (msg.type == interaction::Type::INITIAL) {
allLoaded = true;
@@ -2356,6 +2370,8 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
msg.body = interaction::getContactInteractionString(bestName,
interaction::to_action(
message["action"]));
+ } else if (msg.type == interaction::Type::EDITED) {
+ conversation.interactions->addEdition(msgId, msg, false);
}
insertSwarmInteraction(msgId, msg, conversation, true);
if (downloadFile) {
@@ -2380,7 +2396,6 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
return;
}
}
-
// In this case, we only have loaded merge commits. Load more messages
ConfigurationManager::instance().loadConversationMessages(linked.owner.id,
conversationId,
@@ -2427,6 +2442,7 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
}
auto msgId = message["id"];
auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
+ conversation.interactions->editMessage(msgId, msg);
api::datatransfer::Info info;
QString fileId;
@@ -2464,6 +2480,8 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
} else if (msg.type == interaction::Type::TEXT
&& msg.authorUri != linked.owner.profileInfo.uri) {
conversation.unreadMessages++;
+ } else if (msg.type == interaction::Type::EDITED) {
+ conversation.interactions->addEdition(msgId, msg, true);
}
if (!insertSwarmInteraction(msgId, msg, conversation, false)) {
// message already exists
@@ -2510,7 +2528,7 @@ ConversationModelPimpl::slotConversationProfileUpdated(const QString& accountId,
bool
ConversationModelPimpl::insertSwarmInteraction(const QString& interactionId,
- const interaction::Info& interaction,
+ interaction::Info& interaction,
conversation::Info& conversation,
bool insertAtBegin)
{
@@ -2518,6 +2536,11 @@ ConversationModelPimpl::insertSwarmInteraction(const QString& interactionId,
auto itExists = conversation.interactions->find(interactionId);
if (itExists != conversation.interactions->end()) {
// Erase interaction if exists, as it will be updated via a re-insertion
+ if (itExists->second.previousBodies.size() != 0) {
+ // If the message was edited, we should keep this state
+ interaction.body = itExists->second.body;
+ interaction.previousBodies = itExists->second.previousBodies;
+ }
itExists = conversation.interactions->erase(itExists);
if (itExists != conversation.interactions->end()) {
// next interaction doesn't have parent anymore.
diff --git a/src/libclient/messagelistmodel.cpp b/src/libclient/messagelistmodel.cpp
index 339ec289..a2c5cb43 100644
--- a/src/libclient/messagelistmodel.cpp
+++ b/src/libclient/messagelistmodel.cpp
@@ -176,6 +176,7 @@ MessageListModel::clear()
Q_EMIT beginResetModel();
interactions_.clear();
replyTo_.clear();
+ editedBodies_.clear();
Q_EMIT endResetModel();
}
@@ -410,6 +411,13 @@ 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::PreviousBodies: {
+ QVariantList variantList;
+ for (int i = 0; i < item.second.previousBodies.size(); i++) {
+ variantList.append(QVariant::fromValue(item.second.previousBodies[i]));
+ }
+ return variantList;
+ }
case Role::ReplyTo:
return QVariant(replyId);
case Role::ReplyToAuthor:
@@ -541,12 +549,58 @@ MessageListModel::emitDataChanged(const QString& msgId, VectorInt roles)
Q_EMIT dataChanged(modelIndex, modelIndex, roles);
}
+void
+MessageListModel::addEdition(const QString& msgId, const interaction::Info& info, bool end)
+{
+ auto editedId = info.commit["edit"];
+ if (editedId.isEmpty())
+ return;
+ auto& edited = editedBodies_[editedId];
+ auto value = interaction::Body {msgId, info.body, info.timestamp};
+ if (end)
+ edited.push_back(value);
+ else
+ edited.push_front(value);
+ auto editedIt = find(editedId);
+ if (editedIt != interactions_.end()) {
+ // If already there, we can update the content
+ editMessage(editedId, editedIt->second);
+ }
+}
+
+void
+MessageListModel::editMessage(const QString& msgId, interaction::Info& info)
+{
+ auto it = editedBodies_.find(msgId);
+ if (it != editedBodies_.end()) {
+ if (info.previousBodies.isEmpty()) {
+ info.previousBodies.push_back(interaction::Body {msgId, info.body, info.timestamp});
+ }
+ // Find if already added (because MessageReceived can be triggered
+ // multiple times for same message)
+ for (const auto& editedBody : *it) {
+ auto itCommit = std::find_if(info.previousBodies.begin(),
+ info.previousBodies.end(),
+ [&](const auto& element) {
+ return element.commitId == editedBody.commitId;
+ });
+ if (itCommit == info.previousBodies.end()) {
+ info.previousBodies.push_back(editedBody);
+ }
+ }
+ info.body = it->rbegin()->body;
+ editedBodies_.erase(it);
+ emitDataChanged(msgId, {MessageList::Role::Body, MessageList::Role::PreviousBodies});
+ }
+}
+
QString
MessageListModel::lastMessageUid() const
{
for (auto it = interactions_.rbegin(); it != interactions_.rend(); ++it) {
auto lastType = it->second.type;
- if (lastType != interaction::Type::MERGE and !it->second.body.isEmpty()) {
+ if (lastType != interaction::Type::MERGE and lastType != interaction::Type::EDITED
+ and !it->second.body.isEmpty()) {
return it->first;
}
}
diff --git a/src/libclient/messagelistmodel.h b/src/libclient/messagelistmodel.h
index b3d494ac..241e59bd 100644
--- a/src/libclient/messagelistmodel.h
+++ b/src/libclient/messagelistmodel.h
@@ -45,6 +45,7 @@ struct Info;
X(ActionUri) \
X(LinkPreviewInfo) \
X(Linkified) \
+ X(PreviousBodies) \
X(ReplyTo) \
X(ReplyToBody) \
X(ReplyToAuthor) \
@@ -130,6 +131,8 @@ public:
Q_SIGNAL void timestampUpdate();
+ void addEdition(const QString& msgId, const interaction::Info& info, bool end);
+ void editMessage(const QString& msgId, interaction::Info& info);
QString lastMessageUid() const;
protected:
@@ -145,6 +148,7 @@ private:
QMap lastDisplayedMessageUid_;
QMap messageToReaders_;
QMap replyTo_;
+ QMap> editedBodies_;
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 4946e86a..ad7328bf 100644
--- a/src/libclient/qtwrapper/configurationmanager_wrap.h
+++ b/src/libclient/qtwrapper/configurationmanager_wrap.h
@@ -1047,13 +1047,16 @@ public Q_SLOTS: // METHODS
void sendMessage(const QString& accountId,
const QString& conversationId,
const QString& message,
- const QString& parent)
+ const QString& parent,
+ int flags = 0)
{
DRing::sendMessage(accountId.toStdString(),
conversationId.toStdString(),
message.toStdString(),
- parent.toStdString());
+ parent.toStdString(),
+ flags);
}
+
uint32_t loadConversationMessages(const QString& accountId,
const QString& conversationId,
const QString& fromId,