1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-09-03 13:43:34 +02:00

messagelistmodel: support message edition

Handle application/edited-message type to support message edition.
Previous bodies are saved in the interaction to be able to get the
original post to avoid unwanted editions.

While loading a conversation, we store the editions in a temporary
map that we link once the edited message is detected. This work
because we can't edit a message before this message exists.
PreviousBodies in interaction.h contains every previous body detected
and the client can show previous version of the message in a popup.

Deleting a message works the same way, just that any message
with an empty body is not shown.

https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/316

Change-Id: Ib158abd16ad4b629532de11694e88d86a12d72a8
This commit is contained in:
Sébastien Blin 2022-10-11 16:24:04 -04:00
parent 9457c7ccbb
commit 47cd60fbe4
21 changed files with 448 additions and 18 deletions

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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()
}
}
}

View file

@ -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)
}
}
]

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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.")

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
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 = ""
}
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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)
{

View file

@ -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<QString>, 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);

View file

@ -142,6 +142,7 @@
<file>mainview/components/CallButtonDelegate.qml</file>
<file>mainview/components/CallActionBar.qml</file>
<file>commoncomponents/HalfPill.qml</file>
<file>commoncomponents/EditedPopup.qml</file>
<file>commoncomponents/MaterialToolTip.qml</file>
<file>mainview/components/ParticipantCallInStatusDelegate.qml</file>
<file>mainview/components/ParticipantCallInStatusView.qml</file>
@ -163,6 +164,7 @@
<file>mainview/components/MessageBar.qml</file>
<file>mainview/components/FilesToSendContainer.qml</file>
<file>mainview/components/ReplyingContainer.qml</file>
<file>mainview/components/EditContainer.qml</file>
<file>commoncomponents/Avatar.qml</file>
<file>mainview/components/ConversationAvatar.qml</file>
<file>mainview/components/InvitationView.qml</file>

View file

@ -74,7 +74,7 @@ struct Info
QString callId;
QString confId;
std::unique_ptr<MessageListModel> interactions;
QString lastMessageUid = 0;
QString lastMessageUid;
QHash<QString, QString> parentsId; // pair messageid/parentid for messages without parent loaded
unsigned int unreadMessages = 0;
QVector<QPair<int, QString>> errors;

View file

@ -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

View file

@ -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<Body> 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"];

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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<QString, QString> lastDisplayedMessageUid_;
QMap<QString, QStringList> messageToReaders_;
QMap<QString, QStringList> replyTo_;
QMap<QString, QVector<interaction::Body>> editedBodies_;
void moveMessage(const QString& msgId, const QString& parentId);
void insertMessage(int index, item_t& message);

View file

@ -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,