diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 5bb8642f1..6f18d7dd5 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -54,9 +54,10 @@ #include "core/call/CallProxy.hpp" #include "core/camera/CameraGui.hpp" #include "core/chat/ChatProxy.hpp" +#include "core/chat/message/EventLogGui.hpp" #include "core/chat/message/ChatMessageGui.hpp" -#include "core/chat/message/ChatMessageList.hpp" -#include "core/chat/message/ChatMessageProxy.hpp" +#include "core/chat/message/EventLogList.hpp" +#include "core/chat/message/EventLogProxy.hpp" #include "core/conference/ConferenceGui.hpp" #include "core/conference/ConferenceInfoGui.hpp" #include "core/conference/ConferenceInfoProxy.hpp" @@ -661,9 +662,10 @@ void App::initCppInterfaces() { qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatList"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatGui"); - qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatMessageGui"); - qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatMessageList"); - qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatMessageProxy"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "EventLogGui"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ChatMessageGui"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "EventLogList"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "EventLogProxy"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "ConferenceCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ConferenceGui"); diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index 4d9c9224a..e449cfba4 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -25,8 +25,10 @@ list(APPEND _LINPHONEAPP_SOURCES core/chat/ChatProxy.cpp core/chat/message/ChatMessageCore.cpp core/chat/message/ChatMessageGui.cpp - core/chat/message/ChatMessageList.cpp - core/chat/message/ChatMessageProxy.cpp + core/chat/message/EventLogCore.cpp + core/chat/message/EventLogGui.cpp + core/chat/message/EventLogList.cpp + core/chat/message/EventLogProxy.cpp core/emoji/EmojiModel.cpp core/fps-counter/FPSCounter.cpp core/friend/FriendCore.cpp diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index 333b4026d..c5015a1df 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -76,25 +76,29 @@ ChatCore::ChatCore(const std::shared_ptr &chatRoom) : QObjec mChatModel->setSelf(mChatModel); auto lastMessage = chatRoom->getLastMessageInHistory(); mLastMessage = lastMessage ? ChatMessageCore::create(lastMessage) : nullptr; - auto history = chatRoom->getHistory(0, (int)linphone::ChatRoom::HistoryFilter::ChatMessage); - std::list> lHistory; + + int filter = mIsGroupChat ? static_cast(linphone::ChatRoom::HistoryFilter::ChatMessage) | + static_cast(linphone::ChatRoom::HistoryFilter::InfoNoDevice) + : static_cast(linphone::ChatRoom::HistoryFilter::ChatMessage); + + auto history = chatRoom->getHistory(0, filter); + std::list> lHistory; for (auto &eventLog : history) { - if (eventLog->getChatMessage()) lHistory.push_back(eventLog->getChatMessage()); + lHistory.push_back(eventLog); } - QList> messageList; - for (auto &message : lHistory) { - if (!message) continue; - auto chatMessage = ChatMessageCore::create(message); - messageList.append(chatMessage); + QList> eventList; + for (auto &event : lHistory) { + auto eventLogCore = EventLogCore::create(event); + eventList.append(eventLogCore); } - resetChatMessageList(messageList); + resetEventLogList(eventList); mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier()); mChatRoomState = LinphoneEnums::fromLinphone(chatRoom->getState()); mIsEncrypted = chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Encrypted); mIsReadOnly = chatRoom->isReadOnly(); - connect(this, &ChatCore::messageListChanged, this, &ChatCore::lUpdateLastMessage); - connect(this, &ChatCore::messagesInserted, this, &ChatCore::lUpdateLastMessage); - connect(this, &ChatCore::messageRemoved, this, &ChatCore::lUpdateLastMessage); + connect(this, &ChatCore::eventListChanged, this, &ChatCore::lUpdateLastMessage); + connect(this, &ChatCore::eventsInserted, this, &ChatCore::lUpdateLastMessage); + connect(this, &ChatCore::eventRemoved, this, &ChatCore::lUpdateLastMessage); } ChatCore::~ChatCore() { @@ -112,7 +116,7 @@ void ChatCore::setSelf(QSharedPointer me) { &ChatCore::lLeave, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->leave(); }); }); mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() { mChatModelConnection->invokeToCore([this]() { - clearMessagesList(); + clearEventLogList(); //: Deleted Utils::showInformationPopup(tr("info_toast_deleted_title"), //: Message history has been deleted @@ -148,36 +152,30 @@ void ChatCore::setSelf(QSharedPointer me) { }); }); - mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, + mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, // TODO onNewEvent? [this](const std::shared_ptr &chatRoom, const std::shared_ptr &eventLog) { if (mChatModel->getMonitor() != chatRoom) return; - auto message = eventLog->getChatMessage(); qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle(); - if (message) { - auto newMessage = ChatMessageCore::create(message); - mChatModelConnection->invokeToCore([this, newMessage]() { - appendMessageToMessageList(newMessage); - emit lUpdateUnreadCount(); - emit lUpdateLastUpdatedTime(); - }); - } + auto event = EventLogCore::create(eventLog); + mChatModelConnection->invokeToCore([this, event]() { + appendEventLogToEventLogList(event); + emit lUpdateUnreadCount(); + emit lUpdateLastUpdatedTime(); + }); }); mChatModelConnection->makeConnectToModel( &ChatModel::chatMessagesReceived, [this](const std::shared_ptr &chatRoom, - const std::list> &chatMessages) { + const std::list> &eventsLog) { if (mChatModel->getMonitor() != chatRoom) return; qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle(); - QList> list; - for (auto &m : chatMessages) { - auto message = m->getChatMessage(); - if (message) { - auto newMessage = ChatMessageCore::create(message); - list.push_back(newMessage); - } + QList> list; + for (auto &e : eventsLog) { + auto event = EventLogCore::create(e); + list.push_back(event); } mChatModelConnection->invokeToCore([this, list]() { - appendMessagesToMessageList(list); + appendEventLogsToEventLogList(list); emit lUpdateUnreadCount(); emit lUpdateLastUpdatedTime(); }); @@ -211,11 +209,8 @@ void ChatCore::setSelf(QSharedPointer me) { mChatModelConnection->makeConnectToModel( &ChatModel::chatMessageSending, [this](const std::shared_ptr &chatRoom, const std::shared_ptr &eventLog) { - auto message = eventLog->getChatMessage(); - if (message) { - auto newMessage = ChatMessageCore::create(message); - mChatModelConnection->invokeToCore([this, newMessage]() { appendMessageToMessageList(newMessage); }); - } + auto event = EventLogCore::create(eventLog); + mChatModelConnection->invokeToCore([this, event]() { appendEventLogToEventLogList(event); }); }); mChatModelConnection->makeConnectToCore( &ChatCore::lCompose, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->compose(); }); }); @@ -333,10 +328,6 @@ 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()); @@ -357,45 +348,45 @@ void ChatCore::setUnreadMessagesCount(int count) { } } -QList> ChatCore::getChatMessageList() const { - return mChatMessageList; +QList> ChatCore::getEventLogList() const { + return mEventLogList; } -void ChatCore::resetChatMessageList(QList> list) { - mChatMessageList = list; - emit messageListChanged(); +void ChatCore::resetEventLogList(QList> list) { + mEventLogList = list; + emit eventListChanged(); } -void ChatCore::appendMessagesToMessageList(QList> list) { +void ChatCore::appendEventLogsToEventLogList(QList> list) { int nbAdded = 0; - for (auto &message : list) { - if (mChatMessageList.contains(message)) continue; - mChatMessageList.append(message); + for (auto &e : list) { + if (mEventLogList.contains(e)) continue; + mEventLogList.append(e); ++nbAdded; } - if (nbAdded > 0) emit messagesInserted(list); + if (nbAdded > 0) emit eventsInserted(list); } -void ChatCore::appendMessageToMessageList(QSharedPointer message) { - if (mChatMessageList.contains(message)) return; - mChatMessageList.append(message); - emit messagesInserted({message}); +void ChatCore::appendEventLogToEventLogList(QSharedPointer e) { + if (mEventLogList.contains(e)) return; + mEventLogList.append(e); + emit eventsInserted({e}); } -void ChatCore::removeMessagesFromMessageList(QList> list) { +void ChatCore::removeEventLogsFromEventLogList(QList> list) { int nbRemoved = 0; - for (auto &message : list) { - if (mChatMessageList.contains(message)) { - mChatMessageList.removeAll(message); + for (auto &e : list) { + if (mEventLogList.contains(e)) { + mEventLogList.removeAll(e); ++nbRemoved; } } - if (nbRemoved > 0) emit messageRemoved(); + if (nbRemoved > 0) emit eventRemoved(); } -void ChatCore::clearMessagesList() { - mChatMessageList.clear(); - emit messageListChanged(); +void ChatCore::clearEventLogList() { + mEventLogList.clear(); + emit eventListChanged(); } QString ChatCore::getComposingName() const { diff --git a/Linphone/core/chat/ChatCore.hpp b/Linphone/core/chat/ChatCore.hpp index 2773fa3c4..89dde0f65 100644 --- a/Linphone/core/chat/ChatCore.hpp +++ b/Linphone/core/chat/ChatCore.hpp @@ -21,7 +21,8 @@ #ifndef CHAT_CORE_H_ #define CHAT_CORE_H_ -#include "core/chat/message/ChatMessageGui.hpp" +#include "core/chat/message/EventLogGui.hpp" +#include "message/ChatMessageGui.hpp" #include "model/chat/ChatModel.hpp" #include "model/search/MagicSearchModel.hpp" #include "tool/LinphoneEnums.hpp" @@ -30,6 +31,8 @@ #include #include +class EventLogCore; + class ChatCore : public QObject, public AbstractObject { Q_OBJECT @@ -94,12 +97,12 @@ public: QString getChatRoomAddress() const; QString getPeerAddress() const; - QList> getChatMessageList() const; - void resetChatMessageList(QList> list); - void appendMessageToMessageList(QSharedPointer message); - void appendMessagesToMessageList(QList> list); - void removeMessagesFromMessageList(QList> list); - void clearMessagesList(); + QList> getEventLogList() const; + void resetEventLogList(QList> list); + void appendEventLogToEventLogList(QSharedPointer event); + void appendEventLogsToEventLogList(QList> list); + void removeEventLogsFromEventLogList(QList> list); + void clearEventLogList(); QString getAvatarUri() const; void setAvatarUri(QString avatarUri); @@ -118,9 +121,9 @@ signals: void lastMessageChanged(); void titleChanged(QString title); void unreadMessagesCountChanged(int count); - void messageListChanged(); - void messagesInserted(QList> list); - void messageRemoved(); + void eventListChanged(); + void eventsInserted(QList> list); + void eventRemoved(); void avatarUriChanged(); void deleted(); void composingUserChanged(); @@ -157,7 +160,7 @@ private: LinphoneEnums::ChatRoomState mChatRoomState; std::shared_ptr mChatModel; QSharedPointer mLastMessage; - QList> mChatMessageList; + QList> mEventLogList; QSharedPointer> mChatModelConnection; DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/core/chat/message/ChatMessageCore.hpp b/Linphone/core/chat/message/ChatMessageCore.hpp index 52257170b..02ca18787 100644 --- a/Linphone/core/chat/message/ChatMessageCore.hpp +++ b/Linphone/core/chat/message/ChatMessageCore.hpp @@ -21,6 +21,7 @@ #ifndef CHATMESSAGECORE_H_ #define CHATMESSAGECORE_H_ +#include "EventLogCore.hpp" #include "core/conference/ConferenceInfoCore.hpp" #include "core/conference/ConferenceInfoGui.hpp" #include "model/chat/message/ChatMessageModel.hpp" @@ -48,6 +49,7 @@ public: }; class ChatCore; +class EventLogCore; class ChatMessageCore : public QObject, public AbstractObject { Q_OBJECT diff --git a/Linphone/core/chat/message/ChatMessageGui.cpp b/Linphone/core/chat/message/ChatMessageGui.cpp index cbfbfa062..9ce909e23 100644 --- a/Linphone/core/chat/message/ChatMessageGui.cpp +++ b/Linphone/core/chat/message/ChatMessageGui.cpp @@ -19,6 +19,7 @@ */ #include "ChatMessageGui.hpp" +#include "ChatMessageCore.hpp" #include "core/App.hpp" DEFINE_ABSTRACT_OBJECT(ChatMessageGui) diff --git a/Linphone/core/chat/message/ChatMessageList.cpp b/Linphone/core/chat/message/ChatMessageList.cpp deleted file mode 100644 index 8d3ebfeb9..000000000 --- a/Linphone/core/chat/message/ChatMessageList.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2010-2024 Belledonne Communications SARL. - * - * This file is part of linphone-desktop - * (see https://www.linphone.org). - * - * 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 . - */ - -#include "ChatMessageList.hpp" -#include "ChatMessageCore.hpp" -#include "ChatMessageGui.hpp" -#include "core/App.hpp" -#include "core/chat/ChatCore.hpp" -#include "core/chat/ChatGui.hpp" - -#include -#include - -// ============================================================================= - -DEFINE_ABSTRACT_OBJECT(ChatMessageList) - -QSharedPointer ChatMessageList::create() { - auto model = QSharedPointer(new ChatMessageList(), &QObject::deleteLater); - model->moveToThread(App::getInstance()->thread()); - model->setSelf(model); - return model; -} - -QSharedPointer -ChatMessageList::createChatMessageCore(const std::shared_ptr &chatMessage) { - auto chatMessageCore = ChatMessageCore::create(chatMessage); - return chatMessageCore; -} - -ChatMessageList::ChatMessageList(QObject *parent) : ListProxy(parent) { - mustBeInMainThread(getClassName()); - App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); -} - -ChatMessageList::~ChatMessageList() { - mustBeInMainThread("~" + getClassName()); - mModelConnection = nullptr; -} - -ChatGui *ChatMessageList::getChat() const { - if (mChatCore) return new ChatGui(mChatCore); - else return nullptr; -} - -QSharedPointer ChatMessageList::getChatCore() const { - return mChatCore; -} - -void ChatMessageList::setChatCore(QSharedPointer core) { - if (mChatCore != core) { - if (mChatCore) disconnect(mChatCore.get(), &ChatCore::messageListChanged, this, nullptr); - mChatCore = core; - if (mChatCore) connect(mChatCore.get(), &ChatCore::messageListChanged, this, &ChatMessageList::lUpdate); - if (mChatCore) - connect(mChatCore.get(), &ChatCore::messagesInserted, this, - [this](QList> list) { - auto chatList = getSharedList(); - for (auto &message : list) { - auto it = std::find_if( - chatList.begin(), chatList.end(), - [message](const QSharedPointer item) { return item == message; }); - if (it == chatList.end()) { - add(message); - int index; - get(message.get(), &index); - emit messageInserted(index, new ChatMessageGui(message)); - } - } - }); - emit chatChanged(); - lUpdate(); - } -} - -void ChatMessageList::setChatGui(ChatGui *chat) { - auto chatCore = chat ? chat->mCore : nullptr; - setChatCore(chatCore); -} - -int ChatMessageList::findFirstUnreadIndex() { - auto chatList = getSharedList(); - auto it = std::find_if(chatList.begin(), chatList.end(), - [](const QSharedPointer item) { return !item->isRead(); }); - return it == chatList.end() ? -1 : std::distance(chatList.begin(), it); -} - -void ChatMessageList::setSelf(QSharedPointer me) { - mModelConnection = SafeConnection::create(me, CoreModel::getInstance()); - - mModelConnection->makeConnectToCore(&ChatMessageList::lUpdate, [this]() { - for (auto &message : getSharedList()) { - if (message) disconnect(message.get(), &ChatMessageCore::deleted, this, nullptr); - } - if (!mChatCore) return; - auto messages = mChatCore->getChatMessageList(); - for (auto &message : messages) { - connect(message.get(), &ChatMessageCore::deleted, this, [this, message] { - emit mChatCore->lUpdateLastMessage(); - remove(message); - }); - } - resetData(messages); - }); - - connect(this, &ChatMessageList::filterChanged, [this](QString filter) { - mFilter = filter; - lUpdate(); - }); - lUpdate(); -} - -QVariant ChatMessageList::data(const QModelIndex &index, int role) const { - int row = index.row(); - if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); - if (role == Qt::DisplayRole) - return QVariant::fromValue(new ChatMessageGui(mList[row].objectCast())); - return QVariant(); -} diff --git a/Linphone/core/chat/message/ChatMessageProxy.cpp b/Linphone/core/chat/message/ChatMessageProxy.cpp deleted file mode 100644 index 34f9e8648..000000000 --- a/Linphone/core/chat/message/ChatMessageProxy.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2010-2024 Belledonne Communications SARL. - * - * This file is part of linphone-desktop - * (see https://www.linphone.org). - * - * 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 . - */ - -#include "ChatMessageProxy.hpp" -#include "ChatMessageGui.hpp" -//#include "core/chat/ChatGui.hpp" -#include "core/App.hpp" - -DEFINE_ABSTRACT_OBJECT(ChatMessageProxy) - -ChatMessageProxy::ChatMessageProxy(QObject *parent) : LimitProxy(parent) { - mList = ChatMessageList::create(); - setSourceModel(mList.get()); -} - -ChatMessageProxy::~ChatMessageProxy() { -} - -void ChatMessageProxy::setSourceModel(QAbstractItemModel *model) { - auto oldChatMessageList = getListModel(); - if (oldChatMessageList) { - disconnect(oldChatMessageList); - } - auto newChatMessageList = dynamic_cast(model); - if (newChatMessageList) { - connect(newChatMessageList, &ChatMessageList::chatChanged, this, &ChatMessageProxy::chatChanged); - connect(newChatMessageList, &ChatMessageList::messageInserted, this, - [this, newChatMessageList](int index, ChatMessageGui *message) { - if (index != -1) { - index = dynamic_cast(sourceModel()) - ->mapFromSource(newChatMessageList->index(index, 0)) - .row(); - if (mMaxDisplayItems <= index) setMaxDisplayItems(index + mDisplayItemsStep); - } - emit messageInserted(index, message); - }); - } - setSourceModels(new SortFilterList(model)); - sort(0); -} - -ChatGui *ChatMessageProxy::getChatGui() { - auto model = getListModel(); - if (!mChatGui && model) mChatGui = model->getChat(); - return mChatGui; -} - -void ChatMessageProxy::setChatGui(ChatGui *chat) { - getListModel()->setChatGui(chat); -} - -ChatMessageGui *ChatMessageProxy::getChatMessageAtIndex(int i) { - auto model = getListModel(); - auto sourceIndex = mapToSource(index(i, 0)).row(); - if (model) { - auto chat = model->getAt(sourceIndex); - if (chat) return new ChatMessageGui(chat); - else return nullptr; - } - return nullptr; -} - -int ChatMessageProxy::findFirstUnreadIndex() { - auto chatMessageList = getListModel(); - if (chatMessageList) { - auto listIndex = chatMessageList->findFirstUnreadIndex(); - if (listIndex != -1) { - listIndex = dynamic_cast(sourceModel()) - ->mapFromSource(chatMessageList->index(listIndex, 0)) - .row(); - if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep); - return listIndex; - } else { - return std::max(0, getCount() - 1); - } - } - return std::max(0, getCount() - 1); -} - -bool ChatMessageProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - // auto l = getItemAtSource(sourceRow); - // return l != nullptr; - return true; -} - -bool ChatMessageProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { - auto l = getItemAtSource(sourceLeft.row()); - auto r = getItemAtSource(sourceRight.row()); - if (l && r) return l->getTimestamp() <= r->getTimestamp(); - else return true; -} diff --git a/Linphone/core/chat/message/EventLogCore.cpp b/Linphone/core/chat/message/EventLogCore.cpp new file mode 100644 index 000000000..5539942da --- /dev/null +++ b/Linphone/core/chat/message/EventLogCore.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "EventLogCore.hpp" +#include "core/App.hpp" +#include "core/chat/ChatCore.hpp" +#include "model/tool/ToolModel.hpp" + +DEFINE_ABSTRACT_OBJECT(EventLogCore) + +QSharedPointer EventLogCore::create(const std::shared_ptr &eventLog) { + auto sharedPointer = QSharedPointer(new EventLogCore(eventLog), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +EventLogCore::EventLogCore(const std::shared_ptr &eventLog) { + mustBeInLinphoneThread(getClassName()); + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mEventLogType = LinphoneEnums::fromLinphone(eventLog->getType()); + mTimestamp = QDateTime::fromMSecsSinceEpoch(eventLog->getCreationTime() * 1000); + if (eventLog->getChatMessage()) { + mChatMessageCore = ChatMessageCore::create(eventLog->getChatMessage()); + mEventId = eventLog->getChatMessage()->getMessageId(); + } else if (eventLog->getCallLog()) { + mCallHistoryCore = CallHistoryCore::create(eventLog->getCallLog()); + mEventId = eventLog->getCallLog()->getCallId(); + } else { + mEventId = eventLog->getNotifyId(); + computeEvent(eventLog); + } +} + +EventLogCore::~EventLogCore() { +} + +void EventLogCore::setSelf(QSharedPointer me) { +} + +std::string EventLogCore::getEventLogId() { + return mEventId; +} + +QSharedPointer EventLogCore::getChatMessageCore() { + return mChatMessageCore; +} +QSharedPointer EventLogCore::getCallHistoryCore() { + return mCallHistoryCore; +} + +ChatMessageCore *EventLogCore::getChatMessageCorePointer() { + return mChatMessageCore.get(); +} +CallHistoryCore *EventLogCore::getCallHistoryCorePointer() { + return mCallHistoryCore.get(); +} + +// Events (other than ChatMessage and CallLog which are handled in their respective Core) + +void EventLogCore::computeEvent(const std::shared_ptr &eventLog) { + mustBeInLinphoneThread(getClassName()); + mHandled = true; + mImportant = false; + auto participantAddress = eventLog->getParticipantAddress() ? eventLog->getParticipantAddress()->clone() : nullptr; + + switch (eventLog->getType()) { + case linphone::EventLog::Type::ConferenceCreated: + mEventDetails = tr("conference_created_event"); + break; + case linphone::EventLog::Type::ConferenceTerminated: + mEventDetails = tr("conference_created_terminated"); + mImportant = true; + break; + case linphone::EventLog::Type::ConferenceParticipantAdded: + mEventDetails = tr("conference_participant_added_event").arg(ToolModel::getDisplayName(participantAddress)); + break; + case linphone::EventLog::Type::ConferenceParticipantRemoved: + mEventDetails = + tr("conference_participant_removed_event").arg(ToolModel::getDisplayName(participantAddress)); + mImportant = true; + break; + case linphone::EventLog::Type::ConferenceSecurityEvent: { + if (eventLog->getSecurityEventType() == linphone::EventLog::SecurityEventType::SecurityLevelDowngraded) { + auto faultyParticipant = eventLog->getSecurityEventFaultyDeviceAddress() + ? eventLog->getSecurityEventFaultyDeviceAddress()->clone() + : nullptr; + if (faultyParticipant) + mEventDetails = tr("conference_security_event").arg(ToolModel::getDisplayName(faultyParticipant)); + else if (participantAddress) + mEventDetails = tr("conference_security_event").arg(ToolModel::getDisplayName(participantAddress)); + mImportant = true; + } else mHandled = false; + break; + } + case linphone::EventLog::Type::ConferenceEphemeralMessageEnabled: + mEventDetails = tr("conference_ephemeral_message_enabled_event") + .arg(getEphemeralFormatedTime(eventLog->getEphemeralMessageLifetime())); + break; + case linphone::EventLog::Type::ConferenceEphemeralMessageLifetimeChanged: + mEventDetails = tr("conference_ephemeral_message_lifetime_changed_event") + .arg(getEphemeralFormatedTime(eventLog->getEphemeralMessageLifetime())); + break; + case linphone::EventLog::Type::ConferenceEphemeralMessageDisabled: + mEventDetails = tr("conference_ephemeral_message_disabled_event"); + mImportant = true; + break; + case linphone::EventLog::Type::ConferenceSubjectChanged: + mEventDetails = tr("conference_subject_changed_event").arg(QString::fromStdString(eventLog->getSubject())); + break; + case linphone::EventLog::Type::ConferenceParticipantSetAdmin: + mEventDetails = + tr("conference_participant_unset_admin_event").arg(ToolModel::getDisplayName(participantAddress)); + break; + case linphone::EventLog::Type::ConferenceParticipantUnsetAdmin: + mEventDetails = + tr("conference_participant_set_admin_event").arg(ToolModel::getDisplayName(participantAddress)); + break; + default: + mHandled = false; + } +} + +QString EventLogCore::getEphemeralFormatedTime(int selectedTime) { + if (selectedTime == 60) return tr("nMinute", "", 1).arg(1); + else if (selectedTime == 3600) return tr("nHour", "", 1).arg(1); + else if (selectedTime == 86400) return tr("nDay", "", 1).arg(1); + else if (selectedTime == 259200) return tr("nDay", "", 3).arg(3); + else if (selectedTime == 604800) return tr("nWeek", "", 1).arg(1); + else return tr("nSeconds", "", selectedTime).arg(selectedTime); +} diff --git a/Linphone/core/chat/message/EventLogCore.hpp b/Linphone/core/chat/message/EventLogCore.hpp new file mode 100644 index 000000000..869479eb1 --- /dev/null +++ b/Linphone/core/chat/message/EventLogCore.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef EVENT_LOG_CORE_H_ +#define EVENT_LOG_CORE_H_ + +#include "ChatMessageCore.hpp" +#include "core/call-history/CallHistoryCore.hpp" +#include "core/conference/ConferenceInfoCore.hpp" +#include "core/conference/ConferenceInfoGui.hpp" +#include "model/chat/message/ChatMessageModel.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeConnection.hpp" +#include +#include + +#include + +class ChatMessageCore; + +class EventLogCore : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(LinphoneEnums::EventLogType type MEMBER mEventLogType CONSTANT) + Q_PROPERTY(ChatMessageCore *chatMessage READ getChatMessageCorePointer CONSTANT) + // Q_PROPERTY(NotifyCore *notification MEMBER mNotifyCore CONSTANT) + Q_PROPERTY(CallHistoryCore *callLog READ getCallHistoryCorePointer CONSTANT) + Q_PROPERTY(bool important MEMBER mImportant CONSTANT) + Q_PROPERTY(bool handled MEMBER mHandled CONSTANT) + Q_PROPERTY(QString eventDetails MEMBER mEventDetails CONSTANT) + Q_PROPERTY(QDateTime timestamp MEMBER mTimestamp CONSTANT) + +public: + static QSharedPointer create(const std::shared_ptr &eventLog); + EventLogCore(const std::shared_ptr &eventLog); + ~EventLogCore(); + void setSelf(QSharedPointer me); + std::string getEventLogId(); + QSharedPointer getChatMessageCore(); + QSharedPointer getCallHistoryCore(); + +private: + DECLARE_ABSTRACT_OBJECT + std::string mEventId; + + QSharedPointer mChatMessageCore = nullptr; + QSharedPointer mCallHistoryCore = nullptr; + LinphoneEnums::EventLogType mEventLogType; + bool mHandled; + bool mImportant; + QString mEventDetails; + QDateTime mTimestamp; + + ChatMessageCore *getChatMessageCorePointer(); + CallHistoryCore *getCallHistoryCorePointer(); + void computeEvent(const std::shared_ptr &eventLog); + QString getEphemeralFormatedTime(int selectedTime); +}; + +#endif // EventLogCore_H_ diff --git a/Linphone/core/chat/message/EventLogGui.cpp b/Linphone/core/chat/message/EventLogGui.cpp new file mode 100644 index 000000000..b3342e494 --- /dev/null +++ b/Linphone/core/chat/message/EventLogGui.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "EventLogGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(EventLogGui) + +EventLogGui::EventLogGui(QSharedPointer core) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +EventLogGui::~EventLogGui() { + mustBeInMainThread("~" + getClassName()); +} + +EventLogCore *EventLogGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/chat/message/EventLogGui.hpp b/Linphone/core/chat/message/EventLogGui.hpp new file mode 100644 index 000000000..8378118d3 --- /dev/null +++ b/Linphone/core/chat/message/EventLogGui.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef EVENT_LOG_GUI_H_ +#define EVENT_LOG_GUI_H_ + +#include "EventLogCore.hpp" +#include +#include + +class EventLogGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(EventLogCore *core READ getCore CONSTANT) + +public: + EventLogGui(QSharedPointer core); + ~EventLogGui(); + EventLogCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/chat/message/EventLogList.cpp b/Linphone/core/chat/message/EventLogList.cpp new file mode 100644 index 000000000..f2c9b7a66 --- /dev/null +++ b/Linphone/core/chat/message/EventLogList.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "EventLogList.hpp" +#include "ChatMessageCore.hpp" +#include "ChatMessageGui.hpp" +#include "EventLogGui.hpp" +#include "core/App.hpp" +#include "core/call-history/CallHistoryGui.hpp" +#include "core/chat/ChatCore.hpp" +#include "core/chat/ChatGui.hpp" +#include +#include + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(EventLogList) + +QSharedPointer EventLogList::create() { + auto model = QSharedPointer(new EventLogList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + +EventLogList::EventLogList(QObject *parent) : ListProxy(parent) { + mustBeInMainThread(getClassName()); + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); +} + +EventLogList::~EventLogList() { + mustBeInMainThread("~" + getClassName()); + mModelConnection = nullptr; +} + +ChatGui *EventLogList::getChat() const { + if (mChatCore) return new ChatGui(mChatCore); + else return nullptr; +} + +QSharedPointer EventLogList::getChatCore() const { + return mChatCore; +} + +void EventLogList::setChatCore(QSharedPointer core) { + if (mChatCore != core) { + if (mChatCore) disconnect(mChatCore.get(), &ChatCore::eventListChanged, this, nullptr); + mChatCore = core; + if (mChatCore) connect(mChatCore.get(), &ChatCore::eventListChanged, this, &EventLogList::lUpdate); + if (mChatCore) + connect(mChatCore.get(), &ChatCore::eventsInserted, this, [this](QList> list) { + auto eventsList = getSharedList(); + for (auto &event : list) { + auto it = std::find_if(eventsList.begin(), eventsList.end(), + [event](const QSharedPointer item) { return item == event; }); + if (it == eventsList.end()) { + add(event); + int index; + get(event.get(), &index); + emit eventInserted(index, new EventLogGui(event)); + } + } + }); + emit eventChanged(); + lUpdate(); + } +} + +void EventLogList::setChatGui(ChatGui *chat) { + auto chatCore = chat ? chat->mCore : nullptr; + setChatCore(chatCore); +} + +int EventLogList::findFirstUnreadIndex() { + auto eventList = getSharedList(); + auto it = std::find_if(eventList.begin(), eventList.end(), [](const QSharedPointer item) { + return item->getChatMessageCore() && !item->getChatMessageCore()->isRead(); + }); + return it == eventList.end() ? -1 : std::distance(eventList.begin(), it); +} + +void EventLogList::setSelf(QSharedPointer me) { + mModelConnection = SafeConnection::create(me, CoreModel::getInstance()); + + mModelConnection->makeConnectToCore(&EventLogList::lUpdate, [this]() { + for (auto &event : getSharedList()) { + auto message = event->getChatMessageCore(); + if (message) disconnect(message.get(), &ChatMessageCore::deleted, this, nullptr); + } + if (!mChatCore) return; + auto events = mChatCore->getEventLogList(); + for (auto &event : events) { + auto message = event->getChatMessageCore(); + if (message) + connect(message.get(), &ChatMessageCore::deleted, this, [this, message, event] { + emit mChatCore->lUpdateLastMessage(); + remove(event); + }); + } + resetData(events); + }); + + connect(this, &EventLogList::filterChanged, [this](QString filter) { + mFilter = filter; + lUpdate(); + }); + lUpdate(); +} + +QVariant EventLogList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + + auto core = mList[row].objectCast(); + if (core->getChatMessageCore()) { + switch (role) { + case Qt::DisplayRole: + return QVariant::fromValue(new ChatMessageGui(core->getChatMessageCore())); + case Qt::DisplayRole + 1: + return "chatMessage"; + } + } else if (core->getCallHistoryCore()) { + switch (role) { + case Qt::DisplayRole: + return QVariant::fromValue(new CallHistoryGui(core->getCallHistoryCore())); + case Qt::DisplayRole + 1: + return "callLog"; + } + } else { + switch (role) { + case Qt::DisplayRole: + return QVariant::fromValue(new EventLogGui(core)); + case Qt::DisplayRole + 1: + return "event"; + } + } + return QVariant(); +} + +QHash EventLogList::roleNames() const { + QHash roles; + roles[Qt::DisplayRole] = "modelData"; + roles[Qt::DisplayRole + 1] = "eventType"; + return roles; +} diff --git a/Linphone/core/chat/message/ChatMessageList.hpp b/Linphone/core/chat/message/EventLogList.hpp similarity index 68% rename from Linphone/core/chat/message/ChatMessageList.hpp rename to Linphone/core/chat/message/EventLogList.hpp index c00663f13..4ffe365ea 100644 --- a/Linphone/core/chat/message/ChatMessageList.hpp +++ b/Linphone/core/chat/message/EventLogList.hpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2010-2024 Belledonne Communications SARL. * * This file is part of linphone-desktop @@ -18,28 +18,25 @@ * along with this program. If not, see . */ -#ifndef CHAT_MESSAGE_LIST_H_ -#define CHAT_MESSAGE_LIST_H_ +#ifndef EVENT_LOG_LIST_H_ +#define EVENT_LOG_LIST_H_ #include "core/proxy/ListProxy.hpp" #include "tool/AbstractObject.hpp" #include "tool/thread/SafeConnection.hpp" #include -class ChatMessageGui; -class ChatMessageCore; +class EventLogGui; class ChatCore; class ChatGui; // ============================================================================= -class ChatMessageList : public ListProxy, public AbstractObject { +class EventLogList : public ListProxy, public AbstractObject { Q_OBJECT public: - static QSharedPointer create(); - // Create a ChatMessageCore and make connections to List. - QSharedPointer createChatMessageCore(const std::shared_ptr &chatMessage); - ChatMessageList(QObject *parent = Q_NULLPTR); - ~ChatMessageList(); + static QSharedPointer create(); + EventLogList(QObject *parent = Q_NULLPTR); + ~EventLogList(); QSharedPointer getChatCore() const; ChatGui *getChat() const; @@ -48,18 +45,20 @@ public: int findFirstUnreadIndex(); - void setSelf(QSharedPointer me); + void setSelf(QSharedPointer me); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + signals: void lUpdate(); void filterChanged(QString filter); - void chatChanged(); - void messageInserted(int index, ChatMessageGui *message); + void eventChanged(); + void eventInserted(int index, EventLogGui *message); private: QString mFilter; QSharedPointer mChatCore; - QSharedPointer> mModelConnection; + QSharedPointer> mModelConnection; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/chat/message/EventLogProxy.cpp b/Linphone/core/chat/message/EventLogProxy.cpp new file mode 100644 index 000000000..bf6ec9e0d --- /dev/null +++ b/Linphone/core/chat/message/EventLogProxy.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "EventLogProxy.hpp" +#include "EventLogGui.hpp" +#include "EventLogList.hpp" +// #include "core/chat/ChatGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(EventLogProxy) + +EventLogProxy::EventLogProxy(QObject *parent) : LimitProxy(parent) { + mList = EventLogList::create(); + setSourceModel(mList.get()); +} + +EventLogProxy::~EventLogProxy() { +} + +void EventLogProxy::setSourceModel(QAbstractItemModel *model) { + auto oldEventLogList = getListModel(); + if (oldEventLogList) { + disconnect(oldEventLogList); + } + auto newEventLogList = dynamic_cast(model); + if (newEventLogList) { + connect(newEventLogList, &EventLogList::eventChanged, this, &EventLogProxy::eventChanged); + connect(newEventLogList, &EventLogList::eventInserted, this, + [this, newEventLogList](int index, EventLogGui *event) { + if (index != -1) { + index = dynamic_cast(sourceModel()) + ->mapFromSource(newEventLogList->index(index, 0)) + .row(); + if (mMaxDisplayItems <= index) setMaxDisplayItems(index + mDisplayItemsStep); + } + emit eventInserted(index, event); + }); + } + setSourceModels(new SortFilterList(model)); + sort(0); +} + +ChatGui *EventLogProxy::getChatGui() { + auto model = getListModel(); + if (!mChatGui && model) mChatGui = model->getChat(); + return mChatGui; +} + +void EventLogProxy::setChatGui(ChatGui *chat) { + getListModel()->setChatGui(chat); +} + +EventLogGui *EventLogProxy::getEventAtIndex(int i) { + auto model = getListModel(); + auto sourceIndex = mapToSource(index(i, 0)).row(); + if (model) { + auto event = model->getAt(sourceIndex); + if (event) return new EventLogGui(event); + else return nullptr; + } + return nullptr; +} + +int EventLogProxy::findFirstUnreadIndex() { + auto eventLogList = getListModel(); + if (eventLogList) { + auto listIndex = eventLogList->findFirstUnreadIndex(); + if (listIndex != -1) { + listIndex = + dynamic_cast(sourceModel())->mapFromSource(eventLogList->index(listIndex, 0)).row(); + if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep); + return listIndex; + } else { + return std::max(0, getCount() - 1); + } + } + return std::max(0, getCount() - 1); +} + +bool EventLogProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + // auto l = getItemAtSource(sourceRow); + // return l != nullptr; + return true; +} + +bool EventLogProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { + auto l = getItemAtSource(sourceLeft.row()); + auto r = getItemAtSource(sourceRight.row()); + if (l && r) return l->getTimestamp() <= r->getTimestamp(); + else return true; +} diff --git a/Linphone/core/chat/message/ChatMessageProxy.hpp b/Linphone/core/chat/message/EventLogProxy.hpp similarity index 75% rename from Linphone/core/chat/message/ChatMessageProxy.hpp rename to Linphone/core/chat/message/EventLogProxy.hpp index 2fc9e27ce..efc334906 100644 --- a/Linphone/core/chat/message/ChatMessageProxy.hpp +++ b/Linphone/core/chat/message/EventLogProxy.hpp @@ -18,10 +18,10 @@ * along with this program. If not, see . */ -#ifndef CHAT_MESSAGE_PROXY_H_ -#define CHAT_MESSAGE_PROXY_H_ +#ifndef EVENT_LIST_PROXY_H_ +#define EVENT_LIST_PROXY_H_ -#include "ChatMessageList.hpp" +#include "EventLogList.hpp" #include "core/proxy/LimitProxy.hpp" #include "tool/AbstractObject.hpp" @@ -29,30 +29,30 @@ class ChatGui; -class ChatMessageProxy : public LimitProxy, public AbstractObject { +class EventLogProxy : public LimitProxy, public AbstractObject { Q_OBJECT - Q_PROPERTY(ChatGui *chatGui READ getChatGui WRITE setChatGui NOTIFY chatChanged) + Q_PROPERTY(ChatGui *chatGui READ getChatGui WRITE setChatGui NOTIFY eventChanged) public: DECLARE_SORTFILTER_CLASS() - ChatMessageProxy(QObject *parent = Q_NULLPTR); - ~ChatMessageProxy(); + EventLogProxy(QObject *parent = Q_NULLPTR); + ~EventLogProxy(); ChatGui *getChatGui(); void setChatGui(ChatGui *chat); void setSourceModel(QAbstractItemModel *sourceModel) override; - Q_INVOKABLE ChatMessageGui *getChatMessageAtIndex(int index); + Q_INVOKABLE EventLogGui *getEventAtIndex(int index); Q_INVOKABLE int findFirstUnreadIndex(); signals: - void chatChanged(); - void messageInserted(int index, ChatMessageGui *message); + void eventChanged(); + void eventInserted(int index, EventLogGui *message); protected: - QSharedPointer mList; + QSharedPointer mList; ChatGui *mChatGui = nullptr; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index c5c5ca371..76939785a 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -5921,4 +5921,98 @@ Failed to create 1-1 conversation with %1 ! Ok + + EventLogCore + + conference_created_event + 'You have joined the group' : Little message to show on the event when the user join the chat group. + You have joined the group + + + conference_created_terminated + 'You have left the group' : Little message to show on the event when the user leave the chat group. + You have left the group + + + conference_participant_added_event + '%1 has joined' : Little message to show on the event when someone join the chat group. + %1 has joined + + + conference_participant_removed_event + '%1 has left' : Little message to show on the event when someone leave the chat group + %1 has left + + + conference_participant_set_admin_event + '%1 is now an admin' : Little message to show on the event when someone get the admin status. %1 is somebody + %1 is now an admin + + + conference_participant_unset_admin_event + '%1 is no longer an admin' : Little message to show on the event when somebody lost its admin status. %1 is somebody + %1 is no longer an admin + + + conference_security_event + 'Security level degraded by %1': Little message to show on the event when a security level has been lost. + Security level degraded by %1 + + + conference_ephemeral_message_enabled_event + 'Ephemeral messages have been enabled: %1' : Little message to show on the event when ephemeral has been activated. %1 is a date time + Ephemeral messages have been enabled: %1 + + + conference_ephemeral_message_disabled_event + 'Ephemeral messages have been disabled': Little message to show on the event when ephemeral has been deactivated. + Ephemeral messages have been disabled + + + conference_subject_changed_event + 'New subject : %1' : Little message to show on the event when the subject of the chat room has been changed. %1 is the new subject. + New subject: %1 + + + conference_ephemeral_message_lifetime_changed_event + 'Ephemeral messages have been updated: %1' : Little message to show on the event when ephemeral has been updated. %1 is a date time + Ephemeral messages have been updated: %1 + + + nSeconds + + %1 second + %1 seconds + + + + nMinute + + %1 minute + %1 minutes + + + + nHour + + %1 hour + %1 hours + + + + nDay + + %1 day + %1 days + + + + nWeek + + %1 week + %1 weeks + + + + diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 29c8fe833..9d318ce65 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -1885,3 +1885,24 @@ bool Utils::codepointIsEmoji(uint code) { (code >= 0x2700 && code <= 0x27BF) // Dingbats ); } + +QString Utils::toDateTimeString(QDateTime date, const QString &format) { + if (date.date() == QDate::currentDate()) return toTimeString(date); + else { + return getOffsettedUTC(date).toString(format); + } +} + +QDateTime Utils::getOffsettedUTC(const QDateTime &date) { + QDateTime utc = date.toUTC(); + auto timezone = date.timeZone(); + int offset = timezone.offsetFromUtc(date); + utc = utc.addSecs(offset); + utc.setTimeZone(QTimeZone(offset)); + return utc; +} + +QString Utils::toTimeString(QDateTime date, const QString &format) { + // Issue : date.toString() will not print the good time in timezones. Get it from date and add ourself the offset. + return getOffsettedUTC(date).toString(format); +} diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 6aa3690bb..e6bb51ba1 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -158,6 +158,10 @@ public: Q_INVOKABLE static QString getFilename(QUrl url); static bool codepointIsEmoji(uint code); + Q_INVOKABLE static QString toDateTimeString(QDateTime date, const QString &format = "yyyy/MM/dd hh:mm:ss"); + static QDateTime getOffsettedUTC(const QDateTime &date); + Q_INVOKABLE static QString toTimeString(QDateTime date, const QString &format = "hh:mm:ss"); + // QDir findDirectoryByName(QString startPath, QString name); static QString getApplicationProduct(); diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 22bef91d8..7466d7c5b 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -57,6 +57,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Control/Display/Chat/ChatMessage.qml view/Control/Display/Chat/ChatMessageInvitationBubble.qml view/Control/Display/Chat/ChatMessagesListView.qml + view/Control/Display/Chat/Event.qml view/Control/Display/Contact/Avatar.qml view/Control/Display/Contact/Contact.qml view/Control/Display/Contact/Presence.qml diff --git a/Linphone/view/Control/Display/Chat/ChatListView.qml b/Linphone/view/Control/Display/Chat/ChatListView.qml index 727bd1b46..93ba03db2 100644 --- a/Linphone/view/Control/Display/Chat/ChatListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatListView.qml @@ -246,7 +246,7 @@ ListView { unread: modelData.core.unreadMessagesCount } EffectImage { - visible: modelData?.core.lastMessage && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle + visible: modelData?.core.lastEvent && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle && !modelData.core.lastMessage.core.isRemoteMessage Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 Layout.preferredHeight: 14 * DefaultStyle.dp diff --git a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml index 5b4f2b4e2..923aff317 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml @@ -15,17 +15,17 @@ ListView { property color backgroundColor Component.onCompleted: { - var index = chatMessageProxy.findFirstUnreadIndex() + var index = eventLogProxy.findFirstUnreadIndex() positionViewAtIndex(index, ListView.End) - var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) - if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() + var chatMessage = eventLogProxy.getEventAtIndex(index)?.core?.chatMessage + if (chatMessage && !chatMessage.isRead) chatMessage.lMarkAsRead() } onCountChanged: if (atYEnd) { - var index = chatMessageProxy.findFirstUnreadIndex() + var index = eventLogProxy.findFirstUnreadIndex() mainItem.positionViewAtIndex(index, ListView.End) - var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) - if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() + var chatMessage = eventLogProxy.getEventAtIndex(index)?.core?.chatMessage + if (chatMessage && !chatMessage.isRead) chatMessage.lMarkAsRead() } Button { @@ -40,21 +40,22 @@ ListView { anchors.bottomMargin: Math.round(18 * DefaultStyle.dp) anchors.rightMargin: Math.round(18 * DefaultStyle.dp) onClicked: { - var index = chatMessageProxy.findFirstUnreadIndex() + var index = eventLogProxy.findFirstUnreadIndex() mainItem.positionViewAtIndex(index, ListView.End) - var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) - if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() + var chatMessage = eventLogProxy.getEventAtIndex(index)?.core?.chatMessage + if (chatMessage && !chatMessage.isRead) chatMessage.lMarkAsRead() } } - model: ChatMessageProxy { - id: chatMessageProxy + model: EventLogProxy { + id: eventLogProxy chatGui: mainItem.chat // scroll when in view and message inserted - onMessageInserted: (index, gui) => { + onEventInserted: (index, gui) => { if (!mainItem.visible) return mainItem.positionViewAtIndex(index, ListView.End) - if (!gui.core.isRead) gui.core.lMarkAsRead() + if (gui.core.chatMessage && !gui.core.chatMessage.isRead) + gui.core.chatMessage.lMarkAsRead() } } @@ -107,25 +108,51 @@ ListView { } } } + + + delegate: DelegateChooser { + role: "eventType" - delegate: ChatMessage { - chatMessage: modelData - maxWidth: Math.round(mainItem.width * (3/4)) - onVisibleChanged: { - if (visible && !modelData.core.isRead) modelData.core.lMarkAsRead() + DelegateChoice { + roleValue: "chatMessage" + delegate: + ChatMessage { + chatMessage: modelData + maxWidth: Math.round(mainItem.width * (3/4)) + onVisibleChanged: { + if (visible && !modelData.core.isRead) modelData.core.lMarkAsRead() + } + width: mainItem.width + property var previousIndex: index - 1 + property var previousFromAddress: eventLogProxy.getEventAtIndex(index-1)?.core.chatMessage?.fromAddress + backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100 + isFirstMessage: !previousFromAddress || previousFromAddress !== modelData.core.fromAddress + anchors.right: !isRemoteMessage && parent + ? parent.right + : undefined + + onMessageDeletionRequested: modelData.core.lDelete() + } } - width: mainItem.width - property var previousIndex: index - 1 - property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress - backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100 - isFirstMessage: !previousFromAddress || previousFromAddress !== modelData.core.fromAddress - anchors.right: !isRemoteMessage && parent - ? parent.right - : undefined - onMessageDeletionRequested: modelData.core.lDelete() + DelegateChoice { + roleValue: "event" + delegate: + Item { + property bool showTopMargin: !header.visible && index == 0 + width: mainItem.width + height: (showTopMargin ? 30 : 0 * DefaultStyle.dp) + eventItem.implicitHeight + Event { + id: eventItem + anchors.top: parent.top + anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp + width: parent.width + eventLogGui: modelData + } + } + } } - + footerPositioning: ListView.OverlayFooter footer: Control.Control { visible: composeLayout.composingName !== "" diff --git a/Linphone/view/Control/Display/Chat/Event.qml b/Linphone/view/Control/Display/Chat/Event.qml new file mode 100644 index 000000000..7fd098567 --- /dev/null +++ b/Linphone/view/Control/Display/Chat/Event.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Layouts +import Linphone +import UtilsCpp + +RowLayout { + id: mainLayout + height: 40 * DefaultStyle.dp + visible: eventLogCore.handled + property EventLogGui eventLogGui + property var eventLogCore: eventLogGui.core + + Rectangle { + height: 1 + Layout.fillWidth: true + color: DefaultStyle.main2_200 + } + + ColumnLayout { + Layout.rightMargin: 20 * DefaultStyle.dp + Layout.leftMargin: 20 * DefaultStyle.dp + Layout.alignment: Qt.AlignVCenter + + Text { + id: message + text: eventLogCore.eventDetails + font: Typography.p3 + color: eventLogCore.important ? DefaultStyle.danger_500main : DefaultStyle.main2_500main + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + } + Text { + id: date + text: UtilsCpp.toDateTimeString(eventLogCore.timestamp) + font: Typography.p3 + color: DefaultStyle.main2_500main + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + } + } + + Rectangle { + height: 1 + Layout.fillWidth: true + color: DefaultStyle.main2_200 + } +} + diff --git a/Linphone/view/Page/Form/Chat/SelectedChatView.qml b/Linphone/view/Page/Form/Chat/SelectedChatView.qml index 6c4fbdab9..b46f56f3f 100644 --- a/Linphone/view/Page/Form/Chat/SelectedChatView.qml +++ b/Linphone/view/Page/Form/Chat/SelectedChatView.qml @@ -16,10 +16,10 @@ RowLayout { property alias callHeaderContent: splitPanel.headerContent spacing: 0 - onChatChanged: { + //onEventChanged: { // TODO : call when all messages read after scroll to unread feature available // if (chat) chat.core.lMarkAsRead() - } + //} MainRightPanel { id: splitPanel Layout.fillWidth: true