Add events to chat

This commit is contained in:
Christophe Deschamps 2025-05-28 08:33:55 +00:00
parent 672ae55ea6
commit 460c0334c4
24 changed files with 908 additions and 382 deletions

View file

@ -54,9 +54,10 @@
#include "core/call/CallProxy.hpp" #include "core/call/CallProxy.hpp"
#include "core/camera/CameraGui.hpp" #include "core/camera/CameraGui.hpp"
#include "core/chat/ChatProxy.hpp" #include "core/chat/ChatProxy.hpp"
#include "core/chat/message/EventLogGui.hpp"
#include "core/chat/message/ChatMessageGui.hpp" #include "core/chat/message/ChatMessageGui.hpp"
#include "core/chat/message/ChatMessageList.hpp" #include "core/chat/message/EventLogList.hpp"
#include "core/chat/message/ChatMessageProxy.hpp" #include "core/chat/message/EventLogProxy.hpp"
#include "core/conference/ConferenceGui.hpp" #include "core/conference/ConferenceGui.hpp"
#include "core/conference/ConferenceInfoGui.hpp" #include "core/conference/ConferenceInfoGui.hpp"
#include "core/conference/ConferenceInfoProxy.hpp" #include "core/conference/ConferenceInfoProxy.hpp"
@ -661,9 +662,10 @@ void App::initCppInterfaces() {
qmlRegisterType<ChatList>(Constants::MainQmlUri, 1, 0, "ChatList"); qmlRegisterType<ChatList>(Constants::MainQmlUri, 1, 0, "ChatList");
qmlRegisterType<ChatProxy>(Constants::MainQmlUri, 1, 0, "ChatProxy"); qmlRegisterType<ChatProxy>(Constants::MainQmlUri, 1, 0, "ChatProxy");
qmlRegisterType<ChatGui>(Constants::MainQmlUri, 1, 0, "ChatGui"); qmlRegisterType<ChatGui>(Constants::MainQmlUri, 1, 0, "ChatGui");
qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui"); qmlRegisterType<EventLogGui>(Constants::MainQmlUri, 1, 0, "EventLogGui");
qmlRegisterType<ChatMessageList>(Constants::MainQmlUri, 1, 0, "ChatMessageList"); qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui");
qmlRegisterType<ChatMessageProxy>(Constants::MainQmlUri, 1, 0, "ChatMessageProxy"); qmlRegisterType<EventLogList>(Constants::MainQmlUri, 1, 0, "EventLogList");
qmlRegisterType<EventLogProxy>(Constants::MainQmlUri, 1, 0, "EventLogProxy");
qmlRegisterUncreatableType<ConferenceCore>(Constants::MainQmlUri, 1, 0, "ConferenceCore", qmlRegisterUncreatableType<ConferenceCore>(Constants::MainQmlUri, 1, 0, "ConferenceCore",
QLatin1String("Uncreatable")); QLatin1String("Uncreatable"));
qmlRegisterType<ConferenceGui>(Constants::MainQmlUri, 1, 0, "ConferenceGui"); qmlRegisterType<ConferenceGui>(Constants::MainQmlUri, 1, 0, "ConferenceGui");

View file

@ -25,8 +25,10 @@ list(APPEND _LINPHONEAPP_SOURCES
core/chat/ChatProxy.cpp core/chat/ChatProxy.cpp
core/chat/message/ChatMessageCore.cpp core/chat/message/ChatMessageCore.cpp
core/chat/message/ChatMessageGui.cpp core/chat/message/ChatMessageGui.cpp
core/chat/message/ChatMessageList.cpp core/chat/message/EventLogCore.cpp
core/chat/message/ChatMessageProxy.cpp core/chat/message/EventLogGui.cpp
core/chat/message/EventLogList.cpp
core/chat/message/EventLogProxy.cpp
core/emoji/EmojiModel.cpp core/emoji/EmojiModel.cpp
core/fps-counter/FPSCounter.cpp core/fps-counter/FPSCounter.cpp
core/friend/FriendCore.cpp core/friend/FriendCore.cpp

View file

@ -76,25 +76,29 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
mChatModel->setSelf(mChatModel); mChatModel->setSelf(mChatModel);
auto lastMessage = chatRoom->getLastMessageInHistory(); auto lastMessage = chatRoom->getLastMessageInHistory();
mLastMessage = lastMessage ? ChatMessageCore::create(lastMessage) : nullptr; mLastMessage = lastMessage ? ChatMessageCore::create(lastMessage) : nullptr;
auto history = chatRoom->getHistory(0, (int)linphone::ChatRoom::HistoryFilter::ChatMessage);
std::list<std::shared_ptr<linphone::ChatMessage>> lHistory; int filter = mIsGroupChat ? static_cast<int>(linphone::ChatRoom::HistoryFilter::ChatMessage) |
static_cast<int>(linphone::ChatRoom::HistoryFilter::InfoNoDevice)
: static_cast<int>(linphone::ChatRoom::HistoryFilter::ChatMessage);
auto history = chatRoom->getHistory(0, filter);
std::list<std::shared_ptr<linphone::EventLog>> lHistory;
for (auto &eventLog : history) { for (auto &eventLog : history) {
if (eventLog->getChatMessage()) lHistory.push_back(eventLog->getChatMessage()); lHistory.push_back(eventLog);
} }
QList<QSharedPointer<ChatMessageCore>> messageList; QList<QSharedPointer<EventLogCore>> eventList;
for (auto &message : lHistory) { for (auto &event : lHistory) {
if (!message) continue; auto eventLogCore = EventLogCore::create(event);
auto chatMessage = ChatMessageCore::create(message); eventList.append(eventLogCore);
messageList.append(chatMessage);
} }
resetChatMessageList(messageList); resetEventLogList(eventList);
mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier()); mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier());
mChatRoomState = LinphoneEnums::fromLinphone(chatRoom->getState()); mChatRoomState = LinphoneEnums::fromLinphone(chatRoom->getState());
mIsEncrypted = chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Encrypted); mIsEncrypted = chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Encrypted);
mIsReadOnly = chatRoom->isReadOnly(); mIsReadOnly = chatRoom->isReadOnly();
connect(this, &ChatCore::messageListChanged, this, &ChatCore::lUpdateLastMessage); connect(this, &ChatCore::eventListChanged, this, &ChatCore::lUpdateLastMessage);
connect(this, &ChatCore::messagesInserted, this, &ChatCore::lUpdateLastMessage); connect(this, &ChatCore::eventsInserted, this, &ChatCore::lUpdateLastMessage);
connect(this, &ChatCore::messageRemoved, this, &ChatCore::lUpdateLastMessage); connect(this, &ChatCore::eventRemoved, this, &ChatCore::lUpdateLastMessage);
} }
ChatCore::~ChatCore() { ChatCore::~ChatCore() {
@ -112,7 +116,7 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
&ChatCore::lLeave, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->leave(); }); }); &ChatCore::lLeave, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->leave(); }); });
mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() { mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() {
mChatModelConnection->invokeToCore([this]() { mChatModelConnection->invokeToCore([this]() {
clearMessagesList(); clearEventLogList();
//: Deleted //: Deleted
Utils::showInformationPopup(tr("info_toast_deleted_title"), Utils::showInformationPopup(tr("info_toast_deleted_title"),
//: Message history has been deleted //: Message history has been deleted
@ -148,36 +152,30 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
}); });
}); });
mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, // TODO onNewEvent?
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) { const std::shared_ptr<const linphone::EventLog> &eventLog) {
if (mChatModel->getMonitor() != chatRoom) return; if (mChatModel->getMonitor() != chatRoom) return;
auto message = eventLog->getChatMessage();
qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle(); qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle();
if (message) { auto event = EventLogCore::create(eventLog);
auto newMessage = ChatMessageCore::create(message); mChatModelConnection->invokeToCore([this, event]() {
mChatModelConnection->invokeToCore([this, newMessage]() { appendEventLogToEventLogList(event);
appendMessageToMessageList(newMessage); emit lUpdateUnreadCount();
emit lUpdateUnreadCount(); emit lUpdateLastUpdatedTime();
emit lUpdateLastUpdatedTime(); });
});
}
}); });
mChatModelConnection->makeConnectToModel( mChatModelConnection->makeConnectToModel(
&ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom, &ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::list<std::shared_ptr<linphone::EventLog>> &chatMessages) { const std::list<std::shared_ptr<linphone::EventLog>> &eventsLog) {
if (mChatModel->getMonitor() != chatRoom) return; if (mChatModel->getMonitor() != chatRoom) return;
qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle(); qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle();
QList<QSharedPointer<ChatMessageCore>> list; QList<QSharedPointer<EventLogCore>> list;
for (auto &m : chatMessages) { for (auto &e : eventsLog) {
auto message = m->getChatMessage(); auto event = EventLogCore::create(e);
if (message) { list.push_back(event);
auto newMessage = ChatMessageCore::create(message);
list.push_back(newMessage);
}
} }
mChatModelConnection->invokeToCore([this, list]() { mChatModelConnection->invokeToCore([this, list]() {
appendMessagesToMessageList(list); appendEventLogsToEventLogList(list);
emit lUpdateUnreadCount(); emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime(); emit lUpdateLastUpdatedTime();
}); });
@ -211,11 +209,8 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
mChatModelConnection->makeConnectToModel( mChatModelConnection->makeConnectToModel(
&ChatModel::chatMessageSending, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom, &ChatModel::chatMessageSending, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) { const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto message = eventLog->getChatMessage(); auto event = EventLogCore::create(eventLog);
if (message) { mChatModelConnection->invokeToCore([this, event]() { appendEventLogToEventLogList(event); });
auto newMessage = ChatMessageCore::create(message);
mChatModelConnection->invokeToCore([this, newMessage]() { appendMessageToMessageList(newMessage); });
}
}); });
mChatModelConnection->makeConnectToCore( mChatModelConnection->makeConnectToCore(
&ChatCore::lCompose, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->compose(); }); }); &ChatCore::lCompose, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->compose(); }); });
@ -333,10 +328,6 @@ ChatMessageGui *ChatCore::getLastMessage() const {
return mLastMessage ? new ChatMessageGui(mLastMessage) : nullptr; return mLastMessage ? new ChatMessageGui(mLastMessage) : nullptr;
} }
QSharedPointer<ChatMessageCore> ChatCore::getLastMessageCore() const {
return mLastMessage;
}
void ChatCore::setLastMessage(QSharedPointer<ChatMessageCore> lastMessage) { void ChatCore::setLastMessage(QSharedPointer<ChatMessageCore> lastMessage) {
if (mLastMessage != lastMessage) { if (mLastMessage != lastMessage) {
disconnect(mLastMessage.get()); disconnect(mLastMessage.get());
@ -357,45 +348,45 @@ void ChatCore::setUnreadMessagesCount(int count) {
} }
} }
QList<QSharedPointer<ChatMessageCore>> ChatCore::getChatMessageList() const { QList<QSharedPointer<EventLogCore>> ChatCore::getEventLogList() const {
return mChatMessageList; return mEventLogList;
} }
void ChatCore::resetChatMessageList(QList<QSharedPointer<ChatMessageCore>> list) { void ChatCore::resetEventLogList(QList<QSharedPointer<EventLogCore>> list) {
mChatMessageList = list; mEventLogList = list;
emit messageListChanged(); emit eventListChanged();
} }
void ChatCore::appendMessagesToMessageList(QList<QSharedPointer<ChatMessageCore>> list) { void ChatCore::appendEventLogsToEventLogList(QList<QSharedPointer<EventLogCore>> list) {
int nbAdded = 0; int nbAdded = 0;
for (auto &message : list) { for (auto &e : list) {
if (mChatMessageList.contains(message)) continue; if (mEventLogList.contains(e)) continue;
mChatMessageList.append(message); mEventLogList.append(e);
++nbAdded; ++nbAdded;
} }
if (nbAdded > 0) emit messagesInserted(list); if (nbAdded > 0) emit eventsInserted(list);
} }
void ChatCore::appendMessageToMessageList(QSharedPointer<ChatMessageCore> message) { void ChatCore::appendEventLogToEventLogList(QSharedPointer<EventLogCore> e) {
if (mChatMessageList.contains(message)) return; if (mEventLogList.contains(e)) return;
mChatMessageList.append(message); mEventLogList.append(e);
emit messagesInserted({message}); emit eventsInserted({e});
} }
void ChatCore::removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCore>> list) { void ChatCore::removeEventLogsFromEventLogList(QList<QSharedPointer<EventLogCore>> list) {
int nbRemoved = 0; int nbRemoved = 0;
for (auto &message : list) { for (auto &e : list) {
if (mChatMessageList.contains(message)) { if (mEventLogList.contains(e)) {
mChatMessageList.removeAll(message); mEventLogList.removeAll(e);
++nbRemoved; ++nbRemoved;
} }
} }
if (nbRemoved > 0) emit messageRemoved(); if (nbRemoved > 0) emit eventRemoved();
} }
void ChatCore::clearMessagesList() { void ChatCore::clearEventLogList() {
mChatMessageList.clear(); mEventLogList.clear();
emit messageListChanged(); emit eventListChanged();
} }
QString ChatCore::getComposingName() const { QString ChatCore::getComposingName() const {

View file

@ -21,7 +21,8 @@
#ifndef CHAT_CORE_H_ #ifndef CHAT_CORE_H_
#define 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/chat/ChatModel.hpp"
#include "model/search/MagicSearchModel.hpp" #include "model/search/MagicSearchModel.hpp"
#include "tool/LinphoneEnums.hpp" #include "tool/LinphoneEnums.hpp"
@ -30,6 +31,8 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <linphone++/linphone.hh> #include <linphone++/linphone.hh>
class EventLogCore;
class ChatCore : public QObject, public AbstractObject { class ChatCore : public QObject, public AbstractObject {
Q_OBJECT Q_OBJECT
@ -94,12 +97,12 @@ public:
QString getChatRoomAddress() const; QString getChatRoomAddress() const;
QString getPeerAddress() const; QString getPeerAddress() const;
QList<QSharedPointer<ChatMessageCore>> getChatMessageList() const; QList<QSharedPointer<EventLogCore>> getEventLogList() const;
void resetChatMessageList(QList<QSharedPointer<ChatMessageCore>> list); void resetEventLogList(QList<QSharedPointer<EventLogCore>> list);
void appendMessageToMessageList(QSharedPointer<ChatMessageCore> message); void appendEventLogToEventLogList(QSharedPointer<EventLogCore> event);
void appendMessagesToMessageList(QList<QSharedPointer<ChatMessageCore>> list); void appendEventLogsToEventLogList(QList<QSharedPointer<EventLogCore>> list);
void removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCore>> list); void removeEventLogsFromEventLogList(QList<QSharedPointer<EventLogCore>> list);
void clearMessagesList(); void clearEventLogList();
QString getAvatarUri() const; QString getAvatarUri() const;
void setAvatarUri(QString avatarUri); void setAvatarUri(QString avatarUri);
@ -118,9 +121,9 @@ signals:
void lastMessageChanged(); void lastMessageChanged();
void titleChanged(QString title); void titleChanged(QString title);
void unreadMessagesCountChanged(int count); void unreadMessagesCountChanged(int count);
void messageListChanged(); void eventListChanged();
void messagesInserted(QList<QSharedPointer<ChatMessageCore>> list); void eventsInserted(QList<QSharedPointer<EventLogCore>> list);
void messageRemoved(); void eventRemoved();
void avatarUriChanged(); void avatarUriChanged();
void deleted(); void deleted();
void composingUserChanged(); void composingUserChanged();
@ -157,7 +160,7 @@ private:
LinphoneEnums::ChatRoomState mChatRoomState; LinphoneEnums::ChatRoomState mChatRoomState;
std::shared_ptr<ChatModel> mChatModel; std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage; QSharedPointer<ChatMessageCore> mLastMessage;
QList<QSharedPointer<ChatMessageCore>> mChatMessageList; QList<QSharedPointer<EventLogCore>> mEventLogList;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection; QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT

View file

@ -21,6 +21,7 @@
#ifndef CHATMESSAGECORE_H_ #ifndef CHATMESSAGECORE_H_
#define CHATMESSAGECORE_H_ #define CHATMESSAGECORE_H_
#include "EventLogCore.hpp"
#include "core/conference/ConferenceInfoCore.hpp" #include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp" #include "core/conference/ConferenceInfoGui.hpp"
#include "model/chat/message/ChatMessageModel.hpp" #include "model/chat/message/ChatMessageModel.hpp"
@ -48,6 +49,7 @@ public:
}; };
class ChatCore; class ChatCore;
class EventLogCore;
class ChatMessageCore : public QObject, public AbstractObject { class ChatMessageCore : public QObject, public AbstractObject {
Q_OBJECT Q_OBJECT

View file

@ -19,6 +19,7 @@
*/ */
#include "ChatMessageGui.hpp" #include "ChatMessageGui.hpp"
#include "ChatMessageCore.hpp"
#include "core/App.hpp" #include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageGui) DEFINE_ABSTRACT_OBJECT(ChatMessageGui)

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "ChatMessageList.hpp"
#include "ChatMessageCore.hpp"
#include "ChatMessageGui.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "core/chat/ChatGui.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(ChatMessageList)
QSharedPointer<ChatMessageList> ChatMessageList::create() {
auto model = QSharedPointer<ChatMessageList>(new ChatMessageList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
model->setSelf(model);
return model;
}
QSharedPointer<ChatMessageCore>
ChatMessageList::createChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &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<ChatCore> ChatMessageList::getChatCore() const {
return mChatCore;
}
void ChatMessageList::setChatCore(QSharedPointer<ChatCore> 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<QSharedPointer<ChatMessageCore>> list) {
auto chatList = getSharedList<ChatMessageCore>();
for (auto &message : list) {
auto it = std::find_if(
chatList.begin(), chatList.end(),
[message](const QSharedPointer<ChatMessageCore> 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<ChatMessageCore>();
auto it = std::find_if(chatList.begin(), chatList.end(),
[](const QSharedPointer<ChatMessageCore> item) { return !item->isRead(); });
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
}
void ChatMessageList::setSelf(QSharedPointer<ChatMessageList> me) {
mModelConnection = SafeConnection<ChatMessageList, CoreModel>::create(me, CoreModel::getInstance());
mModelConnection->makeConnectToCore(&ChatMessageList::lUpdate, [this]() {
for (auto &message : getSharedList<ChatMessageCore>()) {
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<ChatMessageCore>(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<ChatMessageCore>()));
return QVariant();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<ChatMessageList>();
if (oldChatMessageList) {
disconnect(oldChatMessageList);
}
auto newChatMessageList = dynamic_cast<ChatMessageList *>(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<SortFilterList *>(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<ChatMessageList>();
if (!mChatGui && model) mChatGui = model->getChat();
return mChatGui;
}
void ChatMessageProxy::setChatGui(ChatGui *chat) {
getListModel<ChatMessageList>()->setChatGui(chat);
}
ChatMessageGui *ChatMessageProxy::getChatMessageAtIndex(int i) {
auto model = getListModel<ChatMessageList>();
auto sourceIndex = mapToSource(index(i, 0)).row();
if (model) {
auto chat = model->getAt<ChatMessageCore>(sourceIndex);
if (chat) return new ChatMessageGui(chat);
else return nullptr;
}
return nullptr;
}
int ChatMessageProxy::findFirstUnreadIndex() {
auto chatMessageList = getListModel<ChatMessageList>();
if (chatMessageList) {
auto listIndex = chatMessageList->findFirstUnreadIndex();
if (listIndex != -1) {
listIndex = dynamic_cast<SortFilterList *>(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<ChatMessageList, ChatMessageCore>(sourceRow);
// return l != nullptr;
return true;
}
bool ChatMessageProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
auto l = getItemAtSource<ChatMessageList, ChatMessageCore>(sourceLeft.row());
auto r = getItemAtSource<ChatMessageList, ChatMessageCore>(sourceRight.row());
if (l && r) return l->getTimestamp() <= r->getTimestamp();
else return true;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "EventLogCore.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "model/tool/ToolModel.hpp"
DEFINE_ABSTRACT_OBJECT(EventLogCore)
QSharedPointer<EventLogCore> EventLogCore::create(const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto sharedPointer = QSharedPointer<EventLogCore>(new EventLogCore(eventLog), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
EventLogCore::EventLogCore(const std::shared_ptr<const linphone::EventLog> &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<EventLogCore> me) {
}
std::string EventLogCore::getEventLogId() {
return mEventId;
}
QSharedPointer<ChatMessageCore> EventLogCore::getChatMessageCore() {
return mChatMessageCore;
}
QSharedPointer<CallHistoryCore> 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<const linphone::EventLog> &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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
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<EventLogCore> create(const std::shared_ptr<const linphone::EventLog> &eventLog);
EventLogCore(const std::shared_ptr<const linphone::EventLog> &eventLog);
~EventLogCore();
void setSelf(QSharedPointer<EventLogCore> me);
std::string getEventLogId();
QSharedPointer<ChatMessageCore> getChatMessageCore();
QSharedPointer<CallHistoryCore> getCallHistoryCore();
private:
DECLARE_ABSTRACT_OBJECT
std::string mEventId;
QSharedPointer<ChatMessageCore> mChatMessageCore = nullptr;
QSharedPointer<CallHistoryCore> mCallHistoryCore = nullptr;
LinphoneEnums::EventLogType mEventLogType;
bool mHandled;
bool mImportant;
QString mEventDetails;
QDateTime mTimestamp;
ChatMessageCore *getChatMessageCorePointer();
CallHistoryCore *getCallHistoryCorePointer();
void computeEvent(const std::shared_ptr<const linphone::EventLog> &eventLog);
QString getEphemeralFormatedTime(int selectedTime);
};
#endif // EventLogCore_H_

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "EventLogGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(EventLogGui)
EventLogGui::EventLogGui(QSharedPointer<EventLogCore> 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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef EVENT_LOG_GUI_H_
#define EVENT_LOG_GUI_H_
#include "EventLogCore.hpp"
#include <QObject>
#include <QSharedPointer>
class EventLogGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(EventLogCore *core READ getCore CONSTANT)
public:
EventLogGui(QSharedPointer<EventLogCore> core);
~EventLogGui();
EventLogCore *getCore() const;
QSharedPointer<EventLogCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(EventLogList)
QSharedPointer<EventLogList> EventLogList::create() {
auto model = QSharedPointer<EventLogList>(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<ChatCore> EventLogList::getChatCore() const {
return mChatCore;
}
void EventLogList::setChatCore(QSharedPointer<ChatCore> 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<QSharedPointer<EventLogCore>> list) {
auto eventsList = getSharedList<EventLogCore>();
for (auto &event : list) {
auto it = std::find_if(eventsList.begin(), eventsList.end(),
[event](const QSharedPointer<EventLogCore> 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<EventLogCore>();
auto it = std::find_if(eventList.begin(), eventList.end(), [](const QSharedPointer<EventLogCore> item) {
return item->getChatMessageCore() && !item->getChatMessageCore()->isRead();
});
return it == eventList.end() ? -1 : std::distance(eventList.begin(), it);
}
void EventLogList::setSelf(QSharedPointer<EventLogList> me) {
mModelConnection = SafeConnection<EventLogList, CoreModel>::create(me, CoreModel::getInstance());
mModelConnection->makeConnectToCore(&EventLogList::lUpdate, [this]() {
for (auto &event : getSharedList<EventLogCore>()) {
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<EventLogCore>(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<EventLogCore>();
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<int, QByteArray> EventLogList::roleNames() const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "modelData";
roles[Qt::DisplayRole + 1] = "eventType";
return roles;
}

View file

@ -1,4 +1,4 @@
/* /*
* Copyright (c) 2010-2024 Belledonne Communications SARL. * Copyright (c) 2010-2024 Belledonne Communications SARL.
* *
* This file is part of linphone-desktop * This file is part of linphone-desktop
@ -18,28 +18,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CHAT_MESSAGE_LIST_H_ #ifndef EVENT_LOG_LIST_H_
#define CHAT_MESSAGE_LIST_H_ #define EVENT_LOG_LIST_H_
#include "core/proxy/ListProxy.hpp" #include "core/proxy/ListProxy.hpp"
#include "tool/AbstractObject.hpp" #include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp" #include "tool/thread/SafeConnection.hpp"
#include <QLocale> #include <QLocale>
class ChatMessageGui; class EventLogGui;
class ChatMessageCore;
class ChatCore; class ChatCore;
class ChatGui; class ChatGui;
// ============================================================================= // =============================================================================
class ChatMessageList : public ListProxy, public AbstractObject { class EventLogList : public ListProxy, public AbstractObject {
Q_OBJECT Q_OBJECT
public: public:
static QSharedPointer<ChatMessageList> create(); static QSharedPointer<EventLogList> create();
// Create a ChatMessageCore and make connections to List. EventLogList(QObject *parent = Q_NULLPTR);
QSharedPointer<ChatMessageCore> createChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &chatMessage); ~EventLogList();
ChatMessageList(QObject *parent = Q_NULLPTR);
~ChatMessageList();
QSharedPointer<ChatCore> getChatCore() const; QSharedPointer<ChatCore> getChatCore() const;
ChatGui *getChat() const; ChatGui *getChat() const;
@ -48,18 +45,20 @@ public:
int findFirstUnreadIndex(); int findFirstUnreadIndex();
void setSelf(QSharedPointer<ChatMessageList> me); void setSelf(QSharedPointer<EventLogList> me);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
signals: signals:
void lUpdate(); void lUpdate();
void filterChanged(QString filter); void filterChanged(QString filter);
void chatChanged(); void eventChanged();
void messageInserted(int index, ChatMessageGui *message); void eventInserted(int index, EventLogGui *message);
private: private:
QString mFilter; QString mFilter;
QSharedPointer<ChatCore> mChatCore; QSharedPointer<ChatCore> mChatCore;
QSharedPointer<SafeConnection<ChatMessageList, CoreModel>> mModelConnection; QSharedPointer<SafeConnection<EventLogList, CoreModel>> mModelConnection;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<EventLogList>();
if (oldEventLogList) {
disconnect(oldEventLogList);
}
auto newEventLogList = dynamic_cast<EventLogList *>(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<SortFilterList *>(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<EventLogList>();
if (!mChatGui && model) mChatGui = model->getChat();
return mChatGui;
}
void EventLogProxy::setChatGui(ChatGui *chat) {
getListModel<EventLogList>()->setChatGui(chat);
}
EventLogGui *EventLogProxy::getEventAtIndex(int i) {
auto model = getListModel<EventLogList>();
auto sourceIndex = mapToSource(index(i, 0)).row();
if (model) {
auto event = model->getAt<EventLogCore>(sourceIndex);
if (event) return new EventLogGui(event);
else return nullptr;
}
return nullptr;
}
int EventLogProxy::findFirstUnreadIndex() {
auto eventLogList = getListModel<EventLogList>();
if (eventLogList) {
auto listIndex = eventLogList->findFirstUnreadIndex();
if (listIndex != -1) {
listIndex =
dynamic_cast<SortFilterList *>(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<EventLogList, ChatMessageCore>(sourceRow);
// return l != nullptr;
return true;
}
bool EventLogProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
auto l = getItemAtSource<EventLogList, ChatMessageCore>(sourceLeft.row());
auto r = getItemAtSource<EventLogList, ChatMessageCore>(sourceRight.row());
if (l && r) return l->getTimestamp() <= r->getTimestamp();
else return true;
}

View file

@ -18,10 +18,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CHAT_MESSAGE_PROXY_H_ #ifndef EVENT_LIST_PROXY_H_
#define CHAT_MESSAGE_PROXY_H_ #define EVENT_LIST_PROXY_H_
#include "ChatMessageList.hpp" #include "EventLogList.hpp"
#include "core/proxy/LimitProxy.hpp" #include "core/proxy/LimitProxy.hpp"
#include "tool/AbstractObject.hpp" #include "tool/AbstractObject.hpp"
@ -29,30 +29,30 @@
class ChatGui; class ChatGui;
class ChatMessageProxy : public LimitProxy, public AbstractObject { class EventLogProxy : public LimitProxy, public AbstractObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(ChatGui *chatGui READ getChatGui WRITE setChatGui NOTIFY chatChanged) Q_PROPERTY(ChatGui *chatGui READ getChatGui WRITE setChatGui NOTIFY eventChanged)
public: public:
DECLARE_SORTFILTER_CLASS() DECLARE_SORTFILTER_CLASS()
ChatMessageProxy(QObject *parent = Q_NULLPTR); EventLogProxy(QObject *parent = Q_NULLPTR);
~ChatMessageProxy(); ~EventLogProxy();
ChatGui *getChatGui(); ChatGui *getChatGui();
void setChatGui(ChatGui *chat); void setChatGui(ChatGui *chat);
void setSourceModel(QAbstractItemModel *sourceModel) override; void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_INVOKABLE ChatMessageGui *getChatMessageAtIndex(int index); Q_INVOKABLE EventLogGui *getEventAtIndex(int index);
Q_INVOKABLE int findFirstUnreadIndex(); Q_INVOKABLE int findFirstUnreadIndex();
signals: signals:
void chatChanged(); void eventChanged();
void messageInserted(int index, ChatMessageGui *message); void eventInserted(int index, EventLogGui *message);
protected: protected:
QSharedPointer<ChatMessageList> mList; QSharedPointer<EventLogList> mList;
ChatGui *mChatGui = nullptr; ChatGui *mChatGui = nullptr;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

@ -5921,4 +5921,98 @@ Failed to create 1-1 conversation with %1 !</extracomment>
<translation>Ok</translation> <translation>Ok</translation>
</message> </message>
</context> </context>
<context>
<name>EventLogCore</name>
<message>
<source>conference_created_event</source>
<extracomment>'You have joined the group' : Little message to show on the event when the user join the chat group.</extracomment>
<translation>You have joined the group</translation>
</message>
<message>
<source>conference_created_terminated</source>
<extracomment>'You have left the group' : Little message to show on the event when the user leave the chat group.</extracomment>
<translation>You have left the group</translation>
</message>
<message>
<source>conference_participant_added_event</source>
<extracomment>'%1 has joined' : Little message to show on the event when someone join the chat group.</extracomment>
<translation>%1 has joined</translation>
</message>
<message>
<source>conference_participant_removed_event</source>
<extracomment>'%1 has left' : Little message to show on the event when someone leave the chat group</extracomment>
<translation>%1 has left</translation>
</message>
<message>
<source>conference_participant_set_admin_event</source>
<extracomment>'%1 is now an admin' : Little message to show on the event when someone get the admin status. %1 is somebody</extracomment>
<translation>%1 is now an admin</translation>
</message>
<message>
<source>conference_participant_unset_admin_event</source>
<extracomment>'%1 is no longer an admin' : Little message to show on the event when somebody lost its admin status. %1 is somebody</extracomment>
<translation>%1 is no longer an admin</translation>
</message>
<message>
<source>conference_security_event</source>
<extracomment>'Security level degraded by %1': Little message to show on the event when a security level has been lost.</extracomment>
<translation>Security level degraded by %1</translation>
</message>
<message>
<source>conference_ephemeral_message_enabled_event</source>
<extracomment>'Ephemeral messages have been enabled: %1' : Little message to show on the event when ephemeral has been activated. %1 is a date time</extracomment>
<translation>Ephemeral messages have been enabled: %1</translation>
</message>
<message>
<source>conference_ephemeral_message_disabled_event</source>
<extracomment>'Ephemeral messages have been disabled': Little message to show on the event when ephemeral has been deactivated.</extracomment>
<translation>Ephemeral messages have been disabled</translation>
</message>
<message>
<source>conference_subject_changed_event</source>
<extracomment>'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.</extracomment>
<translation>New subject: %1</translation>
</message>
<message>
<source>conference_ephemeral_message_lifetime_changed_event</source>
<extracomment>'Ephemeral messages have been updated: %1' : Little message to show on the event when ephemeral has been updated. %1 is a date time</extracomment>
<translation>Ephemeral messages have been updated: %1</translation>
</message>
<message numerus="yes">
<source>nSeconds</source>
<translation>
<numerusform>%1 second</numerusform>
<numerusform>%1 seconds</numerusform>
</translation>
</message>
<message numerus="yes">
<source>nMinute</source>
<translation>
<numerusform>%1 minute</numerusform>
<numerusform>%1 minutes</numerusform>
</translation>
</message>
<message numerus="yes">
<source>nHour</source>
<translation>
<numerusform>%1 hour</numerusform>
<numerusform>%1 hours</numerusform>
</translation>
</message>
<message numerus="yes">
<source>nDay</source>
<translation>
<numerusform>%1 day</numerusform>
<numerusform>%1 days</numerusform>
</translation>
</message>
<message numerus="yes">
<source>nWeek</source>
<translation>
<numerusform>%1 week</numerusform>
<numerusform>%1 weeks</numerusform>
</translation>
</message>
</context>
</TS> </TS>

View file

@ -1885,3 +1885,24 @@ bool Utils::codepointIsEmoji(uint code) {
(code >= 0x2700 && code <= 0x27BF) // Dingbats (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);
}

View file

@ -158,6 +158,10 @@ public:
Q_INVOKABLE static QString getFilename(QUrl url); Q_INVOKABLE static QString getFilename(QUrl url);
static bool codepointIsEmoji(uint code); 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); // QDir findDirectoryByName(QString startPath, QString name);
static QString getApplicationProduct(); static QString getApplicationProduct();

View file

@ -57,6 +57,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Display/Chat/ChatMessage.qml view/Control/Display/Chat/ChatMessage.qml
view/Control/Display/Chat/ChatMessageInvitationBubble.qml view/Control/Display/Chat/ChatMessageInvitationBubble.qml
view/Control/Display/Chat/ChatMessagesListView.qml view/Control/Display/Chat/ChatMessagesListView.qml
view/Control/Display/Chat/Event.qml
view/Control/Display/Contact/Avatar.qml view/Control/Display/Contact/Avatar.qml
view/Control/Display/Contact/Contact.qml view/Control/Display/Contact/Contact.qml
view/Control/Display/Contact/Presence.qml view/Control/Display/Contact/Presence.qml

View file

@ -246,7 +246,7 @@ ListView {
unread: modelData.core.unreadMessagesCount unread: modelData.core.unreadMessagesCount
} }
EffectImage { 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 && !modelData.core.lastMessage.core.isRemoteMessage
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: 14 * DefaultStyle.dp Layout.preferredHeight: 14 * DefaultStyle.dp

View file

@ -15,17 +15,17 @@ ListView {
property color backgroundColor property color backgroundColor
Component.onCompleted: { Component.onCompleted: {
var index = chatMessageProxy.findFirstUnreadIndex() var index = eventLogProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.End) positionViewAtIndex(index, ListView.End)
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) var chatMessage = eventLogProxy.getEventAtIndex(index)?.core?.chatMessage
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() if (chatMessage && !chatMessage.isRead) chatMessage.lMarkAsRead()
} }
onCountChanged: if (atYEnd) { onCountChanged: if (atYEnd) {
var index = chatMessageProxy.findFirstUnreadIndex() var index = eventLogProxy.findFirstUnreadIndex()
mainItem.positionViewAtIndex(index, ListView.End) mainItem.positionViewAtIndex(index, ListView.End)
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) var chatMessage = eventLogProxy.getEventAtIndex(index)?.core?.chatMessage
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() if (chatMessage && !chatMessage.isRead) chatMessage.lMarkAsRead()
} }
Button { Button {
@ -40,21 +40,22 @@ ListView {
anchors.bottomMargin: Math.round(18 * DefaultStyle.dp) anchors.bottomMargin: Math.round(18 * DefaultStyle.dp)
anchors.rightMargin: Math.round(18 * DefaultStyle.dp) anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
onClicked: { onClicked: {
var index = chatMessageProxy.findFirstUnreadIndex() var index = eventLogProxy.findFirstUnreadIndex()
mainItem.positionViewAtIndex(index, ListView.End) mainItem.positionViewAtIndex(index, ListView.End)
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index) var chatMessage = eventLogProxy.getEventAtIndex(index)?.core?.chatMessage
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead() if (chatMessage && !chatMessage.isRead) chatMessage.lMarkAsRead()
} }
} }
model: ChatMessageProxy { model: EventLogProxy {
id: chatMessageProxy id: eventLogProxy
chatGui: mainItem.chat chatGui: mainItem.chat
// scroll when in view and message inserted // scroll when in view and message inserted
onMessageInserted: (index, gui) => { onEventInserted: (index, gui) => {
if (!mainItem.visible) return if (!mainItem.visible) return
mainItem.positionViewAtIndex(index, ListView.End) 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 { DelegateChoice {
chatMessage: modelData roleValue: "chatMessage"
maxWidth: Math.round(mainItem.width * (3/4)) delegate:
onVisibleChanged: { ChatMessage {
if (visible && !modelData.core.isRead) modelData.core.lMarkAsRead() 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 footerPositioning: ListView.OverlayFooter
footer: Control.Control { footer: Control.Control {
visible: composeLayout.composingName !== "" visible: composeLayout.composingName !== ""

View file

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

View file

@ -16,10 +16,10 @@ RowLayout {
property alias callHeaderContent: splitPanel.headerContent property alias callHeaderContent: splitPanel.headerContent
spacing: 0 spacing: 0
onChatChanged: { //onEventChanged: {
// TODO : call when all messages read after scroll to unread feature available // TODO : call when all messages read after scroll to unread feature available
// if (chat) chat.core.lMarkAsRead() // if (chat) chat.core.lMarkAsRead()
} //}
MainRightPanel { MainRightPanel {
id: splitPanel id: splitPanel
Layout.fillWidth: true Layout.fillWidth: true