diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index c5015a1d..f4e9f70a 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -99,6 +99,8 @@ ChatCore::ChatCore(const std::shared_ptr &chatRoom) : QObjec connect(this, &ChatCore::eventListChanged, this, &ChatCore::lUpdateLastMessage); connect(this, &ChatCore::eventsInserted, this, &ChatCore::lUpdateLastMessage); connect(this, &ChatCore::eventRemoved, this, &ChatCore::lUpdateLastMessage); + mEphemeralEnabled = chatRoom->ephemeralEnabled(); + mIsMuted = chatRoom->getMuted(); } ChatCore::~ChatCore() { @@ -228,6 +230,29 @@ void ChatCore::setSelf(QSharedPointer me) { setComposingAddress(address); }); }); + mChatModelConnection->makeConnectToCore(&ChatCore::lSetMuted, [this](bool muted) { + mChatModelConnection->invokeToModel([this, muted]() { mChatModel->setMuted(muted); }); + }); + mChatModelConnection->makeConnectToModel(&ChatModel::mutedChanged, [this](bool muted) { + mChatModelConnection->invokeToCore([this, muted]() { + if (mIsMuted != muted) { + mIsMuted = muted; + emit mutedChanged(); + } + }); + }); + + mChatModelConnection->makeConnectToCore(&ChatCore::lEnableEphemeral, [this](bool enable) { + mChatModelConnection->invokeToModel([this, enable]() { mChatModel->enableEphemeral(enable); }); + }); + mChatModelConnection->makeConnectToModel(&ChatModel::ephemeralEnableChanged, [this](bool enable) { + mChatModelConnection->invokeToCore([this, enable]() { + if (mEphemeralEnabled != enable) { + mEphemeralEnabled = enable; + emit ephemeralEnabledChanged(); + } + }); + }); } QDateTime ChatCore::getLastUpdatedTime() const { @@ -414,3 +439,11 @@ QString ChatCore::getComposingAddress() const { std::shared_ptr ChatCore::getModel() const { return mChatModel; } + +bool ChatCore::isMuted() const { + return mIsMuted; +} + +bool ChatCore::isEphemeralEnabled() const { + return mEphemeralEnabled; +} diff --git a/Linphone/core/chat/ChatCore.hpp b/Linphone/core/chat/ChatCore.hpp index 89dde0f6..65c20323 100644 --- a/Linphone/core/chat/ChatCore.hpp +++ b/Linphone/core/chat/ChatCore.hpp @@ -55,6 +55,8 @@ public: Q_PROPERTY(bool isEncrypted READ isEncrypted CONSTANT) Q_PROPERTY(bool isReadOnly READ getIsReadOnly WRITE setIsReadOnly NOTIFY readOnlyChanged) Q_PROPERTY(QString sendingText READ getSendingText WRITE setSendingText NOTIFY sendingTextChanged) + Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE lEnableEphemeral NOTIFY ephemeralEnabledChanged) + Q_PROPERTY(bool muted READ isMuted WRITE lSetMuted NOTIFY mutedChanged) // Should be call from model Thread. Will be automatically in App thread after initialization static QSharedPointer create(const std::shared_ptr &chatRoom); @@ -72,6 +74,10 @@ public: bool isEncrypted() const; + bool isMuted() const; + + bool isEphemeralEnabled() const; + QString getIdentifier() const; QString getSendingText() const; @@ -130,6 +136,8 @@ signals: void chatRoomStateChanged(); void readOnlyChanged(); void sendingTextChanged(QString text); + void mutedChanged(); + void ephemeralEnabledChanged(); void lDeleteMessage(); void lDelete(); @@ -141,6 +149,8 @@ signals: void lSendTextMessage(QString message); void lCompose(); void lLeave(); + void lSetMuted(bool muted); + void lEnableEphemeral(bool enable); private: QString id; @@ -157,6 +167,8 @@ private: bool mIsGroupChat = false; bool mIsEncrypted = false; bool mIsReadOnly = false; + bool mEphemeralEnabled = false; + bool mIsMuted = false; LinphoneEnums::ChatRoomState mChatRoomState; std::shared_ptr mChatModel; QSharedPointer mLastMessage; diff --git a/Linphone/core/chat/message/ChatMessageCore.cpp b/Linphone/core/chat/message/ChatMessageCore.cpp index f5f88faf..6e8094d1 100644 --- a/Linphone/core/chat/message/ChatMessageCore.cpp +++ b/Linphone/core/chat/message/ChatMessageCore.cpp @@ -72,9 +72,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr &c mUtf8Text = mChatMessageModel->getUtf8Text(); mHasTextContent = mChatMessageModel->getHasTextContent(); mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime()); - auto from = chatmessage->getFromAddress(); - auto to = chatmessage->getLocalAddress(); - mIsRemoteMessage = !from->weakEqual(to); + mIsRemoteMessage = !chatmessage->isOutgoing(); mPeerAddress = Utils::coreStringToAppString(chatmessage->getPeerAddress()->asStringUriOnly()); mPeerName = ToolModel::getDisplayName(chatmessage->getPeerAddress()->clone()); auto fromAddress = chatmessage->getFromAddress()->clone(); @@ -122,6 +120,14 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr &c } } connect(this, &ChatMessageCore::messageReactionChanged, this, &ChatMessageCore::resetReactionsSingleton); + + mIsForward = chatmessage->isForward(); + mIsReply = chatmessage->isReply(); + for (auto &content : chatmessage->getContents()) { + if (content->isFile() && !content->isVoiceRecording()) mHasFileContent = true; + if (content->isIcalendar()) mIsCalendarInvite = true; + if (content->isVoiceRecording()) mIsVoiceRecording = true; + } } ChatMessageCore::~ChatMessageCore() { diff --git a/Linphone/core/chat/message/ChatMessageCore.hpp b/Linphone/core/chat/message/ChatMessageCore.hpp index 02ca1878..4d408a1b 100644 --- a/Linphone/core/chat/message/ChatMessageCore.hpp +++ b/Linphone/core/chat/message/ChatMessageCore.hpp @@ -71,6 +71,11 @@ class ChatMessageCore : public QObject, public AbstractObject { Q_PROPERTY(QString ownReaction READ getOwnReaction WRITE setOwnReaction NOTIFY messageReactionChanged) Q_PROPERTY(QList reactions READ getReactions WRITE setReactions NOTIFY messageReactionChanged) Q_PROPERTY(QList reactionsSingleton READ getReactionsSingleton NOTIFY singletonReactionMapChanged) + Q_PROPERTY(bool isForward MEMBER mIsForward CONSTANT) + Q_PROPERTY(bool isReply MEMBER mIsReply CONSTANT) + Q_PROPERTY(bool hasFileContent MEMBER mHasFileContent CONSTANT) + Q_PROPERTY(bool isVoiceRecording MEMBER mIsVoiceRecording CONSTANT) + Q_PROPERTY(bool isCalendarInvite MEMBER mIsCalendarInvite CONSTANT) public: static QSharedPointer create(const std::shared_ptr &chatmessage); @@ -147,6 +152,12 @@ private: bool mIsRemoteMessage = false; bool mIsFromChatGroup = false; bool mIsRead = false; + bool mIsForward = false; + bool mIsReply = false; + bool mHasFileContent = false; + bool mIsCalendarInvite = false; + bool mIsVoiceRecording = false; + LinphoneEnums::ChatMessageState mMessageState; QSharedPointer mConferenceInfo = nullptr; diff --git a/Linphone/data/image/forward.svg b/Linphone/data/image/forward.svg new file mode 100644 index 00000000..cf198662 --- /dev/null +++ b/Linphone/data/image/forward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Linphone/data/image/reply.svg b/Linphone/data/image/reply.svg new file mode 100644 index 00000000..27291b81 --- /dev/null +++ b/Linphone/data/image/reply.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Linphone/model/chat/ChatModel.cpp b/Linphone/model/chat/ChatModel.cpp index 5e1d1545..0d503ef4 100644 --- a/Linphone/model/chat/ChatModel.cpp +++ b/Linphone/model/chat/ChatModel.cpp @@ -104,6 +104,16 @@ void ChatModel::markAsRead() { emit messagesRead(); } +void ChatModel::setMuted(bool muted) { + mMonitor->setMuted(muted); + emit mutedChanged(muted); +} + +void ChatModel::enableEphemeral(bool enable) { + mMonitor->enableEphemeral(enable); + emit ephemeralEnableChanged(enable); +} + void ChatModel::deleteHistory() { mMonitor->deleteHistory(); emit historyDeleted(); diff --git a/Linphone/model/chat/ChatModel.hpp b/Linphone/model/chat/ChatModel.hpp index 18c76f7a..779c8aa0 100644 --- a/Linphone/model/chat/ChatModel.hpp +++ b/Linphone/model/chat/ChatModel.hpp @@ -51,11 +51,16 @@ public: std::shared_ptr createTextMessageFromText(QString text); void compose(); linphone::ChatRoom::State getState() const; + void setMuted(bool muted); + void enableEphemeral(bool enable); + signals: void historyDeleted(); void messagesRead(); void deleted(); + void mutedChanged(bool muted); + void ephemeralEnableChanged(bool enable); private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/view/Control/Display/Chat/ChatListView.qml b/Linphone/view/Control/Display/Chat/ChatListView.qml index 93ba03db..f5a2b4bc 100644 --- a/Linphone/view/Control/Display/Chat/ChatListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatListView.qml @@ -171,7 +171,7 @@ ListView { property var contactObj: UtilsCpp.findFriendByAddress(modelData.core.peerAddress) contact: contactObj?.value || null displayNameVal: contact ? "" : modelData.core.avatarUri - // secured: securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified + secured: modelData.core.isEncrypted Layout.preferredWidth: Math.round(45 * DefaultStyle.dp) Layout.preferredHeight: Math.round(45 * DefaultStyle.dp) // isConference: modelData.core.isConference @@ -194,32 +194,84 @@ ListView { capitalization: Font.Capitalize } } - Text { - Layout.fillWidth: true - maximumLineCount: 1 - visible: !remoteComposingInfo.visible - text: modelData.core.lastMessageText - color: DefaultStyle.main2_400 - font { - pixelSize: Typography.p1.pixelSize - weight: unreadCount.unread > 0 ? Typography.p2.weight : Typography.p1.weight - } - } - Text { - id: remoteComposingInfo - visible: mainItem.currentIndex !== model.index && (modelData.core.composingName !== "" || modelData.core.sendingText !== "") - Layout.fillWidth: true - maximumLineCount: 1 - font { - pixelSize: Typography.p3.pixelSize - weight: Typography.p3.weight - } - //: %1 is writing… - text: modelData.core.composingName !== "" - ? qsTr("chat_message_is_writing_info").arg(modelData.core.composingName) - : modelData.core.sendingText !== "" - ? qsTr("chat_message_draft_sending_text").arg(modelData.core.sendingText) - : "" + + RowLayout { + spacing: Math.round(5 * DefaultStyle.dp) + Layout.fillWidth: true + + EffectImage { + visible: modelData != undefined && modelData.core.lastMessage && modelData.core.lastMessage.core.isReply && !remoteComposingInfo.visible + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.reply + colorizationColor: DefaultStyle.main2_500 + Layout.preferredHeight: Math.round(14 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(14 * DefaultStyle.dp) + } + + EffectImage { + visible: modelData != undefined && modelData.core.lastMessage && modelData.core.lastMessage.core.isForward && !remoteComposingInfo.visible + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.forward + colorizationColor: DefaultStyle.main2_500 + Layout.preferredHeight: Math.round(14 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(14 * DefaultStyle.dp) + } + + EffectImage { + visible: modelData != undefined && modelData.core.lastMessage && modelData.core.lastMessage.core.hasFileContent && !remoteComposingInfo.visible + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.paperclip + colorizationColor: DefaultStyle.main2_500 + Layout.preferredHeight: Math.round(14 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(14 * DefaultStyle.dp) + } + + EffectImage { + visible: modelData != undefined && modelData.core.lastMessage && modelData.core.lastMessage.core.isVoiceRecording && !remoteComposingInfo.visible + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.waveform + colorizationColor: DefaultStyle.main2_500 + Layout.preferredHeight: Math.round(14 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(14 * DefaultStyle.dp) + } + + EffectImage { + visible: modelData != undefined && modelData.core.lastMessage && modelData.core.lastMessage.core.isCalendarInvite && !remoteComposingInfo.visible + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.calendar + colorizationColor: DefaultStyle.main2_500 + Layout.preferredHeight: Math.round(14 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(14 * DefaultStyle.dp) + } + + Text { + id: lastMessageText + Layout.fillWidth: true + maximumLineCount: 1 + visible: !remoteComposingInfo.visible + text: modelData.core.lastMessageText + color: DefaultStyle.main2_400 + font { + pixelSize: Typography.p1.pixelSize + weight: unreadCount.unread > 0 ? Typography.p2.weight : Typography.p1.weight + } + } + Text { + id: remoteComposingInfo + visible: (modelData.core.composingName !== "" || modelData.core.sendingText !== "") + Layout.fillWidth: true + maximumLineCount: 1 + font { + pixelSize: Typography.p3.pixelSize + weight: Typography.p3.weight + } + //: %1 is writing… + text: modelData.core.composingName !== "" + ? qsTr("chat_message_is_writing_info").arg(modelData.core.composingName) + : modelData.core.sendingText !== "" + ? qsTr("chat_message_draft_sending_text").arg(modelData.core.sendingText) + : "" + } } } ColumnLayout { @@ -240,14 +292,27 @@ ListView { RowLayout { spacing: Math.round(10 * DefaultStyle.dp) Item {Layout.fillWidth: true} - //sourdine, éphémère - UnreadNotification { + EffectImage { + visible: modelData?.core.ephemeralEnabled + Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 + Layout.preferredHeight: 14 * DefaultStyle.dp + colorizationColor: DefaultStyle.main2_400 + imageSource: AppIcons.clockCountDown + } + EffectImage { + visible: modelData != undefined && modelData?.core.muted + Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 + Layout.preferredHeight: 14 * DefaultStyle.dp + colorizationColor: DefaultStyle.main2_400 + imageSource: AppIcons.bellSlash + } + UnreadNotification { id: unreadCount unread: modelData.core.unreadMessagesCount } EffectImage { - visible: modelData?.core.lastEvent && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle - && !modelData.core.lastMessage.core.isRemoteMessage + visible: modelData != undefined && lastMessageText.visible && modelData?.core.lastMessage && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle + && !modelData?.core.lastMessage.core.isRemoteMessage Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 Layout.preferredHeight: 14 * DefaultStyle.dp colorizationColor: DefaultStyle.main1_500_main diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index ce262250..107e9202 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -139,4 +139,9 @@ QtObject { property string presenceDoNotDisturb: "image://internal/presence_do_not_disturb.svg" property string presenceOffline: "image://internal/presence_offline.svg" property string presenceNote: "image://internal/presence-note.svg" + property string clockCountDown: "image://internal/clock-countdown.svg" + property string reply: "image://internal/reply.svg" + property string forward: "image://internal/forward.svg" + + }