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/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<ChatList>(Constants::MainQmlUri, 1, 0, "ChatList");
qmlRegisterType<ChatProxy>(Constants::MainQmlUri, 1, 0, "ChatProxy");
qmlRegisterType<ChatGui>(Constants::MainQmlUri, 1, 0, "ChatGui");
qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui");
qmlRegisterType<ChatMessageList>(Constants::MainQmlUri, 1, 0, "ChatMessageList");
qmlRegisterType<ChatMessageProxy>(Constants::MainQmlUri, 1, 0, "ChatMessageProxy");
qmlRegisterType<EventLogGui>(Constants::MainQmlUri, 1, 0, "EventLogGui");
qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui");
qmlRegisterType<EventLogList>(Constants::MainQmlUri, 1, 0, "EventLogList");
qmlRegisterType<EventLogProxy>(Constants::MainQmlUri, 1, 0, "EventLogProxy");
qmlRegisterUncreatableType<ConferenceCore>(Constants::MainQmlUri, 1, 0, "ConferenceCore",
QLatin1String("Uncreatable"));
qmlRegisterType<ConferenceGui>(Constants::MainQmlUri, 1, 0, "ConferenceGui");

View file

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

View file

@ -76,25 +76,29 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &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<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) {
if (eventLog->getChatMessage()) lHistory.push_back(eventLog->getChatMessage());
lHistory.push_back(eventLog);
}
QList<QSharedPointer<ChatMessageCore>> messageList;
for (auto &message : lHistory) {
if (!message) continue;
auto chatMessage = ChatMessageCore::create(message);
messageList.append(chatMessage);
QList<QSharedPointer<EventLogCore>> 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<ChatCore> 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<ChatCore> me) {
});
});
mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived,
mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, // TODO onNewEvent?
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &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<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;
qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle();
QList<QSharedPointer<ChatMessageCore>> list;
for (auto &m : chatMessages) {
auto message = m->getChatMessage();
if (message) {
auto newMessage = ChatMessageCore::create(message);
list.push_back(newMessage);
}
QList<QSharedPointer<EventLogCore>> 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<ChatCore> me) {
mChatModelConnection->makeConnectToModel(
&ChatModel::chatMessageSending, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &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<ChatMessageCore> ChatCore::getLastMessageCore() const {
return mLastMessage;
}
void ChatCore::setLastMessage(QSharedPointer<ChatMessageCore> lastMessage) {
if (mLastMessage != lastMessage) {
disconnect(mLastMessage.get());
@ -357,45 +348,45 @@ void ChatCore::setUnreadMessagesCount(int count) {
}
}
QList<QSharedPointer<ChatMessageCore>> ChatCore::getChatMessageList() const {
return mChatMessageList;
QList<QSharedPointer<EventLogCore>> ChatCore::getEventLogList() const {
return mEventLogList;
}
void ChatCore::resetChatMessageList(QList<QSharedPointer<ChatMessageCore>> list) {
mChatMessageList = list;
emit messageListChanged();
void ChatCore::resetEventLogList(QList<QSharedPointer<EventLogCore>> list) {
mEventLogList = list;
emit eventListChanged();
}
void ChatCore::appendMessagesToMessageList(QList<QSharedPointer<ChatMessageCore>> list) {
void ChatCore::appendEventLogsToEventLogList(QList<QSharedPointer<EventLogCore>> 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<ChatMessageCore> message) {
if (mChatMessageList.contains(message)) return;
mChatMessageList.append(message);
emit messagesInserted({message});
void ChatCore::appendEventLogToEventLogList(QSharedPointer<EventLogCore> e) {
if (mEventLogList.contains(e)) return;
mEventLogList.append(e);
emit eventsInserted({e});
}
void ChatCore::removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCore>> list) {
void ChatCore::removeEventLogsFromEventLogList(QList<QSharedPointer<EventLogCore>> 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 {

View file

@ -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 <QSharedPointer>
#include <linphone++/linphone.hh>
class EventLogCore;
class ChatCore : public QObject, public AbstractObject {
Q_OBJECT
@ -94,12 +97,12 @@ public:
QString getChatRoomAddress() const;
QString getPeerAddress() const;
QList<QSharedPointer<ChatMessageCore>> getChatMessageList() const;
void resetChatMessageList(QList<QSharedPointer<ChatMessageCore>> list);
void appendMessageToMessageList(QSharedPointer<ChatMessageCore> message);
void appendMessagesToMessageList(QList<QSharedPointer<ChatMessageCore>> list);
void removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCore>> list);
void clearMessagesList();
QList<QSharedPointer<EventLogCore>> getEventLogList() const;
void resetEventLogList(QList<QSharedPointer<EventLogCore>> list);
void appendEventLogToEventLogList(QSharedPointer<EventLogCore> event);
void appendEventLogsToEventLogList(QList<QSharedPointer<EventLogCore>> list);
void removeEventLogsFromEventLogList(QList<QSharedPointer<EventLogCore>> 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<QSharedPointer<ChatMessageCore>> list);
void messageRemoved();
void eventListChanged();
void eventsInserted(QList<QSharedPointer<EventLogCore>> list);
void eventRemoved();
void avatarUriChanged();
void deleted();
void composingUserChanged();
@ -157,7 +160,7 @@ private:
LinphoneEnums::ChatRoomState mChatRoomState;
std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage;
QList<QSharedPointer<ChatMessageCore>> mChatMessageList;
QList<QSharedPointer<EventLogCore>> mEventLogList;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;
DECLARE_ABSTRACT_OBJECT

View file

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

View file

@ -19,6 +19,7 @@
*/
#include "ChatMessageGui.hpp"
#include "ChatMessageCore.hpp"
#include "core/App.hpp"
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.
*
* This file is part of linphone-desktop
@ -18,28 +18,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <QLocale>
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<ChatMessageList> create();
// Create a ChatMessageCore and make connections to List.
QSharedPointer<ChatMessageCore> createChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &chatMessage);
ChatMessageList(QObject *parent = Q_NULLPTR);
~ChatMessageList();
static QSharedPointer<EventLogList> create();
EventLogList(QObject *parent = Q_NULLPTR);
~EventLogList();
QSharedPointer<ChatCore> getChatCore() const;
ChatGui *getChat() const;
@ -48,18 +45,20 @@ public:
int findFirstUnreadIndex();
void setSelf(QSharedPointer<ChatMessageList> me);
void setSelf(QSharedPointer<EventLogList> me);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> 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<ChatCore> mChatCore;
QSharedPointer<SafeConnection<ChatMessageList, CoreModel>> mModelConnection;
QSharedPointer<SafeConnection<EventLogList, CoreModel>> mModelConnection;
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/>.
*/
#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<ChatMessageList> mList;
QSharedPointer<EventLogList> mList;
ChatGui *mChatGui = nullptr;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -5921,4 +5921,98 @@ Failed to create 1-1 conversation with %1 !</extracomment>
<translation>Ok</translation>
</message>
</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>

View file

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

View file

@ -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();

View file

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

View file

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

View file

@ -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 !== ""

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