diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index 149b8262f..339244531 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -394,6 +394,7 @@ void ChatCore::setSelf(QSharedPointer me) { [this](std::shared_ptr f) { updateInfo(f); }); mCoreModelConnection->makeConnectToModel(&CoreModel::friendRemoved, [this](std::shared_ptr f) { updateInfo(f, true); }); + } QDateTime ChatCore::getLastUpdatedTime() const { diff --git a/Linphone/core/chat/message/ChatMessageCore.cpp b/Linphone/core/chat/message/ChatMessageCore.cpp index ff1dbe9fc..578ba3a43 100644 --- a/Linphone/core/chat/message/ChatMessageCore.cpp +++ b/Linphone/core/chat/message/ChatMessageCore.cpp @@ -115,11 +115,13 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr &c if (chatmessage) { mChatMessageModel = Utils::makeQObject_ptr(chatmessage); mChatMessageModel->setSelf(mChatMessageModel); - mText = ToolModel::getMessageFromContent(chatmessage->getContents()); + mText = ToolModel::getMessageFromMessage(chatmessage); mUtf8Text = mChatMessageModel->getUtf8Text(); mHasTextContent = mChatMessageModel->getHasTextContent(); mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime()); mIsOutgoing = chatmessage->isOutgoing(); + mIsRetractable = chatmessage->isRetractable(); + mIsRetracted = chatmessage->isRetracted(); mIsRemoteMessage = !chatmessage->isOutgoing(); mPeerAddress = Utils::coreStringToAppString(chatmessage->getPeerAddress()->asStringUriOnly()); mPeerName = ToolModel::getDisplayName(chatmessage->getPeerAddress()); @@ -191,7 +193,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr &c if (mIsReply) { auto replymessage = chatmessage->getReplyMessage(); if (replymessage) { - mReplyText = ToolModel::getMessageFromContent(replymessage->getContents()); + mReplyText = ToolModel::getMessageFromMessage(replymessage); if (mIsFromChatGroup) mRepliedToName = ToolModel::getDisplayName(replymessage->getFromAddress()); } } @@ -207,6 +209,9 @@ void ChatMessageCore::setSelf(QSharedPointer me) { mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lDelete, [this] { mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->deleteMessageFromChatRoom(true); }); }); + mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lRetract, [this] { + mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->retractMessageFromChatRoom(); }); + }); mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageDeleted, [this](bool deletedByUser) { mChatMessageModelConnection->invokeToCore([this, deletedByUser] { //: Deleted @@ -350,6 +355,15 @@ void ChatMessageCore::setSelf(QSharedPointer me) { int duration = now.secsTo(QDateTime::fromSecsSinceEpoch(expireTime)); mChatMessageModelConnection->invokeToCore([this, duration] { setEphemeralDuration(duration); }); }); + + mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::retracted, + [this](const std::shared_ptr &message) { + QString text = ToolModel::getMessageFromMessage(message); + mChatMessageModelConnection->invokeToCore([this, text] { + setText(text); + setRetracted(); + }); + }); } QList ChatMessageCore::computeDeliveryStatus(const std::shared_ptr &message) { @@ -472,6 +486,18 @@ void ChatMessageCore::setIsRead(bool read) { } } +void ChatMessageCore::setRetracted() { + if (!mIsRetracted) { + mIsRetracted = true; + emit isRetractedChanged(); + emit messageStateChanged(); + } +} + +bool ChatMessageCore::isRetracted() const { + return mIsRetracted; +} + QString ChatMessageCore::getOwnReaction() const { return mOwnReaction; } @@ -650,4 +676,4 @@ std::shared_ptr ChatMessageCore::getModel() const { ChatMessageContentGui *ChatMessageCore::getVoiceRecordingContent() const { return new ChatMessageContentGui(mVoiceRecordingContent); -} \ No newline at end of file +} diff --git a/Linphone/core/chat/message/ChatMessageCore.hpp b/Linphone/core/chat/message/ChatMessageCore.hpp index 8997f8ac6..3ec7fea3d 100644 --- a/Linphone/core/chat/message/ChatMessageCore.hpp +++ b/Linphone/core/chat/message/ChatMessageCore.hpp @@ -111,6 +111,9 @@ class ChatMessageCore : public QObject, public AbstractObject { Q_PROPERTY(bool hasFileContent MEMBER mHasFileContent CONSTANT) Q_PROPERTY(bool isVoiceRecording MEMBER mIsVoiceRecording CONSTANT) Q_PROPERTY(bool isCalendarInvite MEMBER mIsCalendarInvite CONSTANT) + Q_PROPERTY(bool isOutgoing MEMBER mIsOutgoing CONSTANT) + Q_PROPERTY(bool isRetractable MEMBER mIsRetractable CONSTANT) + Q_PROPERTY(bool isRetracted READ isRetracted NOTIFY isRetractedChanged) public: static QSharedPointer create(const std::shared_ptr &chatmessage); @@ -143,6 +146,9 @@ public: bool isRead() const; void setIsRead(bool read); + bool isRetracted() const; + void setRetracted(); + QString getOwnReaction() const; void setOwnReaction(const QString &reaction); QString getTotalReactionsLabel() const; @@ -176,9 +182,11 @@ signals: void messageReactionChanged(); void singletonReactionMapChanged(); void ephemeralDurationChanged(int duration); + void isRetractedChanged(); void lDelete(); void deleted(); + void lRetract(); void lMarkAsRead(); void readChanged(); void lSendReaction(const QString &reaction); @@ -214,6 +222,8 @@ private: bool mIsVoiceRecording = false; bool mIsEphemeral = false; int mEphemeralDuration = 0; + bool mIsRetractable = false; + bool mIsRetracted = false; bool mIsOutgoing = false; QString mTotalReactionsLabel; diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index fc1ffd3fb..bad87c1bd 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -2486,6 +2486,42 @@ Only your correspondent can decrypt them. %1 is writing… %1 is writing… + + + conversation_dialog_delete_chat_message_title + "Delete this message?" + Delete this message? + + + + conversation_dialog_delete_locally_label + "For me" + For me + + + + conversation_dialog_delete_for_everyone_label + "For everyone" + For everyone + + + + dialog_cancel + "Cancel" + Cancel + + + + info_toast_deleted_title + Deleted + Deleted + + + + info_toast_deleted_message + The message has been deleted + The message has been deleted + ChatPage @@ -5947,6 +5983,18 @@ To enable them in a commercial project, please contact us. conference_invitation_updated Meeting modification + + + conversation_message_content_deleted_label + "<i>This message has been deleted</i>" + <i>This message has been deleted</i> + + + + conversation_message_content_deleted_by_us_label + "<i>You have deleted this message</i>" + <i>You have deleted this message</i> + Utils diff --git a/Linphone/data/languages/fr.ts b/Linphone/data/languages/fr.ts index e644676e8..d85b11cce 100644 --- a/Linphone/data/languages/fr.ts +++ b/Linphone/data/languages/fr.ts @@ -2486,6 +2486,42 @@ en bout. Seul votre correspondant peut les déchiffrer. %1 is writing… %1 est en train d'écrire… + + + conversation_dialog_delete_chat_message_title + "Delete this message?" + Supprimer le message ? + + + + conversation_dialog_delete_locally_label + "For me" + Pour moi + + + + conversation_dialog_delete_for_everyone_label + "For everyone" + Pour tout le monde + + + + dialog_cancel + "Cancel" + Annuler + + + + info_toast_deleted_title + Deleted + Supprimé + + + + info_toast_deleted_message + The message has been deleted + Le message a été supprimé + ChatPage @@ -5947,6 +5983,18 @@ Pour les activer dans un projet commercial, merci de nous contacter.conference_invitation_updated Modification d'une réunion + + + conversation_message_content_deleted_label + "<i>This message has been deleted</i>" + <i>Le message a été supprimé</i> + + + + conversation_message_content_deleted_by_us_label + "<i>You have deleted this message</i>" + <i>Vous avez supprimé le message</i> + Utils diff --git a/Linphone/model/chat/message/ChatMessageModel.cpp b/Linphone/model/chat/message/ChatMessageModel.cpp index d69fbd94d..347b4bd83 100644 --- a/Linphone/model/chat/message/ChatMessageModel.cpp +++ b/Linphone/model/chat/message/ChatMessageModel.cpp @@ -51,7 +51,7 @@ ChatMessageModel::~ChatMessageModel() { } QString ChatMessageModel::getText() const { - return ToolModel::getMessageFromContent(mMonitor->getContents()); + return ToolModel::getMessageFromMessage(mMonitor); } QString ChatMessageModel::getUtf8Text() const { @@ -103,6 +103,13 @@ void ChatMessageModel::deleteMessageFromChatRoom(bool deletedByUser) { } } +void ChatMessageModel::retractMessageFromChatRoom() { + auto chatRoom = mMonitor->getChatRoom(); + if (chatRoom) { + chatRoom->retractMessage(mMonitor); + } +} + void ChatMessageModel::sendReaction(const QString &reaction) { auto linReaction = mMonitor->createReaction(Utils::appStringToCoreString(reaction)); linReaction->send(); @@ -188,3 +195,7 @@ void ChatMessageModel::onEphemeralMessageTimerStarted(const std::shared_ptr &message) { emit ephemeralMessageDeleted(message); } + +void ChatMessageModel::onRetracted(const std::shared_ptr &message) { + emit retracted(message); +} diff --git a/Linphone/model/chat/message/ChatMessageModel.hpp b/Linphone/model/chat/message/ChatMessageModel.hpp index 5ad9b71b2..6d916f5bd 100644 --- a/Linphone/model/chat/message/ChatMessageModel.hpp +++ b/Linphone/model/chat/message/ChatMessageModel.hpp @@ -52,6 +52,7 @@ public: void markAsRead(); void deleteMessageFromChatRoom(bool deletedByUser); + void retractMessageFromChatRoom(); void sendReaction(const QString &reaction); @@ -95,6 +96,7 @@ signals: void ephemeralMessageTimerStarted(const std::shared_ptr &message); void ephemeralMessageDeleted(const std::shared_ptr &message); void ephemeralMessageTimeUpdated(const std::shared_ptr &message, int expireTime); + void retracted(const std::shared_ptr &message); private: linphone::ChatMessage::State mMessageState; @@ -130,6 +132,7 @@ private: const std::shared_ptr &state) override; void onEphemeralMessageTimerStarted(const std::shared_ptr &message) override; void onEphemeralMessageDeleted(const std::shared_ptr &message) override; + void onRetracted(const std::shared_ptr &message) override; }; -#endif \ No newline at end of file +#endif diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index 9e654721a..215c2e5eb 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -537,6 +537,15 @@ QString ToolModel::getMessageFromContent(std::list message) { + if (message->isRetracted()) { + return message->isOutgoing() ? tr("conversation_message_content_deleted_by_us_label") + : tr("conversation_message_content_deleted_label"); + } else { + return getMessageFromContent(message->getContents()); + } +} + // Load downloaded codecs like OpenH264 (needs to be after core is created and has loaded its plugins, as // reloadMsPlugins modifies plugin path for the factory) void ToolModel::loadDownloadedCodecs() { diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index 6cc2924fa..e778414e2 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -77,6 +77,7 @@ public: const std::shared_ptr &f); static QString getMessageFromContent(std::list> contents); + static QString getMessageFromMessage(std::shared_ptr message); static void loadDownloadedCodecs(); static void updateCodecs(); diff --git a/Linphone/view/Control/Display/Chat/ChatMessage.qml b/Linphone/view/Control/Display/Chat/ChatMessage.qml index 16c3df312..aa68b8f9f 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessage.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessage.qml @@ -247,11 +247,21 @@ Control.Control { } contentItem: ColumnLayout { spacing: Utils.getSizeWithScreenRatio(5) + Text { + id: retractedId + visible: mainItem.chatMessage.core.isRetracted + font: Typography.p1i + color: DefaultStyle.info_800_main + Layout.fillWidth: true + Layout.fillHeight: true + text: mainItem.chatMessage.core.text + } ChatMessageContent { id: chatBubbleContent Layout.fillWidth: true Layout.fillHeight: true chatGui: mainItem.chat + visible: !mainItem.chatMessage.core.isRetracted searchedTextPart: mainItem.searchedTextPart chatMessageGui: mainItem.chatMessage maxWidth: mainItem.maxWidth @@ -415,6 +425,7 @@ Control.Control { } IconLabelButton { inverseLayout: true + visible: !mainItem.chatMessage.core.isRetracted //: Reply text: qsTr("chat_message_reply") icon.source: AppIcons.reply @@ -427,6 +438,7 @@ Control.Control { } IconLabelButton { inverseLayout: true + visible: !mainItem.chatMessage.core.isRetracted text: chatBubbleContent.selectedText != "" //: "Copy selection" ? qsTr("chat_message_copy_selection") @@ -447,6 +459,7 @@ Control.Control { } IconLabelButton { inverseLayout: true + visible: !mainItem.chatMessage.core.isRetracted //: Forward text: qsTr("chat_message_forward") icon.source: AppIcons.forward diff --git a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml index a152f0285..b042c5f2e 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml @@ -225,6 +225,51 @@ ListView { indicatorColor: DefaultStyle.main1_500_main } + Dialog { + id: messageDeletionDialog + width: Utils.getSizeWithScreenRatio(637) + //: "Supprimer le message ?" + title: qsTr("conversation_dialog_delete_chat_message_title") + property var chatMessage + buttons: RowLayout { + id: buttonsLayout + Layout.alignment: Qt.AlignBottom | Qt.AlignRight + spacing: Utils.getSizeWithScreenRatio(20) + MediumButton { + id: firstButtonId + text: qsTr("conversation_dialog_delete_locally_label") + style: ButtonStyle.main + onClicked: { + messageDeletionDialog.chatMessage.core.lDelete() + messageDeletionDialog.close() + } + KeyNavigation.left: thirdButtonId + KeyNavigation.right: secondButtonId + } + MediumButton { + id: secondButtonId + text: qsTr("conversation_dialog_delete_for_everyone_label") + style: ButtonStyle.main + onClicked: { + messageDeletionDialog.chatMessage.core.lRetract() + messageDeletionDialog.close() + } + KeyNavigation.left: firstButtonId + KeyNavigation.right: thirdButtonId + } + MediumButton { + id: thirdButtonId + text: qsTr("dialog_cancel") + style: ButtonStyle.secondary + onClicked: { + messageDeletionDialog.close() + } + KeyNavigation.left: secondButtonId + KeyNavigation.right: firstButtonId + } + } + } + delegate: DelegateChooser { role: "eventType" DelegateChoice { @@ -262,7 +307,14 @@ ListView { ? parent.right : undefined - onMessageDeletionRequested: chatMessage.core.lDelete() + onMessageDeletionRequested: { + if (chatMessage.core.isOutgoing && chatMessage.core.isRetractable && !chatMessage.core.isRetracted) { + messageDeletionDialog.chatMessage = chatMessage + messageDeletionDialog.open() + } else { + chatMessage.core.lDelete() + } + } onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(chatMessage) onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(chatMessage) onReplyToMessageRequested: mainItem.replyToMessageRequested(chatMessage) @@ -283,6 +335,16 @@ ListView { } } } + Connections { + target: chatMessage.core + onIsRetractedChanged: { + if (chatMessage.core.isRetracted && chatMessage.core.isOutgoing) { + UtilsCpp.showInformationPopup(qsTr("info_toast_deleted_title"), + //: The message has been deleted + qsTr("info_toast_deleted_message"), true) + } + } + } } } diff --git a/Linphone/view/Style/DefaultStyle.qml b/Linphone/view/Style/DefaultStyle.qml index c514f19e7..e1adb509b 100644 --- a/Linphone/view/Style/DefaultStyle.qml +++ b/Linphone/view/Style/DefaultStyle.qml @@ -47,6 +47,7 @@ QtObject { property var success_700: "#377d71" property var success_900: "#1E4C53" property var info_500_main: "#4AA8FF" + property var info_800_main: "#02528D" property var vue_meter_light_green: "#6FF88D" property var vue_meter_dark_green: "#00D916" diff --git a/Linphone/view/Style/Typography.qml b/Linphone/view/Style/Typography.qml index 0e0a8bf5f..97cd15e32 100644 --- a/Linphone/view/Style/Typography.qml +++ b/Linphone/view/Style/Typography.qml @@ -81,6 +81,14 @@ QtObject { weight: Font.Normal }) + // Text/P1i - Paragraph text Italic + property font p1i: Qt.font( { + family: DefaultStyle.defaultFont, + pixelSize: Utils.getSizeWithScreenRatio(14), + weight: Font.Normal, + italic: true + }) + // Text/P1s - Paragraph text property font p1s: Qt.font( { family: DefaultStyle.defaultFont,