From 9b677947522d6ee0271cddcf3b41def97a37bdca Mon Sep 17 00:00:00 2001 From: Gaelle Braud Date: Mon, 12 May 2025 16:01:00 +0200 Subject: [PATCH] imdn on each chat message imdn on chat last message change message notification ui press enter to send message --- Linphone/core/chat/ChatCore.cpp | 61 ++++++--- Linphone/core/chat/ChatCore.hpp | 20 ++- Linphone/core/chat/ChatList.cpp | 29 +++- Linphone/core/chat/ChatList.hpp | 1 + Linphone/core/chat/ChatProxy.cpp | 2 +- .../core/chat/message/ChatMessageCore.cpp | 20 +++ .../core/chat/message/ChatMessageCore.hpp | 8 ++ Linphone/model/chat/ChatModel.cpp | 23 +--- Linphone/model/chat/ChatModel.hpp | 3 +- .../model/chat/message/ChatMessageModel.cpp | 93 +++++++++++-- .../model/chat/message/ChatMessageModel.hpp | 66 ++++++++- Linphone/tool/Utils.cpp | 4 + Linphone/tool/Utils.hpp | 1 + .../Control/Display/Chat/ChatListView.qml | 49 +++++-- .../view/Control/Display/Chat/ChatMessage.qml | 39 ++++-- .../Display/Chat/ChatMessagesListView.qml | 7 + Linphone/view/Control/Popup/DesktopPopup.qml | 104 +-------------- .../Notification/NotificationReceivedCall.qml | 2 +- .../NotificationReceivedMessage.qml | 125 ++++++++++-------- .../view/Page/Form/Chat/SelectedChatView.qml | 14 ++ Linphone/view/Page/Main/Chat/ChatPage.qml | 2 +- Linphone/view/Style/AppIcons.qml | 2 + 22 files changed, 427 insertions(+), 248 deletions(-) diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index 78101f695..6bfc12d50 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -69,18 +69,22 @@ ChatCore::ChatCore(const std::shared_ptr &chatRoom) : QObjec }); mChatModel = Utils::makeQObject_ptr(chatRoom); mChatModel->setSelf(mChatModel); - mLastMessageInHistory = mChatModel->getLastMessageInHistory(); + auto lastMessage = chatRoom->getLastMessageInHistory(); + mLastMessage = lastMessage ? ChatMessageCore::create(lastMessage) : nullptr; auto history = chatRoom->getHistory(0, (int)linphone::ChatRoom::HistoryFilter::ChatMessage); - std::list> linHistory; + std::list> lHistory; for (auto &eventLog : history) { - if (eventLog->getChatMessage()) linHistory.push_back(eventLog->getChatMessage()); + if (eventLog->getChatMessage()) lHistory.push_back(eventLog->getChatMessage()); } - for (auto &message : linHistory) { + for (auto &message : lHistory) { if (!message) continue; auto chatMessage = ChatMessageCore::create(message); mChatMessageList.append(chatMessage); } mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier()); + connect(this, &ChatCore::messageListChanged, this, &ChatCore::lUpdateLastMessage); + connect(this, &ChatCore::messagesInserted, this, &ChatCore::lUpdateLastMessage); + connect(this, &ChatCore::messageRemoved, this, &ChatCore::lUpdateLastMessage); } ChatCore::~ChatCore() { @@ -123,16 +127,14 @@ void ChatCore::setSelf(QSharedPointer me) { [this](const std::shared_ptr &chatRoom, const std::shared_ptr &eventLog) { if (mChatModel->getMonitor() != chatRoom) return; - emit lUpdateLastMessage(); - emit lUpdateUnreadCount(); - emit lUpdateLastUpdatedTime(); auto message = eventLog->getChatMessage(); qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle(); if (message) { auto newMessage = ChatMessageCore::create(message); mChatModelConnection->invokeToCore([this, newMessage]() { - qDebug() << log().arg("append message to chatRoom") << this; appendMessageToMessageList(newMessage); + emit lUpdateUnreadCount(); + emit lUpdateLastUpdatedTime(); }); } }); @@ -140,9 +142,6 @@ void ChatCore::setSelf(QSharedPointer me) { &ChatModel::chatMessagesReceived, [this](const std::shared_ptr &chatRoom, const std::list> &chatMessages) { if (mChatModel->getMonitor() != chatRoom) return; - emit lUpdateLastMessage(); - emit lUpdateUnreadCount(); - emit lUpdateLastUpdatedTime(); qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle(); QList> list; for (auto &m : chatMessages) { @@ -153,8 +152,9 @@ void ChatCore::setSelf(QSharedPointer me) { } } mChatModelConnection->invokeToCore([this, list]() { - qDebug() << log().arg("append messages to chatRoom") << this; appendMessagesToMessageList(list); + emit lUpdateUnreadCount(); + emit lUpdateLastUpdatedTime(); }); }); @@ -167,12 +167,17 @@ void ChatCore::setSelf(QSharedPointer me) { }); mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateLastMessage, [this]() { - mChatModelConnection->invokeToModel([this]() { - auto message = mChatModel->getLastMessageInHistory(); - mChatModelConnection->invokeToCore([this, message]() { setLastMessageInHistory(message); }); + auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr; + mChatModelConnection->invokeToModel([this, lastMessageModel]() { + auto linphoneMessage = mChatModel->getLastChatMessage(); + if (lastMessageModel && lastMessageModel->getMonitor() != linphoneMessage) { + auto chatMessageCore = ChatMessageCore::create(linphoneMessage); + mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); }); + } }); }); mChatModelConnection->makeConnectToCore(&ChatCore::lSendTextMessage, [this](QString message) { + if (Utils::isEmptyMessage(message)) return; mChatModelConnection->invokeToModel([this, message]() { auto linMessage = mChatModel->createTextMessageFromText(message); linMessage->send(); @@ -253,14 +258,28 @@ void ChatCore::setAvatarUri(QString avatarUri) { } } -QString ChatCore::getLastMessageInHistory() const { - return mLastMessageInHistory; +QString ChatCore::getLastMessageText() const { + return mLastMessage ? mLastMessage->getText() : QString(); } -void ChatCore::setLastMessageInHistory(QString lastMessageInHistory) { - if (mLastMessageInHistory != lastMessageInHistory) { - mLastMessageInHistory = lastMessageInHistory; - emit lastMessageInHistoryChanged(lastMessageInHistory); +LinphoneEnums::ChatMessageState ChatCore::getLastMessageState() const { + return mLastMessage ? mLastMessage->getMessageState() : LinphoneEnums::ChatMessageState::StateIdle; +} + +ChatMessageGui *ChatCore::getLastMessage() const { + return mLastMessage ? new ChatMessageGui(mLastMessage) : nullptr; +} + +QSharedPointer ChatCore::getLastMessageCore() const { + return mLastMessage; +} + +void ChatCore::setLastMessage(QSharedPointer lastMessage) { + if (mLastMessage != lastMessage) { + disconnect(mLastMessage.get()); + mLastMessage = lastMessage; + connect(mLastMessage.get(), &ChatMessageCore::messageStateChanged, this, &ChatCore::lastMessageChanged); + emit lastMessageChanged(); } } diff --git a/Linphone/core/chat/ChatCore.hpp b/Linphone/core/chat/ChatCore.hpp index 296ab5c5a..ccd6f029b 100644 --- a/Linphone/core/chat/ChatCore.hpp +++ b/Linphone/core/chat/ChatCore.hpp @@ -21,7 +21,7 @@ #ifndef CHAT_CORE_H_ #define CHAT_CORE_H_ -#include "core/chat/message/ChatMessageCore.hpp" +#include "core/chat/message/ChatMessageGui.hpp" #include "model/chat/ChatModel.hpp" #include "model/search/MagicSearchModel.hpp" #include "tool/LinphoneEnums.hpp" @@ -39,8 +39,9 @@ public: Q_PROPERTY(QString peerAddress READ getPeerAddress WRITE setPeerAddress NOTIFY peerAddressChanged) Q_PROPERTY(QString avatarUri READ getAvatarUri WRITE setAvatarUri NOTIFY avatarUriChanged) Q_PROPERTY(QDateTime lastUpdatedTime READ getLastUpdatedTime WRITE setLastUpdatedTime NOTIFY lastUpdatedTimeChanged) - Q_PROPERTY(QString lastMessageInHistory READ getLastMessageInHistory WRITE setLastMessageInHistory NOTIFY - lastMessageInHistoryChanged) + Q_PROPERTY(QString lastMessageText READ getLastMessageText NOTIFY lastMessageChanged) + Q_PROPERTY(ChatMessageGui *lastMessage READ getLastMessage NOTIFY lastMessageChanged) + Q_PROPERTY(LinphoneEnums::ChatMessageState lastMessageState READ getLastMessageState NOTIFY lastMessageChanged) Q_PROPERTY(int unreadMessagesCount READ getUnreadMessagesCount WRITE setUnreadMessagesCount NOTIFY unreadMessagesCountChanged) Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged) @@ -61,8 +62,13 @@ public: QString getIdentifier() const; - QString getLastMessageInHistory() const; - void setLastMessageInHistory(QString message); + ChatMessageGui *getLastMessage() const; + QString getLastMessageText() const; + + LinphoneEnums::ChatMessageState getLastMessageState() const; + + QSharedPointer getLastMessageCore() const; + void setLastMessage(QSharedPointer lastMessage); int getUnreadMessagesCount() const; void setUnreadMessagesCount(int count); @@ -89,7 +95,7 @@ public: signals: void lastUpdatedTimeChanged(QDateTime time); - void lastMessageInHistoryChanged(QString time); + void lastMessageChanged(); void titleChanged(QString title); void peerAddressChanged(QString address); void unreadMessagesCountChanged(int count); @@ -113,7 +119,6 @@ signals: private: QString id; QDateTime mLastUpdatedTime; - QString mLastMessageInHistory; QString mPeerAddress; QString mTitle; QString mIdentifier; @@ -122,6 +127,7 @@ private: QString mComposingName; QString mComposingAddress; std::shared_ptr mChatModel; + QSharedPointer mLastMessage; QList> mChatMessageList; QSharedPointer> mChatModelConnection; diff --git a/Linphone/core/chat/ChatList.cpp b/Linphone/core/chat/ChatList.cpp index 07f3726f5..bbf49d759 100644 --- a/Linphone/core/chat/ChatList.cpp +++ b/Linphone/core/chat/ChatList.cpp @@ -52,6 +52,25 @@ ChatList::~ChatList() { mModelConnection = nullptr; } +void ChatList::connectItem(QSharedPointer chat) { + connect(chat.get(), &ChatCore::deleted, this, [this, chat] { + remove(chat); + // We cannot use countChanged here because it is called before mList + // really has removed the item, then emit specific signal + emit chatRemoved(chat ? new ChatGui(chat) : nullptr); + }); + auto dataChange = [this, chat] { + int i = -1; + get(chat.get(), &i); + if (i != -1) { + auto modelIndex = index(i); + emit dataChanged(modelIndex, modelIndex); + } + }; + connect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, dataChange); + connect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, dataChange); +} + void ChatList::setSelf(QSharedPointer me) { mModelConnection = SafeConnection::create(me, CoreModel::getInstance()); @@ -70,16 +89,12 @@ void ChatList::setSelf(QSharedPointer me) { for (auto &chat : getSharedList()) { if (chat) { disconnect(chat.get(), &ChatCore::deleted, this, nullptr); + disconnect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, nullptr); + disconnect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, nullptr); } } for (auto &chat : *chats) { - connect(chat.get(), &ChatCore::deleted, this, [this, chat] { - remove(chat); - // We cannot use countChanged here because it is called before mList - // really has removed the item, then emit specific signal - emit chatRemoved(chat ? new ChatGui(chat) : nullptr); - }); - connect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, [this] { emit chatUpdated(); }); + connectItem(chat); } mustBeInMainThread(getClassName()); resetData(*chats); diff --git a/Linphone/core/chat/ChatList.hpp b/Linphone/core/chat/ChatList.hpp index c37f1ce52..15a52b35a 100644 --- a/Linphone/core/chat/ChatList.hpp +++ b/Linphone/core/chat/ChatList.hpp @@ -39,6 +39,7 @@ public: ChatList(QObject *parent = Q_NULLPTR); ~ChatList(); void setSelf(QSharedPointer me); + void connectItem(QSharedPointer chat); int findChatIndex(ChatGui *chat); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/Linphone/core/chat/ChatProxy.cpp b/Linphone/core/chat/ChatProxy.cpp index 089b2d91a..a30a58bc1 100644 --- a/Linphone/core/chat/ChatProxy.cpp +++ b/Linphone/core/chat/ChatProxy.cpp @@ -45,9 +45,9 @@ void ChatProxy::setSourceModel(QAbstractItemModel *model) { [this, newChatList] { emit newChatList->filterChanged(getFilterText()); }); connect(newChatList, &ChatList::chatRemoved, this, &ChatProxy::chatRemoved); // connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); }); - connect(newChatList, &ChatList::chatUpdated, this, [this] { invalidate(); }); } auto firstList = new SortFilterList(model, Qt::AscendingOrder); + firstList->setDynamicSortFilter(true); setSourceModels(firstList); sort(0); } diff --git a/Linphone/core/chat/message/ChatMessageCore.cpp b/Linphone/core/chat/message/ChatMessageCore.cpp index b08fad041..7230e8267 100644 --- a/Linphone/core/chat/message/ChatMessageCore.cpp +++ b/Linphone/core/chat/message/ChatMessageCore.cpp @@ -54,6 +54,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr &c mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) && !chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne); mIsRead = chatmessage->isRead(); + mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState()); } ChatMessageCore::~ChatMessageCore() { @@ -72,6 +73,14 @@ void ChatMessageCore::setSelf(QSharedPointer me) { mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); }); }); mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() { setIsRead(true); }); + + mChatMessageModelConnection->makeConnectToModel( + &ChatMessageModel::msgStateChanged, + [this](const std::shared_ptr &message, linphone::ChatMessage::State state) { + if (mChatMessageModel->getMonitor() != message) return; + auto msgState = LinphoneEnums::fromLinphone(state); + mChatMessageModelConnection->invokeToCore([this, msgState] { setMessageState(msgState); }); + }); } QDateTime ChatMessageCore::getTimestamp() const { @@ -135,6 +144,17 @@ void ChatMessageCore::setIsRead(bool read) { } } +LinphoneEnums::ChatMessageState ChatMessageCore::getMessageState() const { + return mMessageState; +} + +void ChatMessageCore::setMessageState(LinphoneEnums::ChatMessageState state) { + if (mMessageState != state) { + mMessageState = state; + emit messageStateChanged(); + } +} + std::shared_ptr ChatMessageCore::getModel() const { return mChatMessageModel; } \ No newline at end of file diff --git a/Linphone/core/chat/message/ChatMessageCore.hpp b/Linphone/core/chat/message/ChatMessageCore.hpp index 79744bf89..70989d6c5 100644 --- a/Linphone/core/chat/message/ChatMessageCore.hpp +++ b/Linphone/core/chat/message/ChatMessageCore.hpp @@ -40,6 +40,8 @@ class ChatMessageCore : public QObject, public AbstractObject { Q_PROPERTY(QString toAddress READ getToAddress CONSTANT) Q_PROPERTY(QString peerName READ getPeerName CONSTANT) Q_PROPERTY(QString fromName READ getFromName CONSTANT) + Q_PROPERTY(LinphoneEnums::ChatMessageState messageState READ getMessageState WRITE setMessageState NOTIFY + messageStateChanged) Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT) Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT) Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged) @@ -68,6 +70,9 @@ public: bool isRead() const; void setIsRead(bool read); + LinphoneEnums::ChatMessageState getMessageState() const; + void setMessageState(LinphoneEnums::ChatMessageState state); + std::shared_ptr getModel() const; signals: @@ -75,6 +80,7 @@ signals: void textChanged(QString text); void isReadChanged(bool read); void isRemoteMessageChanged(bool isRemote); + void messageStateChanged(); void lDelete(); void deleted(); @@ -92,6 +98,8 @@ private: bool mIsRemoteMessage = false; bool mIsFromChatGroup = false; bool mIsRead = false; + LinphoneEnums::ChatMessageState mMessageState; + std::shared_ptr mChatMessageModel; QSharedPointer> mChatMessageModelConnection; }; diff --git a/Linphone/model/chat/ChatModel.cpp b/Linphone/model/chat/ChatModel.cpp index 5ec1f6348..4787fa0d1 100644 --- a/Linphone/model/chat/ChatModel.cpp +++ b/Linphone/model/chat/ChatModel.cpp @@ -88,23 +88,8 @@ QString ChatModel::getPeerAddress() const { return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly()); } -QString ChatModel::getLastMessageInHistory(std::list> startList) const { - if (startList.empty()) { - auto lastMessage = mMonitor->getLastMessageInHistory(); - if (lastMessage) startList = lastMessage->getContents(); - } - for (auto &content : startList) { - if (content->isText()) { - return Utils::coreStringToAppString(content->getUtf8Text()); - } else if (content->isFile()) { - return Utils::coreStringToAppString(content->getName()); - } else if (content->isIcalendar()) { - return QString("Invitation à une réunion"); - } else if (content->isMultipart()) { - return getLastMessageInHistory(content->getParts()); - } - } - return QString(""); +std::shared_ptr ChatModel::getLastChatMessage() { + return mMonitor->getLastMessageInHistory(); } int ChatModel::getUnreadMessagesCount() const { @@ -137,6 +122,10 @@ void ChatModel::compose() { mMonitor->compose(); } +linphone::ChatRoom::State ChatModel::getState() const { + return mMonitor->getState(); +} + //---------------------------------------------------------------// void ChatModel::onIsComposingReceived(const std::shared_ptr &chatRoom, diff --git a/Linphone/model/chat/ChatModel.hpp b/Linphone/model/chat/ChatModel.hpp index bc3cfac18..362dcfd49 100644 --- a/Linphone/model/chat/ChatModel.hpp +++ b/Linphone/model/chat/ChatModel.hpp @@ -40,7 +40,7 @@ public: QDateTime getLastUpdateTime(); QString getTitle(); QString getPeerAddress() const; - QString getLastMessageInHistory(std::list> startList = {}) const; + std::shared_ptr getLastChatMessage(); int getUnreadMessagesCount() const; void markAsRead(); std::list> getHistory() const; @@ -49,6 +49,7 @@ public: void deleteChatRoom(); std::shared_ptr createTextMessageFromText(QString text); void compose(); + linphone::ChatRoom::State getState() const; signals: void historyDeleted(); diff --git a/Linphone/model/chat/message/ChatMessageModel.cpp b/Linphone/model/chat/message/ChatMessageModel.cpp index 18cd64cb9..62d1600a2 100644 --- a/Linphone/model/chat/message/ChatMessageModel.cpp +++ b/Linphone/model/chat/message/ChatMessageModel.cpp @@ -60,14 +60,6 @@ QDateTime ChatMessageModel::getTimestamp() const { return QDateTime::fromSecsSinceEpoch(mMonitor->getTime()); } -std::shared_ptr -ChatMessageModel::onFileTransferSend(const std::shared_ptr &message, - const std::shared_ptr &content, - size_t offset, - size_t size) { - return nullptr; -} - bool ChatMessageModel::isRead() const { return mMonitor->isRead(); } @@ -85,3 +77,88 @@ void ChatMessageModel::deleteMessageFromChatRoom() { emit messageDeleted(); } } + +linphone::ChatMessage::State ChatMessageModel::getState() const { + return mMonitor->getState(); +} + +void ChatMessageModel::computeDeliveryStatus() { + // Read + for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::Displayed)) { + } + // Received + for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::DeliveredToUser)) { + } + // Sent + for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::Delivered)) { + } + // Error + for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::NotDelivered)) { + } +} + +void ChatMessageModel::onMsgStateChanged(const std::shared_ptr &message, + linphone::ChatMessage::State state) { + computeDeliveryStatus(); + emit msgStateChanged(message, state); +} + +void ChatMessageModel::onNewMessageReaction(const std::shared_ptr &message, + const std::shared_ptr &reaction) { + emit newMessageReaction(message, reaction); +} + +void ChatMessageModel::onReactionRemoved(const std::shared_ptr &message, + const std::shared_ptr &address) { + emit reactionRemoved(message, address); +} + +void ChatMessageModel::onFileTransferTerminated(const std::shared_ptr &message, + const std::shared_ptr &content) { + emit fileTransferTerminated(message, content); +} + +void ChatMessageModel::onFileTransferRecv(const std::shared_ptr &message, + const std::shared_ptr &content, + const std::shared_ptr &buffer) { + emit fileTransferRecv(message, content, buffer); +} + +std::shared_ptr +ChatMessageModel::onFileTransferSend(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t size) { + emit fileTransferSend(message, content, offset, size); + return nullptr; +} + +void ChatMessageModel::onFileTransferSendChunk(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t size, + const std::shared_ptr &buffer) { + emit fileTransferSendChunk(message, content, offset, size, buffer); +} + +void ChatMessageModel::onFileTransferProgressIndication(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t total) { + emit fileTransferProgressIndication(message, content, offset, total); +} + +void ChatMessageModel::onParticipantImdnStateChanged( + const std::shared_ptr &message, + const std::shared_ptr &state) { + computeDeliveryStatus(); + emit participantImdnStateChanged(message, state); +} + +void ChatMessageModel::onEphemeralMessageTimerStarted(const std::shared_ptr &message) { + emit ephemeralMessageTimerStarted(message); +} + +void ChatMessageModel::onEphemeralMessageDeleted(const std::shared_ptr &message) { + emit ephemeralMessageDeleted(message); +} diff --git a/Linphone/model/chat/message/ChatMessageModel.hpp b/Linphone/model/chat/message/ChatMessageModel.hpp index b01fbcf6a..384485f35 100644 --- a/Linphone/model/chat/message/ChatMessageModel.hpp +++ b/Linphone/model/chat/message/ChatMessageModel.hpp @@ -49,16 +49,74 @@ public: void deleteMessageFromChatRoom(); + void computeDeliveryStatus(); + + linphone::ChatMessage::State getState() const; + signals: void messageDeleted(); void messageRead(); + void msgStateChanged(const std::shared_ptr &message, linphone::ChatMessage::State state); + void newMessageReaction(const std::shared_ptr &message, + const std::shared_ptr &reaction); + void reactionRemoved(const std::shared_ptr &message, + const std::shared_ptr &address); + void fileTransferTerminated(const std::shared_ptr &message, + const std::shared_ptr &content); + void fileTransferRecv(const std::shared_ptr &message, + const std::shared_ptr &content, + const std::shared_ptr &buffer); + void fileTransferSend(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t size); + void fileTransferSendChunk(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t size, + const std::shared_ptr &buffer); + void fileTransferProgressIndication(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t total); + void participantImdnStateChanged(const std::shared_ptr &message, + const std::shared_ptr &state); + void ephemeralMessageTimerStarted(const std::shared_ptr &message); + void ephemeralMessageDeleted(const std::shared_ptr &message); + private: + linphone::ChatMessage::State mMessageState; + DECLARE_ABSTRACT_OBJECT - virtual std::shared_ptr onFileTransferSend(const std::shared_ptr &message, - const std::shared_ptr &content, - size_t offset, - size_t size) override; + + void onMsgStateChanged(const std::shared_ptr &message, linphone::ChatMessage::State state); + void onNewMessageReaction(const std::shared_ptr &message, + const std::shared_ptr &reaction); + void onReactionRemoved(const std::shared_ptr &message, + const std::shared_ptr &address); + void onFileTransferTerminated(const std::shared_ptr &message, + const std::shared_ptr &content); + void onFileTransferRecv(const std::shared_ptr &message, + const std::shared_ptr &content, + const std::shared_ptr &buffer); + std::shared_ptr onFileTransferSend(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t size); + void onFileTransferSendChunk(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t size, + const std::shared_ptr &buffer); + void onFileTransferProgressIndication(const std::shared_ptr &message, + const std::shared_ptr &content, + size_t offset, + size_t total); + void onParticipantImdnStateChanged(const std::shared_ptr &message, + const std::shared_ptr &state); + void onEphemeralMessageTimerStarted(const std::shared_ptr &message); + void onEphemeralMessageDeleted(const std::shared_ptr &message); }; #endif diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index ea797bdc8..b91406e7e 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -1582,6 +1582,10 @@ VariantObject *Utils::getChatForAddress(QString address) { return data; } +bool Utils::isEmptyMessage(QString message) { + return message.trimmed().isEmpty(); +} + // CLI void Utils::runCommandLine(const QString command) { diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 7e7748680..ecca7f307 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -148,6 +148,7 @@ public: Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call); Q_INVOKABLE static VariantObject *getChatForAddress(QString address); + Q_INVOKABLE static bool isEmptyMessage(QString message); // QDir findDirectoryByName(QString startPath, QString name); static QString getApplicationProduct(); diff --git a/Linphone/view/Control/Display/Chat/ChatListView.qml b/Linphone/view/Control/Display/Chat/ChatListView.qml index badad463b..c3000955d 100644 --- a/Linphone/view/Control/Display/Chat/ChatListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatListView.qml @@ -185,7 +185,7 @@ ListView { Text { Layout.fillWidth: true maximumLineCount: 1 - text: modelData.core.lastMessageInHistory + text: modelData.core.lastMessageText color: DefaultStyle.main2_400 font { pixelSize: Typography.p1.pixelSize @@ -194,25 +194,45 @@ ListView { } } - ColumnLayout { - Text { - color: DefaultStyle.main2_500main - text: UtilsCpp.formatDate(modelData.core.lastUpdatedTime, true, false) - font { - pixelSize: Typography.p3.pixelSize - weight: Typography.p3.weight - capitalization: Font.Capitalize + Layout.alignment: Qt.AlignRight + RowLayout { + Item{Layout.fillWidth: true} + Text { + color: DefaultStyle.main2_500main + text: UtilsCpp.formatDate(modelData.core.lastUpdatedTime, true, false) + font { + pixelSize: Typography.p3.pixelSize + weight: Typography.p3.weight + capitalization: Font.Capitalize + } } } RowLayout { + spacing: Math.round(10 * DefaultStyle.dp) Item {Layout.fillWidth: true} + //sourdine, éphémère UnreadNotification { id: unreadCount unread: modelData.core.unreadMessagesCount } - //sourdine, éphémère, IMDN + EffectImage { + 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 + imageSource: modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateDelivered + ? AppIcons.envelope + : modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateDeliveredToUser + ? AppIcons.check + : modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateNotDelivered + ? AppIcons.warningCircle + : modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateDisplayed + ? AppIcons.checks + : "" + } } } PopupButton { @@ -251,6 +271,7 @@ ListView { hoverEnabled: true anchors.fill: parent focus: true + acceptedButtons: Qt.RightButton | Qt.LeftButton onContainsMouseChanged: { if (containsMouse) mainItem.lastMouseContainsIndex = index @@ -265,8 +286,12 @@ ListView { visible: mainItem.lastMouseContainsIndex === index || mainItem.currentIndex === index } onPressed: { - mainItem.currentIndex = model.index - mainItem.forceActiveFocus() + if (pressedButtons & Qt.RightButton) { + chatroomPopup.open() + } else { + mainItem.currentIndex = model.index + mainItem.forceActiveFocus() + } } } } diff --git a/Linphone/view/Control/Display/Chat/ChatMessage.qml b/Linphone/view/Control/Display/Chat/ChatMessage.qml index 7abb04314..7707412e3 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessage.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessage.qml @@ -19,6 +19,8 @@ Control.Control { property string fromAddress: chatMessage? chatMessage.core.fromAddress : "" property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false + property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle + onMsgStateChanged: console.log("message state", msgState, chatMessage.core.text) hoverEnabled: true signal messageDeletionRequested() @@ -62,9 +64,9 @@ Control.Control { Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth) spacing: Math.round(2 * DefaultStyle.dp) topPadding: Math.round(12 * DefaultStyle.dp) - bottomPadding: Math.round(12 * DefaultStyle.dp) - leftPadding: Math.round(18 * DefaultStyle.dp) - rightPadding: Math.round(18 * DefaultStyle.dp) + bottomPadding: Math.round(6 * DefaultStyle.dp) + leftPadding: Math.round(12 * DefaultStyle.dp) + rightPadding: Math.round(12 * DefaultStyle.dp) MouseArea { anchors.fill: parent @@ -110,19 +112,38 @@ Control.Control { visible: modelData.core.text != undefined text: modelData.core.text Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: modelData.core.isRemoteMessage ? Text.AlignLeft : Text.AlignRight color: DefaultStyle.main2_700 font { pixelSize: Typography.p1.pixelSize weight: Typography.p1.weight } } - Text { + RowLayout { Layout.alignment: Qt.AlignRight - text: UtilsCpp.formatDate(modelData.core.timestamp, true, false) - color: DefaultStyle.main2_500main - font { - pixelSize: Typography.p3.pixelSize - weight: Typography.p3.weight + Text { + text: UtilsCpp.formatDate(modelData.core.timestamp, true, false) + color: DefaultStyle.main2_500main + font { + pixelSize: Typography.p3.pixelSize + weight: Typography.p3.weight + } + } + EffectImage { + visible: !mainItem.isRemoteMessage + Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 + Layout.preferredHeight: 14 * DefaultStyle.dp + colorizationColor: DefaultStyle.main1_500_main + imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered + ? AppIcons.envelope + : mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser + ? AppIcons.check + : mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered + ? AppIcons.warningCircle + : mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed + ? AppIcons.checks + : "" } } } diff --git a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml index 85b367008..47f7594f3 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml @@ -20,6 +20,13 @@ ListView { var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() } + + onCountChanged: if (atYEnd) { + var index = chatMessageProxy.findFirstUnreadIndex() + mainItem.positionViewAtIndex(index, ListView.End) + var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) + if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() + } Button { visible: !mainItem.atYEnd diff --git a/Linphone/view/Control/Popup/DesktopPopup.qml b/Linphone/view/Control/Popup/DesktopPopup.qml index c41a222e1..81f37524e 100644 --- a/Linphone/view/Control/Popup/DesktopPopup.qml +++ b/Linphone/view/Control/Popup/DesktopPopup.qml @@ -5,10 +5,8 @@ import QtQuick.Controls.Basic import QtQuick.Layouts import Qt.labs.platform - -// ============================================================================= Window { - id: mainItem + id: mainItem // --------------------------------------------------------------------------- @@ -17,8 +15,6 @@ Window { property bool requestActivate: false //property int flags: Qt.SplashScreen - - default property alias _content: content.data property bool _isOpen: false signal isOpened() @@ -89,100 +85,4 @@ Window { } ] */ -} - -/* -Item { - id: wrapper - objectName: '__internalWrapper' - - // --------------------------------------------------------------------------- - - property alias popupX: window.x - property alias popupY: window.y - property bool requestActivate: false - property int flags: Qt.SplashScreen - - readonly property alias popupWidth: window.width - readonly property alias popupHeight: window.height - - default property alias _content: content.data - property bool _isOpen: false - signal isOpened() - signal isClosed() - signal dataChanged() - - on_ContentChanged: dataChanged(_content) - // --------------------------------------------------------------------------- - - function open () { - _isOpen = true; - isOpened(); - } - - function close () { - _isOpen = false - isClosed() - } - - // --------------------------------------------------------------------------- - - // No size, no position. - height: 0 - width: 0 - visible:true - - Window { - id: window - objectName: '__internalWindow' - property bool isFrameLess : false; - property bool showAsTool : false - // Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool ensure to have the Window on Top and fullscreen independant - flags: Qt.BypassWindowManagerHint | (showAsTool?Qt.Tool:Qt.WindowStaysOnTopHint) | Qt.Window | Qt.FramelessWindowHint; - onXChanged: console.log(x) - opacity: 1.0 - height: _content[0] != null ? _content[0].height : 0 - width: _content[0] != null ? _content[0].width : 0 - visible:true - Item { - id: content - anchors.fill:parent - - property var $parent: wrapper - } - } - - // --------------------------------------------------------------------------- - - states: State { - name: 'opening' - when: _isOpen - - PropertyChanges { - opacity: 1.0 - target: window - } - } - - transitions: [ - Transition { - from: '' - to: 'opening' - ScriptAction { - script: { - if (wrapper.requestActivate) { - window.requestActivate() - } - } - } - }, - Transition { - from: '*' - to: '' - ScriptAction { - script: window.close() - } - } - ] -} -*/ +} \ No newline at end of file diff --git a/Linphone/view/Control/Popup/Notification/NotificationReceivedCall.qml b/Linphone/view/Control/Popup/Notification/NotificationReceivedCall.qml index 65d902f7f..a489d2e9b 100644 --- a/Linphone/view/Control/Popup/Notification/NotificationReceivedCall.qml +++ b/Linphone/view/Control/Popup/Notification/NotificationReceivedCall.qml @@ -8,7 +8,7 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle Notification { id: mainItem - radius: Math.round(20 * DefaultStyle.dp) + radius: Math.round(10 * DefaultStyle.dp) backgroundColor: DefaultStyle.grey_600 backgroundOpacity: 0.8 overriddenWidth: Math.round(400 * DefaultStyle.dp) diff --git a/Linphone/view/Control/Popup/Notification/NotificationReceivedMessage.qml b/Linphone/view/Control/Popup/Notification/NotificationReceivedMessage.qml index 3bed2be77..8444fba2d 100644 --- a/Linphone/view/Control/Popup/Notification/NotificationReceivedMessage.qml +++ b/Linphone/view/Control/Popup/Notification/NotificationReceivedMessage.qml @@ -9,7 +9,7 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle Notification { id: mainItem - radius: Math.round(20 * DefaultStyle.dp) + radius: Math.round(10 * DefaultStyle.dp) backgroundColor: DefaultStyle.grey_600 backgroundOpacity: 0.8 overriddenWidth: Math.round(400 * DefaultStyle.dp) @@ -26,14 +26,15 @@ Notification { width: parent.width leftPadding: Math.round(18 * DefaultStyle.dp) rightPadding: Math.round(18 * DefaultStyle.dp) - topPadding: Math.round(9 * DefaultStyle.dp) + topPadding: Math.round(32 * DefaultStyle.dp) bottomPadding: Math.round(18 * DefaultStyle.dp) - background: Item{} - contentItem: ColumnLayout { - spacing: Math.round(9 * DefaultStyle.dp) + background: Item { + anchors.fill: parent RowLayout { + anchors.top: parent.top + anchors.topMargin: Math.round(9 * DefaultStyle.dp) + anchors.horizontalCenter: parent.horizontalCenter spacing: Math.round(4 * DefaultStyle.dp) - Layout.alignment: Qt.AlignHCenter Image { Layout.preferredWidth: Math.round(12 * DefaultStyle.dp) Layout.preferredHeight: Math.round(12 * DefaultStyle.dp) @@ -49,65 +50,75 @@ Notification { } } } - ColumnLayout { - spacing: Math.round(14 * DefaultStyle.dp) - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - RowLayout { - spacing: Math.round(10 * DefaultStyle.dp) - Avatar { - Layout.preferredWidth: Math.round(45 * DefaultStyle.dp) - Layout.preferredHeight: Math.round(45 * DefaultStyle.dp) - Layout.alignment: Qt.AlignHCenter - property var contactObj: UtilsCpp.findFriendByAddress(mainItem.remoteAddress) - contact: contactObj?.value || null - displayNameVal: contact ? "" : mainItem.avatarUri - } - ColumnLayout { - spacing: 0 - Text { - text: mainItem.chatRoomName - color: DefaultStyle.main2_200 - Layout.fillWidth: true - maximumLineCount: 1 - font { - pixelSize: Typography.h3.pixelSize - weight: Typography.h3.weight - capitalization: Font.Capitalize - } - } - Text { - text: mainItem.remoteAddress - color: DefaultStyle.main2_100 - Layout.fillWidth: true - maximumLineCount: 1 - font { - pixelSize: Typography.p1.pixelSize - weight: Typography.p1.weight - } - } - } - Item{Layout.fillWidth: true} + Button { + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: Math.round(9 * DefaultStyle.dp) + anchors.rightMargin: Math.round(12 * DefaultStyle.dp) + padding: 0 + z: mousearea.z + 1 + background: Item{anchors.fill: parent} + icon.source: AppIcons.closeX + width: Math.round(14 * DefaultStyle.dp) + height: Math.round(14 * DefaultStyle.dp) + icon.width: Math.round(14 * DefaultStyle.dp) + icon.height: Math.round(14 * DefaultStyle.dp) + contentImageColor: DefaultStyle.grey_0 + onPressed: { + mainItem._close() } - Rectangle { - Layout.fillWidth: true + } + MouseArea { + id: mousearea + anchors.fill: parent + onClicked: { + console.log("notif clicked, open chat") + } + } + } + contentItem: ColumnLayout { + spacing: Math.round(9 * DefaultStyle.dp) + RowLayout { + spacing: Math.round(14 * DefaultStyle.dp) + Avatar { + Layout.preferredWidth: Math.round(60 * DefaultStyle.dp) Layout.preferredHeight: Math.round(60 * DefaultStyle.dp) - color: DefaultStyle.main2_400 - radius: Math.round(5 * DefaultStyle.dp) + // Layout.alignment: Qt.AlignHCenter + property var contactObj: UtilsCpp.findFriendByAddress(mainItem.remoteAddress) + contact: contactObj?.value || null + displayNameVal: contact ? "" : mainItem.avatarUri + } + ColumnLayout { + spacing: 0 + Text { + text: mainItem.chatRoomName + color: DefaultStyle.grey_100 + Layout.fillWidth: true + maximumLineCount: 1 + font { + pixelSize: Typography.h4.pixelSize + weight: Typography.h4.weight + capitalization: Font.Capitalize + } + } + Text { + text: mainItem.remoteAddress + color: DefaultStyle.main2_100 + Layout.fillWidth: true + maximumLineCount: 1 + font { + pixelSize: Typography.p4.pixelSize + weight: Typography.p4.weight + } + } Text { - anchors.fill: parent - anchors.leftMargin: 8 * DefaultStyle.dp - anchors.rightMargin: 8 * DefaultStyle.dp - anchors.topMargin: 8 * DefaultStyle.dp - anchors.bottomMargin: 8 * DefaultStyle.dp - verticalAlignment: Text.AlignVCenter text: mainItem.message + Layout.fillWidth: true maximumLineCount: 2 - color: DefaultStyle.grey_1000 + color: DefaultStyle.grey_300 font { pixelSize: Typography.p1s.pixelSize weight: Typography.p1s.weight - italic: true } } } diff --git a/Linphone/view/Page/Form/Chat/SelectedChatView.qml b/Linphone/view/Page/Form/Chat/SelectedChatView.qml index ed0517ef4..292fa032c 100644 --- a/Linphone/view/Page/Form/Chat/SelectedChatView.qml +++ b/Linphone/view/Page/Form/Chat/SelectedChatView.qml @@ -153,6 +153,10 @@ RowLayout { anchors.fill: parent radius: Math.round(35 * DefaultStyle.dp) color: DefaultStyle.grey_0 + MouseArea { + anchors.fill: parent + onPressed: sendingTextArea.forceActiveFocus() + } } contentItem: RowLayout { Flickable { @@ -204,6 +208,16 @@ RowLayout { if (previousText === "" && text !== "") { mainItem.chat.core.lCompose() } + previousText = text + } + Keys.onPressed: (event) => { + event.accepted = false + if (UtilsCpp.isEmptyMessage(sendingTextArea.text)) return + if (!(event.modifiers & Qt.ControlModifier) && (event.key == Qt.Key_Return || event.key == Qt.Key_Enter)) { + mainItem.chat.core.lSendTextMessage(sendingTextArea.text) + sendingTextArea.clear() + event.accepted = true + } } } } diff --git a/Linphone/view/Page/Main/Chat/ChatPage.qml b/Linphone/view/Page/Main/Chat/ChatPage.qml index e19c25eb3..ed0692b15 100644 --- a/Linphone/view/Page/Main/Chat/ChatPage.qml +++ b/Linphone/view/Page/Main/Chat/ChatPage.qml @@ -131,7 +131,7 @@ AbstractMainPage { anchors.fill: parent anchors.rightMargin: Math.round(39 * DefaultStyle.dp) Text { - visible: chatListView.count === 0 + visible: chatListView.count === 0 && chatListView.loading === false Layout.alignment: Qt.AlignHCenter Layout.topMargin: Math.round(137 * DefaultStyle.dp) //: "Aucun résultat…" diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index d3bbde49a..ce262250b 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -30,7 +30,9 @@ QtObject { property string profile: "image://internal/user-circle.svg" property string manageProfile: "image://internal/user-circle-gear.svg" property string verif_page_image: "image://internal/verif_page_image.svg" + property string envelope: "image://internal/envelope-simple.svg" property string check: "image://internal/check.svg" + property string checks: "image://internal/checks.svg" property string dialer: "image://internal/numpad.svg" property string chiffrement: "image://internal/chiffrement.svg" property string interoperable: "image://internal/interoperable.svg"